Kunkun
构建扩展

后端进程

启动一个托管的长期运行子进程,并通过类型化 RPC 与其通信。

有时一个命令不够——你需要一个持久进程:一个 PTY、一个 SSH 连接池、一个原生扫描器、一个长期运行的服务器。spawnBackend 从你的扩展启动一个托管的 Node/Deno/Bun 进程,并为你提供一个类型化的 RPC 句柄。这也是扩展既可以作为 CLI 也可以作为插件发布的接缝。

两个部分

前端 —— spawnBackend

import { spawnBackend } from "@kunkunsh/sdk";

interface TerminalBackend {
  open(cmd: string): Promise<string>;   // 返回会话 id
  write(id: string, data: string): Promise<void>;
}

const conn = await spawnBackend<TerminalBackend>({
  scriptPath: "$EXTENSION/dist/backend.js",
  runtime: "node", // "auto" | "node" | "bun" | "deno"
});

const sessionId = await conn.api.open("bash");
await conn.api.write(sessionId, "ls\n");
await conn.destroy(); // 终止进程

spawnBackend 返回 { api, backendId, destroy } —— api 是一个基于 kkrpc 的类型化代理。

后端 —— exposeBackend

// src/backend.ts  (打包到 dist/backend.js)
import { exposeBackend } from "@kunkunsh/sdk/backend";
import { spawn } from "node:child_process";

const facade: TerminalBackend = {
  async open(cmd) { /* 启动 pty,返回 id */ return "s1"; },
  async write(id, data) { /* 写入 pty */ },
};

exposeBackend<TerminalBackend>(facade);

你也可以向 spawnBackend 传递 localAPI,这样后端可以回调到前端(双向 kkrpc)。

清单权限

声明允许启动的具体脚本及其运行时:

{
  "permissions": [
    { "permission": "backend", "allow": [{ "script": "$EXTENSION/dist/backend.js", "runtime": "node" }] }
  ]
}

能力(capabilities)

额外的进程权限在每个 backend scope 的 capabilities 下声明。每一项都遵循同一形状——省略 = 无,true/"all" = 全量,数组 = 限定列表:

{
  "permission": "backend",
  "allow": [{
    "script": "$EXTENSION/dist/backend.js",
    "runtime": "auto",
    "capabilities": {
      "subprocess": "all",                          // 或 ["ps", "sysctl"]
      "env": true,                                  // 或 ["HTTP_PROXY"]
      "sys": ["loadavg", "cpus", "systemMemoryInfo"],
      "nativeAddons": ["node-pty/**/*.node"],
      "ffi": ["native/libscanner.dylib"]
    }
  }]
}
能力形式授予内容
subprocess"all" | string[]子进程。程序列表可限定 Deno 的 --allow-run;Node 无法收窄(列表 ⇒ 仅 Deno)。
envtrue | string[]环境访问。true 枚举(宿主已净化、不含机密的)env;列表还会把这些 key 从宿主 env 透传进来。被会展开 process.env 的依赖所需(如 systeminformation)。
systrue | string[]系统信息(Deno --allow-sys 种类:loadavgcpussystemMemoryInfonetworkInterfaceshostnameosUptime 等)。
workersbooleanWorker 线程。
nativeAddonsstring[](glob)backend 加载的打包 .node 产物。
ffistring[]通过 Deno.dlopen() 加载的原生库。
unsandboxedboolean逃生舱——无进程沙箱。最后手段。

运行时与沙箱

某运行时只有在能至少按声明那样窄地强制每一项能力时才合格;否则会被拒绝,auto 转向下一个候选。

运行时说明
Deno最佳沙箱——限定的 --allow-read/write、按域 --allow-net--allow-run/--allow-env/--allow-sys(全量或限定)。auto 优先选择。
Node v25+Node 权限模型。全量 env: true/sys: true 隐式满足(Node 不拦截);限定env/sys/subprocess、域限定 net、FFI 除非 capabilities.unsandboxed 否则被拒绝。
Bun无沙箱——仅允许使用 capabilities.unsandboxed

"auto" 按顺序选择:Deno → Node。因此声明限定的 sys/env/subprocess 列表会把 auto 导向 Deno(Node 无法强制更窄的授予)。进程沙箱将你的清单范围作为运行时标志应用。

原生插件不能内联

.node 插件不能打包到 JS 文件中。请将插件的加载程序 + 平台 .node 文件与 dist/backend.js 放在一起,并通过 capabilities.nativeAddons 允许它们。或者惰性加载原生功能,使扩展即使没有该功能也能正常工作。

构建后端

将后端入口打包为独立的 Node/Deno 脚本:

await Bun.build({
  entrypoints: ["./src/backend.ts"],
  outdir: "./dist",
  target: "node", format: "esm",
  // 保持原生插件包外部,并将其文件复制到 dist/
});

实际示例

  • wterm-terminal-demo —— 由基于 spawnBackend 的 Node PTY 支持的自定义视图
  • kkterminal / space-lens —— 与 CLI 共享的生产级后端;参见 CLI + 插件

On this page