@sema-agent/server 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/MIGRATION.md +74 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +391 -0
  5. package/dist/boot/budget-tracing.d.ts +48 -0
  6. package/dist/boot/budget-tracing.js +86 -0
  7. package/dist/boot/config-center.d.ts +62 -0
  8. package/dist/boot/config-center.js +1002 -0
  9. package/dist/boot/coordinators.d.ts +33 -0
  10. package/dist/boot/coordinators.js +97 -0
  11. package/dist/boot/execution-env.d.ts +26 -0
  12. package/dist/boot/execution-env.js +370 -0
  13. package/dist/boot/leader.d.ts +27 -0
  14. package/dist/boot/leader.js +81 -0
  15. package/dist/boot/reapers.d.ts +53 -0
  16. package/dist/boot/reapers.js +252 -0
  17. package/dist/boot/resolve-spec.d.ts +70 -0
  18. package/dist/boot/resolve-spec.js +1072 -0
  19. package/dist/boot/runner-deps.d.ts +101 -0
  20. package/dist/boot/runner-deps.js +343 -0
  21. package/dist/boot/runtime-caps.d.ts +21 -0
  22. package/dist/boot/runtime-caps.js +62 -0
  23. package/dist/boot/session-faces.d.ts +57 -0
  24. package/dist/boot/session-faces.js +157 -0
  25. package/dist/boot/shutdown.d.ts +50 -0
  26. package/dist/boot/shutdown.js +129 -0
  27. package/dist/boot/stores.d.ts +32 -0
  28. package/dist/boot/stores.js +361 -0
  29. package/dist/boot/workflow-orchestration.d.ts +46 -0
  30. package/dist/boot/workflow-orchestration.js +150 -0
  31. package/dist/capabilities/scenarios.d.ts +5 -3
  32. package/dist/capabilities/scenarios.js +5 -3
  33. package/dist/config-center/apply-effective.js +4 -3
  34. package/dist/config-lkg.d.ts +2 -1
  35. package/dist/config-lkg.js +2 -1
  36. package/dist/config-types.d.ts +47 -7
  37. package/dist/config.d.ts +30 -13
  38. package/dist/config.js +562 -387
  39. package/dist/hooks/hook-llm.js +9 -0
  40. package/dist/http/routes/approvals-assistant.js +1 -1
  41. package/dist/http/routes/attachments.js +2 -2
  42. package/dist/http/routes/memory-policy.js +3 -3
  43. package/dist/http/routes/runs.js +1 -1
  44. package/dist/http/routes/session-sync.js +2 -2
  45. package/dist/http/routes/sessions.js +2 -2
  46. package/dist/http/routes/tasks.js +2 -2
  47. package/dist/http/routes/trace-usage.js +2 -2
  48. package/dist/http/routes/workflows.js +3 -1
  49. package/dist/http/server.d.ts +1 -1
  50. package/dist/http/server.js +25 -5
  51. package/dist/http/sse-log.js +1 -1
  52. package/dist/main.js +164 -3799
  53. package/dist/model-select.d.ts +1 -1
  54. package/dist/model-select.js +1 -1
  55. package/dist/plugins/checkpoint-store-sql.d.ts +13 -5
  56. package/dist/plugins/checkpoint-store-sql.js +10 -3
  57. package/dist/plugins/local-checkpoint-store.js +8 -2
  58. package/dist/plugins/remote-env-host.d.ts +2 -1
  59. package/dist/run-local.js +2 -1
  60. package/dist/session-titler.d.ts +3 -1
  61. package/dist/session-titler.js +2 -2
  62. package/dist/trace/project.js +4 -1
  63. package/package.json +5 -3
@@ -0,0 +1,33 @@
1
+ /**
2
+ * design/158 A10:composition root 分段 —— 进程内活体 HITL 协调器 + SendUserFile 工具面。
3
+ *
4
+ * 纯搬运:函数体逐字来自 `main.ts`(原 1443-1518 行),缩进不变;新增的只有 import 与包壳。
5
+ *
6
+ * ⚠️ **位置即契约**:`durableEnabled` 必须在 `RunnerDeps` 字面量**之前**求值([1.294 G1]:onAsk 的
7
+ * 在场性本身是 core suspendAsk 的分路条件,不能靠闭包惰性读),故本段整体排在 runner-deps 段之前;
8
+ * `sendUserFileTaskEnvs` 由 exec-env 段产出,故本段又必须排在它之后。
9
+ */
10
+ import type { ServiceConfig } from "../config.js";
11
+ import { ElicitationCoordinator } from "../elicitation.js";
12
+ import type { Logger } from "../observability/logger.js";
13
+ import { QuestionCoordinator } from "../question.js";
14
+ import { ToolApprovalCoordinator } from "../tool-approval.js";
15
+ import { SendUserFileEmitter } from "../capabilities/send-user-file-tool.js";
16
+ import { TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
17
+ import type { StoreBackend } from "../plugins/store-backend.js";
18
+ export interface LiveCoordinatorsCtx {
19
+ config: ServiceConfig;
20
+ logger: Logger;
21
+ backend: StoreBackend | undefined;
22
+ sendUserFileTaskEnvs: TaskEnvRegistry | undefined;
23
+ }
24
+ export declare function createLiveCoordinators(ctx: LiveCoordinatorsCtx): {
25
+ elicitation: ElicitationCoordinator | undefined;
26
+ question: QuestionCoordinator | undefined;
27
+ toolApproval: ToolApprovalCoordinator | undefined;
28
+ durableEnabled: boolean;
29
+ sendUserFileEmitter: SendUserFileEmitter | undefined;
30
+ sendFileLedger: import("../plugins/send-file-ledger.js").SendFileLedger | undefined;
31
+ sendUserFileToolSpec: import("@sema-agent/core").ToolSpec<import("typebox").TSchema> | undefined;
32
+ };
33
+ //# sourceMappingURL=coordinators.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { ElicitationCoordinator } from "../elicitation.js";
2
+ import { QuestionCoordinator } from "../question.js";
3
+ import { ToolApprovalCoordinator } from "../tool-approval.js";
4
+ import { SendUserFileEmitter, SEND_USER_FILE_MAX_BYTES, sendUserFileTool } from "../capabilities/send-user-file-tool.js";
5
+ import { createSandboxFileSend, TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
6
+ import { createSendUserFileIssuer } from "../plugins/send-user-file.js";
7
+ import { withLedgerRecording } from "../plugins/send-file-ledger.js";
8
+ import { basename, resolve } from "node:path";
9
+ import { stat as fsStat, readFile as fsReadFile } from "node:fs/promises";
10
+ export function createLiveCoordinators(ctx) {
11
+ const { config, logger, backend, sendUserFileTaskEnvs } = ctx;
12
+ // E23 (shell-host contract): inbound MCP elicitation coordinator (live-only HITL). Present ONLY when MCP_ELICITATION_ENABLED
13
+ // — absent ⇒ onElicit is not wired ⇒ core advertises no elicitation capability to any server (fail-closed). Shared
14
+ // by the runner (the onElicit seam) and the HTTP layer (the respond route + the per-run ALS context wraps).
15
+ const elicitation = config.mcpElicitation.enabled ? new ElicitationCoordinator(config.mcpElicitation.throttle) : undefined;
16
+ // §4④: AskUserQuestion LIVE-stream HITL coordinator — the sibling of `elicitation`. Present ⇒ wired onto
17
+ // `RunnerDeps.onQuestion` (core mounts the AskUserQuestion tool + routes live asks here) AND consumed by the HTTP layer
18
+ // (the `POST /v1/questions/:id/respond` route + the per-run ALS context wraps on the streaming legs). Absent ⇒ core
19
+ // mounts the tool with the headless default (a run never hangs; the model just can't get a live answer).
20
+ const question = config.askQuestionEnabled ? new QuestionCoordinator() : undefined;
21
+ // [816]/[820]②: the live tool-approval HITL coordinator — the sibling of `question` on core's `RunnerDeps.onAsk`
22
+ // seam (1.290 sync-ask leg). Present ⇒ a policy `ask` on a LIVE streaming leg becomes a `tool_approval` frame the
23
+ // shell renders as the CC three-choice card, answered via POST /v1/tool-approvals/:id/respond.
24
+ // [1535] 正名(旧文「background/workflow ⇒ headless auto-deny stands」已过时):宿主 sync 腿现同时装
25
+ // `spec.onAsk = boundAsk(ctx)`(server.ts 装配点)——core 继承链把闭包冻给委派子代,**bg/嵌套子代的
26
+ // ask 浮到宿主 live 流**(带 sourceTaskId);真正无宿主流的腿(durable-submit/headless resume)才留
27
+ // 「unavailable → durable park;无 park 设施 core fail-closed deny」。durable 部署 G1 语义不变。
28
+ const toolApproval = config.toolApprovalEnabled ? new ToolApprovalCoordinator() : undefined;
29
+ // [1.294 G1] durable 姿势必须在 runnerDeps 字面量之前可判(onAsk 的「在场性」本身就是 core suspendAsk 的
30
+ // 分路条件,不能再靠闭包惰性读)——判据与 checkpointStore 的构造条件同源(backend?.checkpoint 存在 ∧
31
+ // DURABLE_APPROVAL;store 本体已上移至 subRunner 构造前,[1535] 断点② 双 Runner 挂载)。
32
+ const durableEnabled = backend?.checkpoint !== undefined && config.durableApproval === true;
33
+ // SendUserFile(真 CC 契约,clay 2026-07-14)两种 lane 形态,其余缺席=诚实(工具不进 roster):
34
+ // - host lane:本地盘读+服务端上传,【单用户 only】——host 多租户=任意路径读→公网 URL 的 exfil 面,
35
+ // clay 拍永久关死,不留口子。路径=绝对或相对 process.cwd(用户目录语义,与 CC 一致)。
36
+ // - e2b/k8s lane(v2,Plan B 直传):沙箱内 stat+`curl -T` 打 server 预签的单对象 PUT(字节不中转、
37
+ // 凭据不进沙箱);沙箱自身文件系统=租户边界,故任意租户可用。
38
+ // - ssh lane(后补 2026-07-14):同一直传链,但 peer=跨任务共享的真实主机(非租户边界)→
39
+ // 【单用户 only】,租户门在 sandboxSendLaneEnabled(多租户下注册表不建,工具诚实缺席)。
40
+ // adb/local-docker 仍后补。
41
+ // 签发面缺 S3_PUBLIC_ENDPOINT 时 issue-time fail-loud(工具在但用即报修法);100MiB/文件帽在各 send 面。
42
+ const sendUserFileEmitter = (() => {
43
+ if (!config.sendUserFile)
44
+ return undefined;
45
+ if (sendUserFileTaskEnvs)
46
+ return new SendUserFileEmitter(); // e2b/k8s/ssh(单用户):注册表已就位(lane+租户+签发面判定)
47
+ // unset REMOTE_EXEC(in-process/TOC 本地形态)有意归入 host 语义:exec 与文件都在本机,单用户门
48
+ // 同样把关(codex 复审提出收紧到显式 "host";判定=不收——unset 单机+配了 S3 是合法 dev/TOC 场景,
49
+ // 且与 1.187.0 已发行为一致;真正的红线是 requirePrincipal 多租户,这里恒关)。
50
+ const provider = config.remoteExec?.provider ?? "host";
51
+ if (provider === "host" && config.requirePrincipal !== true)
52
+ return new SendUserFileEmitter();
53
+ return undefined;
54
+ })();
55
+ // SendUserFile 账本(多租户治理面,[768]②a 后半):key 里的 scope 段是哈希(URL 不泄 principal),
56
+ // 按租户列/撤销的 scope↔对象映射记在 server 侧账本(四后端孪生,sendfile_link / JSONL)。有 backend 即建
57
+ // (签发面也配了才有写点);无 backend(env-only worker)= 无账本 = 不记账不 501 治理面(诚实缺席,
58
+ // capabilities.sendUserFileLedger=false)。
59
+ const sendFileLedger = config.sendUserFile && backend ? backend.sendFileLedger() : undefined;
60
+ const sendUserFileToolSpec = (() => {
61
+ if (!sendUserFileEmitter || !config.sendUserFile)
62
+ return undefined;
63
+ const issuer = createSendUserFileIssuer(config.sendUserFile);
64
+ // 两条 lane 的 send 都把 ctx.principal(core VERIFIED)传给签发面 → key 带哈希 scope 段 + link 携原文 scope。
65
+ const rawSend = sendUserFileTaskEnvs
66
+ ? createSandboxFileSend({
67
+ registry: sendUserFileTaskEnvs,
68
+ prepare: (filename, scope) => issuer.prepareDirectUpload(filename, undefined, scope),
69
+ maxBytes: SEND_USER_FILE_MAX_BYTES,
70
+ })
71
+ : async (p, sendCtx) => {
72
+ const resolved = resolve(p);
73
+ const st = await fsStat(resolved).catch(() => undefined);
74
+ if (!st || !st.isFile())
75
+ throw new Error(`file not found (or not a regular file): ${p} — ls first; absolute paths are safest`);
76
+ if (st.size > SEND_USER_FILE_MAX_BYTES)
77
+ throw new Error(`file exceeds the ${Math.floor(SEND_USER_FILE_MAX_BYTES / (1024 * 1024))} MiB per-file cap (${st.size} bytes)`);
78
+ const bytes = await fsReadFile(resolved);
79
+ // 读后复验(codex 复审 MED):stat→read 间文件可增长——上传的是读到的字节,帽必须按真实字节数把关。
80
+ if (bytes.byteLength > SEND_USER_FILE_MAX_BYTES)
81
+ throw new Error(`file exceeds the ${Math.floor(SEND_USER_FILE_MAX_BYTES / (1024 * 1024))} MiB per-file cap (${bytes.byteLength} bytes)`);
82
+ return issuer.issue(bytes, basename(resolved), undefined, sendCtx.principal);
83
+ };
84
+ // 账本写点(两 lane 同语义):上传+verify 成功后、file_link 帧前记账;记账失败=该文件 fail-loud +
85
+ // best-effort 删已传对象(治理面必须有行;删失败 warn)。无账本(env-only)= 直通,行为与 1.189 一致。
86
+ const send = sendFileLedger
87
+ ? withLedgerRecording(rawSend, {
88
+ ledger: sendFileLedger,
89
+ deleteObject: (bucket, key) => issuer.deleteObject(bucket, key),
90
+ warn: (msg, meta) => logger.warn(msg, meta),
91
+ })
92
+ : rawSend;
93
+ return sendUserFileTool({ send, emitter: sendUserFileEmitter });
94
+ })();
95
+ return { elicitation, question, toolApproval, durableEnabled, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec };
96
+ }
97
+ //# sourceMappingURL=coordinators.js.map
@@ -0,0 +1,26 @@
1
+ import { NodeLspManager } from "@sema-agent/core";
2
+ import { TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
3
+ import { SessionEnvironmentSelection } from "../capabilities/select-environment-tool.js";
4
+ import type { ServiceConfig } from "../config.js";
5
+ import type { Logger } from "../observability/logger.js";
6
+ import type { Metrics } from "../observability/metrics.js";
7
+ import { PerTaskImageRegistry } from "../per-task-image.js";
8
+ import { type TaskAttachmentStore } from "../plugins/task-attachment-store.js";
9
+ export interface ExecutionEnvCtx {
10
+ config: ServiceConfig;
11
+ logger: Logger;
12
+ metrics: Metrics;
13
+ taskAttachmentStore: TaskAttachmentStore | undefined;
14
+ }
15
+ export declare function createExecutionEnv(ctx: ExecutionEnvCtx): {
16
+ perTaskImage: PerTaskImageRegistry;
17
+ sessionEnvSelection: SessionEnvironmentSelection;
18
+ perSessionCwd: Map<string, string>;
19
+ setSessionCwd: (sid: string, cwd: string) => void;
20
+ setSessionShellEnv: (sid: string, env: Record<string, string>) => void;
21
+ executionEnvFactory: import("@sema-agent/core").ExecutionEnvFactory | undefined;
22
+ worktreeReap: (() => Promise<void>) | undefined;
23
+ sendUserFileTaskEnvs: TaskEnvRegistry | undefined;
24
+ lspManager: import("../lsp/manager.js").E2bLspManager | NodeLspManager | undefined;
25
+ };
26
+ //# sourceMappingURL=execution-env.d.ts.map
@@ -0,0 +1,370 @@
1
+ /**
2
+ * design/158 A10:composition root 分段 —— 执行环境装配(per-task 镜像 / cwd·shellEnv 注册表 /
3
+ * 五条 remote-exec 腿的工厂 / 三层工厂装饰 / LSP 管理器)。
4
+ *
5
+ * 纯搬运:函数体逐字来自 `main.ts`(原 997-1302 行),缩进不变;新增的只有 import 与包壳。
6
+ *
7
+ * ⚠️ **位置即契约 —— 装饰顺序就是行为**:`executionEnvFactory` 由内向外被依次包装,
8
+ * 次序逐字保留不可动:
9
+ * base(e2b|k8s|ssh|adb|host|local-docker)
10
+ * → `withRemoteScratchpad`(远程车道首 exec 才 mkdir)
11
+ * → `withWorktreeIsolation`(host 车道 per-agent worktree,定 cwd)
12
+ * → `TaskEnvRegistry.wrapFactory`(SendUserFile 直传登记 taskId→env)
13
+ * → 附件物化(**最外层**:内层先定 cwd,附件才落在最终 cwd —— 原文逐字注释即此)。
14
+ * 另:`lspManager` 声明在 host 工厂**之后**,host 工厂闭包对它是同作用域前向引用(闭包每任务才跑,
15
+ * 那时已初始化)——本段整体搬运保住了这个前向引用,拆开两个模块就会破。
16
+ */
17
+ import { existsSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { NodeLspManager } from "@sema-agent/core";
20
+ import { sandboxSendLaneEnabled, TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
21
+ import { SessionEnvironmentSelection } from "../capabilities/select-environment-tool.js";
22
+ import { createE2bLspManager } from "../lsp/e2b-manager.js";
23
+ import { evictLspOnDestroy } from "../lsp-evict.js";
24
+ import { PerTaskImageRegistry } from "../per-task-image.js";
25
+ import { adbExecutionEnvFactory } from "../plugins/remote-env-adb.js";
26
+ import { e2bExecutionEnvFactory } from "../plugins/remote-env-e2b.js";
27
+ import { hostExecutionEnvFactory, RemoteHostExecutionEnv } from "../plugins/remote-env-host.js";
28
+ import { k8sExecutionEnvFactory } from "../plugins/remote-env-k8s.js";
29
+ import { localDockerExecutionEnvFactory } from "../plugins/remote-env-local-docker.js";
30
+ import { sshExecutionEnvFactory } from "../plugins/remote-env-ssh.js";
31
+ import { isRemoteScratchpadLane, withRemoteScratchpad } from "../plugins/remote-scratchpad.js";
32
+ import { FileSchedulerBackend, defaultSchedulerStorePath } from "../plugins/scheduler-support.js";
33
+ import { materializeAttachmentsInto } from "../plugins/task-attachment-store.js";
34
+ import { reapOrphanWorktrees, withWorktreeIsolation } from "../plugins/worktree-isolation.js";
35
+ import { customPkgSourceFromEnv, derivePkgSourceEnv } from "../sandbox-pkg-source.js";
36
+ import { effectiveHostWorkspace } from "../task-cwd.js";
37
+ export function createExecutionEnv(ctx) {
38
+ const { config, logger, metrics, taskAttachmentStore } = ctx;
39
+ // design/48 v1b: deployment-level remote execution(部署级路由 + 懒汉连接). When
40
+ // REMOTE_EXEC=e2b, this deployment is a "code-agent worker" — each task's hand runs in a per-task E2B VM,
41
+ // provisioned lazily on first hand use (a plan-only task pays zero VM cost). Unset → in-process stub env.
42
+ // design/48 v1b + design/61: pick the remote-exec backend by provider (peer adapters). SSH/ADB target REAL
43
+ // systems (not isolated/suspendable) — the leader's autonomy + the design/37 gate + HITL run accordingly.
44
+ // §7 P0.5 per-task sandbox image: the trusted-control-plane bridge from resolveSpec (resolves the requested
45
+ // profile → digest with the caller's principal, fail-closed) to the k8s factory (applies it per-pod). See
46
+ // per-task-image.ts + resolveSpec below. Worker-global default image is the fallback when no profile is requested.
47
+ const perTaskImage = new PerTaskImageRegistry();
48
+ // RFC A2: session-keyed environment selection (PROFILE intent, never a digest) written by the model-facing
49
+ // SelectEnvironment tool; resolveSpec re-resolves it FAIL-CLOSED per task (same re-admit as a body profile).
50
+ const sessionEnvSelection = new SessionEnvironmentSelection();
51
+ // [#40 / TOC cwd seam] per-session launch dir → the host lane's agent workspace (resolveSpec registers it gated by
52
+ // cwdHonored; the host factory reads it by ctx.sessionId). Only ever written for the single-user host lane.
53
+ // BOUNDED (avoid unbounded growth over the persistent local engine's lifetime): an LRU cap; the shell
54
+ // re-sends cwd on every request, so evicting a stale session is harmless (it re-registers on next use).
55
+ const MAX_CWD_SESSIONS = 4096;
56
+ const perSessionCwd = new Map();
57
+ const setSessionCwd = (sid, cwd) => {
58
+ perSessionCwd.delete(sid); // re-insert at the tail = most-recently-used
59
+ perSessionCwd.set(sid, cwd);
60
+ if (perSessionCwd.size > MAX_CWD_SESSIONS)
61
+ perSessionCwd.delete(perSessionCwd.keys().next().value); // evict oldest
62
+ };
63
+ // [R-survey / TOC shellEnv seam, core PLAN批注] per-session `settings.env` → the host lane's agent shell env
64
+ // (resolveSpec registers it gated by cwdHonored — single-user host lane only; the host factory merges it by
65
+ // ctx.sessionId). design/107 "env = capability axis". Same LRU bound + re-send-on-every-request semantics as cwd.
66
+ const perSessionShellEnv = new Map();
67
+ const setSessionShellEnv = (sid, env) => {
68
+ perSessionShellEnv.delete(sid);
69
+ perSessionShellEnv.set(sid, env);
70
+ if (perSessionShellEnv.size > MAX_CWD_SESSIONS)
71
+ perSessionShellEnv.delete(perSessionShellEnv.keys().next().value);
72
+ };
73
+ let executionEnvFactory;
74
+ // SVC-3 worktree isolation: the reaper (defined far below) reuses ONE long-lived git base env + repoRoot to
75
+ // `git worktree prune` crash-orphaned worktrees. Holders are populated when the wrapper is wired (host lane).
76
+ let worktreeReap;
77
+ if (config.remoteExec?.provider === "e2b") {
78
+ executionEnvFactory = e2bExecutionEnvFactory({
79
+ apiKey: config.remoteExec.apiKey,
80
+ ...(config.remoteExec.template ? { template: config.remoteExec.template } : {}),
81
+ ...(config.remoteExec.timeoutMs != null ? { timeoutMs: config.remoteExec.timeoutMs } : {}),
82
+ ...(config.remoteExec.livenessMs != null ? { livenessMs: config.remoteExec.livenessMs } : {}),
83
+ ...(config.remoteExec.allowInternetAccess != null ? { allowInternetAccess: config.remoteExec.allowInternetAccess } : {}),
84
+ // [1452]/[1454] CWD-A: workspace 根透传(E2B_MOUNT_PATH)——缺省仍 /home/user(adapter 默认)。
85
+ ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
86
+ // RFC B5: region package-source env set rides the operator-trusted sandboxEnv — ENV-FIRST, the derived
87
+ // ecosystem overrides (pip/uv/npm/go/rustup/flutter/…) beat the image's baked CN ENV on existing images,
88
+ // no rebake needed. Explicit E2B_SANDBOX_ENV keys win over derived on collision (deliberate override).
89
+ ...(() => {
90
+ const merged = { ...derivePkgSourceEnv(config.sandboxPkgSource, customPkgSourceFromEnv()), ...(config.remoteExec.sandboxEnv ?? {}) };
91
+ return Object.keys(merged).length > 0 ? { sandboxEnv: merged } : {};
92
+ })(),
93
+ logger,
94
+ metrics,
95
+ });
96
+ }
97
+ else if (config.remoteExec?.provider === "k8s") {
98
+ const k8sCfg = {
99
+ image: config.remoteExec.image,
100
+ ...(config.remoteExec.apiUrl ? { apiUrl: config.remoteExec.apiUrl } : {}),
101
+ ...(config.remoteExec.token ? { token: config.remoteExec.token } : {}),
102
+ ...(config.remoteExec.caCert ? { caCert: config.remoteExec.caCert } : {}),
103
+ ...(config.remoteExec.insecureTls ? { insecureTls: true } : {}),
104
+ ...(config.remoteExec.namespace ? { namespace: config.remoteExec.namespace } : {}),
105
+ ...(config.remoteExec.runtimeClass != null ? { runtimeClass: config.remoteExec.runtimeClass } : {}),
106
+ ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
107
+ ...(config.remoteExec.timeoutMs != null ? { timeoutMs: config.remoteExec.timeoutMs } : {}),
108
+ // Resource-profile knobs. config.ts reads K8S_MEMORY/K8S_CPU; this passthrough was
109
+ // missing, so the adapter silently stayed on its 2Gi default (Kata VM = default_memory 2G + limit 2G = 4G
110
+ // MemTotal — task02b run9/10 integration gradle OOM'd there even after the env was set).
111
+ ...(config.remoteExec.memory ? { memory: config.remoteExec.memory } : {}),
112
+ ...(config.remoteExec.cpu ? { cpu: config.remoteExec.cpu } : {}),
113
+ ...(config.remoteExec.s3Snapshot ? { s3Snapshot: config.remoteExec.s3Snapshot } : {}),
114
+ // RFC B5: region package-source env set → pod container env (operator-trusted, never task-controlled).
115
+ // ENV-FIRST: pod env beats image ENV and reaches every exec — the derived ecosystem overrides switch
116
+ // region on existing images; the SEMA_PKG_SOURCE marker drives the in-image hook (file-bound pieces).
117
+ ...(() => {
118
+ const derived = derivePkgSourceEnv(config.sandboxPkgSource, customPkgSourceFromEnv());
119
+ return Object.keys(derived).length > 0 ? { podEnv: derived } : {};
120
+ })(),
121
+ };
122
+ // §7 P0.5: per-pod image override. resolveSpec resolved the requested profile→digest (with the caller's
123
+ // principal, fail-closed) and registered it by sessionId — the ONLY identifier stable across the factory ctx
124
+ // on every path (adversarial-review round-2: /v1/runs mints its own durable taskId that CLOBBERS spec.taskId,
125
+ // so taskId-keying misses on the primary path; sessionId survives). get() is non-removing (the factory may be
126
+ // invoked >once per logical task). Absent ⇒ the worker-global k8sCfg.image. Building the factory per task is a
127
+ // cheap closure alloc; the ref is immutable (repo@digest). podSpecPatch can NOT carry the image (hard invariant).
128
+ executionEnvFactory = (ctx) => k8sExecutionEnvFactory({ ...k8sCfg, image: perTaskImage.get(ctx.sessionId) ?? k8sCfg.image })(ctx);
129
+ }
130
+ else if (config.remoteExec?.provider === "ssh") {
131
+ executionEnvFactory = sshExecutionEnvFactory({
132
+ host: config.remoteExec.host,
133
+ username: config.remoteExec.username,
134
+ privateKey: config.remoteExec.privateKey,
135
+ ...(config.remoteExec.port != null ? { port: config.remoteExec.port } : {}),
136
+ ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
137
+ });
138
+ }
139
+ else if (config.remoteExec?.provider === "adb") {
140
+ executionEnvFactory = adbExecutionEnvFactory({
141
+ serial: config.remoteExec.serial,
142
+ ...(config.remoteExec.adbPath ? { adbPath: config.remoteExec.adbPath } : {}),
143
+ ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
144
+ });
145
+ }
146
+ else if (config.remoteExec?.provider === "host") {
147
+ // DUAL-MODE §5: the TOC `host` lane — run on THIS machine, no container (isolation=none). Fan-out still
148
+ // works (bounded by one box). Secrets are env-NAMEs the host resolves from its own process.env.
149
+ {
150
+ // R7 self-wake: inject the self-wake SchedulerCapability backend on the single-user TOC host lane
151
+ // (opt-in SCHEDULER_ENABLED). `hasScheduler(env)` then mounts CronCreate/CronDelete/CronList(旧名 CronCancel、Sleep 已 design/136 撤除——L8 注释订正,且挂载在 core prepare-task 非 createHandsToolkit); the TOC shell
152
+ // daemon reads the SAME ~/.sema/scheduled_tasks.json store (registry-core /node binding) and fires due intents.
153
+ // Multi-tenant (requirePrincipal) routes scheduling to center, NOT this host daemon → gate it off there. ONE
154
+ // backend instance at boot → every host task shares the same store file.
155
+ const hostScheduler = config.schedulerEnabled && config.requirePrincipal !== true
156
+ ? new FileSchedulerBackend({
157
+ ...(config.schedulerStorePath ? { storePath: config.schedulerStorePath } : {}),
158
+ // [1009]② host-signal: the spawning shell knows whether a resident daemon will honor session
159
+ // wakeups — SCHEDULER_SESSION_WAKEUP=false flips the capability off at CONSTRUCTION (instance-
160
+ // lifetime snapshot, core TOCTOU 契约) so core's ScheduleWakeup refuses up front.
161
+ ...(config.schedulerSessionWakeup === false ? { caps: { supportsSessionWakeup: false } } : {}),
162
+ })
163
+ : undefined;
164
+ if (hostScheduler)
165
+ logger.info(`R7: self-wake scheduler enabled (host lane), store=${config.schedulerStorePath ?? defaultSchedulerStorePath()}`);
166
+ const hostCfg = {
167
+ ...(config.remoteExec.workspaceBase ? { workspaceBase: config.remoteExec.workspaceBase } : {}),
168
+ ...(config.remoteExec.commandTimeoutMs != null ? { commandTimeoutMs: config.remoteExec.commandTimeoutMs } : {}),
169
+ ...(hostScheduler ? { scheduler: hostScheduler } : {}),
170
+ // design/103 background shell: single-user host lane only (parity with scheduler/cwd). Multi-tenant host
171
+ // lane (which shouldn't exist — host runs on the worker's own box) → INERT (run_in_background/etc. don't mount).
172
+ backgroundShell: config.requirePrincipal !== true,
173
+ };
174
+ // [#40 / TOC cwd seam] per-request workspace: if resolveSpec registered a caller `cwd` for this session (gated by
175
+ // cwdHonored — single-user host lane only), run the agent VERBATIM in that dir (persistent, never deleted — the
176
+ // user's project). Else the boot-time host config (ephemeral random subdir). Mirrors the k8s perTaskImage wrap.
177
+ // core 1.219 `ctx.parentCwd` (dogfood finding: sub-agents landed in an EMPTY sandbox): a DELEGATED child
178
+ // (workflow ctx.agent / Task tool) now roots at its PARENT's working dir — CC parity — via core's TRUSTED
179
+ // RunInternals channel. Precedence in effectiveHostWorkspace: session cwd → parentCwd (unless the child asked
180
+ // for isolation:"worktree" — that wrapper wins below) → ephemeral. Container lanes never consult this.
181
+ executionEnvFactory = (ctx) => {
182
+ const cwd = effectiveHostWorkspace(perSessionCwd.get(ctx.sessionId), ctx);
183
+ const shellEnv = perSessionShellEnv.get(ctx.sessionId);
184
+ const env = hostExecutionEnvFactory({
185
+ ...hostCfg,
186
+ ...(cwd ? { workspaceDir: cwd } : {}),
187
+ ...(shellEnv ? { env: shellEnv } : {}), // R-survey: per-task shell env (merged UNDER per-command options.env by the host adapter)
188
+ })(ctx);
189
+ // core 1.191: an EPHEMERAL host workspace (no honored `cwd` → a per-task `sema-host-<id>` dir destroy()
190
+ // rm's) evicts its LSP servers at task-end so a busy server doesn't accumulate one heavy language server per
191
+ // task. cwdHonored (persistent project) is NOT evicted — its stable root stays warm across turns (CC-parity);
192
+ // worktree/fan-out is bounded by maxSessions=16. Only wraps when a NodeLspManager is actually active.
193
+ return lspManager && !cwd ? evictLspOnDestroy(env, lspManager) : env;
194
+ };
195
+ }
196
+ }
197
+ else if (config.remoteExec?.provider === "local-docker") {
198
+ // DUAL-MODE §5: the TOC `local-docker` lane — a per-task container on THIS machine's docker daemon
199
+ // (isolation:true, suspendable:false). The base image comes ENTIRELY from config (no docker.io default —
200
+ // domestic-images iron rule). Secrets are resolved env-NAME→value here (config.ts already did the forward).
201
+ executionEnvFactory = localDockerExecutionEnvFactory({
202
+ image: config.remoteExec.image,
203
+ ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
204
+ ...(config.remoteExec.dockerPath ? { dockerPath: config.remoteExec.dockerPath } : {}),
205
+ ...(config.remoteExec.dockerHost ? { dockerHost: config.remoteExec.dockerHost } : {}),
206
+ ...(config.remoteExec.memory ? { memory: config.remoteExec.memory } : {}),
207
+ ...(config.remoteExec.cpus != null ? { cpus: config.remoteExec.cpus } : {}),
208
+ ...(config.remoteExec.network ? { network: config.remoteExec.network } : {}),
209
+ ...(config.remoteExec.commandTimeoutMs != null ? { commandTimeoutMs: config.remoteExec.commandTimeoutMs } : {}),
210
+ ...(config.remoteExec.env ? { env: config.remoteExec.env } : {}),
211
+ });
212
+ }
213
+ // [848] remote scratchpad: decorate the REMOTE sandbox lanes (e2b/k8s/local-docker/ssh) so each env
214
+ // lazily `mkdir -p`s `/tmp/scratchpad/<sessionId>` on its FIRST exec — never at factory time (e2b is
215
+ // lazy-VM; a factory-time exec would force-boot the VM). adb is skipped (no standard /tmp on Android);
216
+ // host keeps its worker-local scratchpad (ensureScratchpadDir at the envFacts consumer). The envFacts
217
+ // advertisement below uses the SAME remoteScratchpadDirFor rule, so fact and mkdir can never disagree.
218
+ if (executionEnvFactory && isRemoteScratchpadLane(config.remoteExec?.provider)) {
219
+ executionEnvFactory = withRemoteScratchpad(executionEnvFactory, logger);
220
+ }
221
+ // SVC-3 (design/97 CORE-6): wrap the factory with git-worktree isolation. When core marks an agent
222
+ // `isolation:"worktree"` (TRUSTED RunInternals — never a TaskSpec), the wrapper mints a per-agent detached
223
+ // worktree under the operator-trusted repoRoot; otherwise it passes the base env through untouched. v1
224
+ // covers the `host` lane (the TOC fan-out lane). Other lanes would need a worktree-ROOTED REMOTE env from
225
+ // `rootEnvAt` (a follow-on; a host/NodeExecutionEnv worktree is not durable-suspendable — see the plugin
226
+ // header) — they are left unwrapped here, isolation:"worktree" is then a no-op for them (the base env wins).
227
+ if (config.worktreeIsolation && executionEnvFactory) {
228
+ // M4 (adversarial-review): a mis-wired WORKTREE_REPO_ROOT must fail LOUD at boot, not per-task at runtime. The
229
+ // root must exist AND be a git repo (`.git` dir or worktree-link file). On a bad root, log an error + LEAVE
230
+ // isolation OFF (the factory unwrapped) rather than wiring a factory that throws on every isolated agent.
231
+ const wtRoot = config.worktreeIsolation.repoRoot;
232
+ const wtValid = existsSync(wtRoot) && existsSync(join(wtRoot, ".git"));
233
+ if (config.remoteExec?.provider === "host" && !wtValid) {
234
+ logger.error("worktree_isolation_disabled_bad_repo_root", {
235
+ repoRoot: wtRoot,
236
+ reason: existsSync(wtRoot) ? "not a git repository (no .git)" : "path does not exist",
237
+ note: "WORKTREE_ISOLATION_ENABLED is set but WORKTREE_REPO_ROOT is not a usable git repo — isolation left OFF; isolation:'worktree' agents run on the shared base env. Fix the path or unset the flag.",
238
+ });
239
+ }
240
+ else if (config.remoteExec?.provider === "host") {
241
+ // The SHARED base env that runs `git worktree add/remove/prune` (cwd:repoRoot is passed explicitly by
242
+ // core on every git call, so this env's own root is irrelevant — it just needs `exec` + git on PATH).
243
+ // ONE long-lived env per deployment; the reaper reuses it. `inheritEnv:"all"` so the operator's git
244
+ // config/credentials are visible (this is the operator's own machine + own repo, the TOC posture).
245
+ // systematic-audit (gate-fail-direction): every worktree-lane RemoteHostExecutionEnv carries the SAME
246
+ // background-shell gate as the primary host factory (main.ts ~442) — not the env's `?? true` default. The
247
+ // git-base env is git-ops only (agents run on rootEnvAt below), but gating it keeps the policy uniform / future-proof.
248
+ const gitBaseEnv = new RemoteHostExecutionEnv({ workspaceDir: config.worktreeIsolation.repoRoot, inheritEnv: "all", backgroundShell: config.requirePrincipal !== true });
249
+ executionEnvFactory = withWorktreeIsolation(executionEnvFactory, {
250
+ repoRoot: config.worktreeIsolation.repoRoot,
251
+ ...(config.worktreeIsolation.allowedRoots ? { allowedRoots: config.worktreeIsolation.allowedRoots } : {}),
252
+ ...(config.worktreeIsolation.commit ? { commit: config.worktreeIsolation.commit } : {}),
253
+ baseEnvForGit: gitBaseEnv,
254
+ // Host-lane worktree-rooted env: a host adapter in PERSISTENT-DIR mode — its own destroy() does NOT
255
+ // rm the dir (so core's `git worktree remove` owns teardown), and it keeps the host adapter's secret
256
+ // scrub + timeout semantics (vs a bare NodeExecutionEnv). inheritEnv defaults to "scrub" (model-driven).
257
+ rootEnvAt: (dir) => {
258
+ const env = new RemoteHostExecutionEnv({ workspaceDir: dir, backgroundShell: config.requirePrincipal !== true });
259
+ // A worktree root is per-agent + ephemeral (core's `git worktree remove` reclaims it at
260
+ // task-end), so evict its LSP servers DETERMINISTICALLY too — not just via the maxSessions LRU backstop. The
261
+ // worktree dir IS the LSP cache root (env.cwd), so evict(env.cwd) on destroy hits it. (`lspManager` is declared
262
+ // below but this closure only runs per-task, well after it's initialized — same forward-ref as the host factory.)
263
+ return lspManager ? evictLspOnDestroy(env, lspManager) : env;
264
+ },
265
+ logger,
266
+ });
267
+ // 复审 2026-07-30 F3:in-flight 单飞语义内建在铸造处——本函数有**两个**调用点(下方 boot recovery
268
+ // sweep 与 reapers.ts 的周期 tick),守卫只装 tick 侧会漏 boot 长跑(>REAP_INTERVAL_SEC 的
269
+ // `git worktree prune` 与首个 tick 叠跑,正是守卫要防的并发)。装在源头,调用点全免疫。
270
+ {
271
+ let inFlight = false;
272
+ worktreeReap = () => {
273
+ if (inFlight)
274
+ return Promise.resolve();
275
+ inFlight = true;
276
+ return reapOrphanWorktrees(gitBaseEnv, config.worktreeIsolation.repoRoot, logger).finally(() => (inFlight = false));
277
+ };
278
+ }
279
+ logger.info("worktree_isolation_enabled", { provider: "host", repoRoot: config.worktreeIsolation.repoRoot });
280
+ // BOOT recovery sweep: reap any worktrees orphaned by a crash BEFORE this process started (the periodic
281
+ // reaper would otherwise wait a full interval). Best-effort, fire-and-forget; never blocks boot.
282
+ void worktreeReap().catch(() => undefined);
283
+ }
284
+ else {
285
+ // Not a no-op-silent: an operator who set WORKTREE_ISOLATION_ENABLED on a non-host lane should know it
286
+ // doesn't take effect yet (the worktree-rooted REMOTE env is a follow-on). Fail-loud-ish via a warning.
287
+ logger.warn("worktree_isolation_unsupported_lane", {
288
+ provider: config.remoteExec?.provider,
289
+ note: "git-worktree isolation v1 covers only the 'host' lane — isolation:'worktree' is a no-op here (the base env is used). Worktree-rooted remote envs are a follow-on.",
290
+ });
291
+ }
292
+ }
293
+ // SendUserFile v2(clay 拍 2026-07-14,Plan B 直传):沙箱 lane 的文件源需要「工具执行时拿到当前
294
+ // 任务的 env」——core 把 per-task env 关在 prepare-task 闭包里(ToolExecuteContext 无 env 面),但
295
+ // 工厂是我们装配的、工厂 ctx 自带 taskId,包一层登记 taskId→env(destroy 时注销)即可,零 core 改动
296
+ // (withWorktreeIsolation 同款包装先例)。lane 门+租户门=sandboxSendLaneEnabled(判定依据在其 doc):
297
+ // e2b/k8s=一任务一沙箱(沙箱文件系统=租户边界)任意租户可用;ssh(后补 2026-07-14)=同一 exec 契约
298
+ // (timeout 秒×1000/Result shape 同形)直传链零适配,但 peer=一台跨任务共享的常驻真实主机
299
+ // (sshExecutionEnvFactory 忽略 ctx、destroy=断连不删文件、isolation:false)→「沙箱 scoped 读=租户
300
+ // 隔离」不成立,与 host 同门单用户 only(requirePrincipal!==true 恒关多租户,不留口子)。
301
+ // 其余 lane 不包(零开销);签发面未配同样不包。
302
+ const sendUserFileTaskEnvs = (() => {
303
+ const provider = config.remoteExec?.provider;
304
+ if (!config.sendUserFile || !executionEnvFactory || !sandboxSendLaneEnabled(provider, config.requirePrincipal))
305
+ return undefined;
306
+ const registry = new TaskEnvRegistry();
307
+ executionEnvFactory = registry.wrapFactory(executionEnvFactory);
308
+ return registry;
309
+ })();
310
+ // D-1 附件物化包装(半场③):env 每次建立(fresh + resume/重建沙箱)都把该 session 绑定的附件写进
311
+ // 工作目录 `attachments/`。全 lane 统一(host/e2b/k8s/ssh/adb 都走 factory;ExecutionEnv.writeFile
312
+ // 双向通用);路径与 objective 告知共用 materializedRelPaths(确定性,两处不漂)。**fail-loud**:
313
+ // objective 已向模型宣告文件在场,静默缺文件=模型按幻影文件行动,比任务失败更糟 ⇒ 写失败即抛
314
+ // (env 建立失败,任务带明确错误)。链尾最外层:worktree 隔离等内层先定 cwd,附件落在最终 cwd。
315
+ if (taskAttachmentStore && executionEnvFactory) {
316
+ const inner = executionEnvFactory;
317
+ const attStore = taskAttachmentStore;
318
+ executionEnvFactory = async (ctx) => {
319
+ const env = await inner(ctx);
320
+ const n = await materializeAttachmentsInto(env, attStore, ctx.sessionId);
321
+ if (n > 0)
322
+ logger.info("attachments_materialized", { sessionId: ctx.sessionId, count: n });
323
+ return env;
324
+ };
325
+ }
326
+ if (executionEnvFactory) {
327
+ // k8s (Kata) is isolated; it becomes WORKSPACE-suspendable when an S3 snapshot store is configured
328
+ // (tar→S3→fresh pod restore — files durable, in-VM memory not); e2b is fully suspendable.
329
+ const isolated = config.remoteExec.provider === "e2b" ||
330
+ config.remoteExec.provider === "k8s" ||
331
+ config.remoteExec.provider === "local-docker"; // a container is a real OS-level isolation boundary
332
+ const suspendable = config.remoteExec.provider === "e2b" ||
333
+ (config.remoteExec.provider === "k8s" && !!config.remoteExec.s3Snapshot);
334
+ // Out-of-band sandbox env (e2b): log the KEY NAMES only (never the secret values) so ops can confirm
335
+ // injection is active without leaking the credential.
336
+ const sandboxEnvKeys = config.remoteExec.provider === "e2b" ? Object.keys(config.remoteExec.sandboxEnv ?? {}) : [];
337
+ logger.info("remote_exec_enabled", { provider: config.remoteExec.provider, isolated, suspendable, ...(sandboxEnvKeys.length > 0 ? { sandboxEnvKeys } : {}) });
338
+ if (!isolated)
339
+ logger.warn("remote_exec_real_system", { provider: config.remoteExec.provider, note: "non-isolated target — actions are permanent; rely on the policy gate + HITL (design/61 §5)" });
340
+ }
341
+ // LSP sidecar (design/64 §13.1, the 1.86.2 seam): ONE stateless manager on RunnerDeps — core passes each
342
+ // task's env into sessionFor at the tool mount point, so the manager reaches the SAME sandbox the agent edits
343
+ // (no sessionId registry / per-task construction). Opt-in (LSP_ENABLED — the SANDBOX lane's knob; the host lane
344
+ // below has its own, LSP_HOST_ENABLED, because its default is the opposite); with the baked `sema-code-lsp`
345
+ // template the first call is fast, without it the language server installs on first use (slow once per sandbox).
346
+ // k8s lane (gate#2 alignment): same manager/bridge over the pod network — `ws://podIP:port` instead of E2B's
347
+ // public wss proxy, so it needs an IN-CLUSTER worker and a sandbox image with node + the language servers baked
348
+ // (no on-the-fly npm path on Kata; a miss degrades the lsp tool gracefully, same as E2B).
349
+ const lspProvider = config.remoteExec?.provider;
350
+ const lspManager = (lspProvider === "e2b" || lspProvider === "k8s") && config.lspEnabled
351
+ ? createE2bLspManager({ log: (event, fields) => logger.info(event, fields), scheme: lspProvider === "k8s" ? "ws" : "wss" })
352
+ : // TOC local LSP (core 1.190): the host lane runs on THIS machine, so core's `NodeLspManager` spawns the
353
+ // language server as a LOCAL child_process over stdio (CC `services/lsp` parity). Its default resolveRoot uses
354
+ // `env.cwd` — and core passes each task's executionEnv (the per-agent WORKTREE env, so the server roots in the
355
+ // worktree not the base repo) into sessionFor. host ONLY: ssh/adb target another host/device and local-docker a
356
+ // container, none of which a local child_process can reach (those keep the E2B-style bridge / stay degraded).
357
+ // 🔒 `requirePrincipal !== true` gate = uniform with the sibling host powers (scheduler/backgroundShell/
358
+ // loadProjectMemory): the host lane is single-user by design; a (discouraged) multi-tenant host fails SAFE to
359
+ // grep/read rather than fanning a per-tenant×language×root pool of heavy language servers (double-review B).
360
+ lspProvider === "host" && config.lspHostEnabled && config.requirePrincipal !== true
361
+ ? new NodeLspManager({ log: (event, fields) => logger.info(event, fields) })
362
+ : undefined;
363
+ if (lspManager)
364
+ logger.info("lsp_enabled", { provider: lspProvider });
365
+ return {
366
+ perTaskImage, sessionEnvSelection, perSessionCwd, setSessionCwd, setSessionShellEnv,
367
+ executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager,
368
+ };
369
+ }
370
+ //# sourceMappingURL=execution-env.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * design/158 A10:composition root 分段 —— v2 leader endpoint(design/50 + design/68)。
3
+ *
4
+ * 纯搬运:函数体逐字来自 `main.ts`(原 3094-3171 行),缩进不变;新增的只有 import 与包壳。
5
+ *
6
+ * ⚠️ **位置即契约**:必须在 `executionEnvFactory` / `checkpointStore` / `sessionStore` 都装配完之后调用
7
+ * (工厂模式的 k8s/host 腿以 `executionEnvFactory` 在场为门;durable 子 worker 挂 `checkpointStore`)。
8
+ */
9
+ import type { ServiceConfig } from "../config.js";
10
+ import type { createBrain } from "../brain.js";
11
+ import type { buildPricing } from "../budget.js";
12
+ import { createLeaderEndpoint } from "../leader/endpoint.js";
13
+ import type { Logger } from "../observability/logger.js";
14
+ import type { CheckpointStoreFull, StoreBackend, ToolResultStoreFull } from "../plugins/store-backend.js";
15
+ import type { RunnerDeps } from "@sema-agent/core";
16
+ export interface LeaderCtx {
17
+ config: ServiceConfig;
18
+ logger: Logger;
19
+ brain: ReturnType<typeof createBrain>;
20
+ pricing: ReturnType<typeof buildPricing>;
21
+ executionEnvFactory: RunnerDeps["executionEnvFactory"];
22
+ toolResultStore: ToolResultStoreFull | undefined;
23
+ sessionStore: ReturnType<StoreBackend["session"]>;
24
+ checkpointStore: CheckpointStoreFull | undefined;
25
+ }
26
+ export declare function createLeaderFace(ctx: LeaderCtx): ReturnType<typeof createLeaderEndpoint> | undefined;
27
+ //# sourceMappingURL=leader.d.ts.map