@sema-agent/server 7.69.0 → 7.71.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 (107) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +4 -1
  3. package/USAGE.md +109 -10
  4. package/dist/approval-ask-machine.d.ts +1 -1
  5. package/dist/approval-card.d.ts +3 -2
  6. package/dist/approval-content-kind.d.ts +13 -7
  7. package/dist/approval-content-kind.js +3 -1
  8. package/dist/approval-reconciler.d.ts +0 -54
  9. package/dist/approval-reconciler.js +2 -2
  10. package/dist/approval.js +3 -2
  11. package/dist/bench/s1/live-deps.js +2 -0
  12. package/dist/boot/execution-env.js +12 -2
  13. package/dist/boot/memory-consolidation.d.ts +2 -1
  14. package/dist/boot/parked-revive-gate.d.ts +2 -0
  15. package/dist/boot/parked-revive-gate.js +15 -10
  16. package/dist/boot/resolve-spec.js +8 -3
  17. package/dist/boot/runner-deps.d.ts +50 -46
  18. package/dist/boot/runner-deps.js +12 -10
  19. package/dist/brain.d.ts +3 -1
  20. package/dist/brain.js +2 -1
  21. package/dist/budget.js +3 -2
  22. package/dist/config-catalog.d.ts +31 -2
  23. package/dist/config-catalog.js +44 -24
  24. package/dist/config-types.d.ts +31 -1
  25. package/dist/config-types.js +1 -0
  26. package/dist/config.js +15 -3
  27. package/dist/deployment-governance.d.ts +6 -3
  28. package/dist/deployment-governance.js +2 -1
  29. package/dist/execution-lane-caps.d.ts +51 -37
  30. package/dist/execution-lane-caps.js +23 -0
  31. package/dist/fleet/fleet-bus.d.ts +22 -1
  32. package/dist/fleet/fleet-bus.js +2 -1
  33. package/dist/http/routes/approvals-assistant.js +3 -12
  34. package/dist/http/routes/diagnostics.js +4 -0
  35. package/dist/http/routes/runs.js +2 -3
  36. package/dist/http/routes/tasks.js +22 -18
  37. package/dist/http/server.js +21 -7
  38. package/dist/index.d.ts +1 -1
  39. package/dist/leader/wire.js +4 -3
  40. package/dist/main.js +6 -4
  41. package/dist/memory-operator-faces.d.ts +1 -1
  42. package/dist/memory-scope.d.ts +24 -0
  43. package/dist/memory-scope.js +12 -3
  44. package/dist/observability/metrics.d.ts +4 -6
  45. package/dist/observability/metrics.js +5 -1
  46. package/dist/observability/permission-rule-events.d.ts +20 -0
  47. package/dist/observability/permission-rule-events.js +28 -0
  48. package/dist/observability/run-terminal-log.d.ts +13 -22
  49. package/dist/observability/run-terminal-log.js +187 -47
  50. package/dist/observability/tool-trace.d.ts +12 -0
  51. package/dist/observability/tool-trace.js +5 -1
  52. package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
  53. package/dist/orchestration/workflow-agent-session-index.js +38 -5
  54. package/dist/parked-decide.d.ts +40 -1
  55. package/dist/parked-decide.js +19 -2
  56. package/dist/plugins/checkpoint-store-sql.js +5 -5
  57. package/dist/plugins/local-checkpoint-store.js +3 -3
  58. package/dist/plugins/remote-env-adb.d.ts +11 -0
  59. package/dist/plugins/remote-env-adb.js +2 -0
  60. package/dist/plugins/remote-env-device.d.ts +9 -0
  61. package/dist/plugins/remote-env-device.js +3 -0
  62. package/dist/plugins/remote-env-e2b.d.ts +11 -0
  63. package/dist/plugins/remote-env-e2b.js +2 -0
  64. package/dist/plugins/remote-env-host.d.ts +11 -0
  65. package/dist/plugins/remote-env-host.js +2 -0
  66. package/dist/plugins/remote-env-k8s.d.ts +11 -0
  67. package/dist/plugins/remote-env-k8s.js +2 -0
  68. package/dist/plugins/remote-env-local-docker.d.ts +11 -0
  69. package/dist/plugins/remote-env-local-docker.js +2 -0
  70. package/dist/plugins/remote-env-ssh.d.ts +11 -0
  71. package/dist/plugins/remote-env-ssh.js +2 -0
  72. package/dist/plugins/store-backend.d.ts +36 -13
  73. package/dist/plugins/store-backend.js +12 -3
  74. package/dist/plugins/store-contracts.d.ts +54 -18
  75. package/dist/plugins/store-contracts.js +17 -30
  76. package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
  77. package/dist/plugins/workflow-journal-store-sql.js +3 -10
  78. package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
  79. package/dist/plugins/workflow-run-store-sql.js +28 -1
  80. package/dist/posture-source.d.ts +23 -3
  81. package/dist/posture-source.js +1 -1
  82. package/dist/read-face-posture.d.ts +8 -1
  83. package/dist/read-face-posture.js +1 -0
  84. package/dist/run-cancel-context.d.ts +50 -1
  85. package/dist/run-cancel-context.js +22 -0
  86. package/dist/run-local.d.ts +14 -6
  87. package/dist/run-local.js +4 -1
  88. package/dist/runs.d.ts +0 -51
  89. package/dist/runs.js +40 -25
  90. package/dist/task-a2a.js +2 -2
  91. package/dist/task-mcp.js +2 -2
  92. package/dist/task-settings.d.ts +21 -8
  93. package/dist/task-settings.js +2 -2
  94. package/dist/tool-approval.d.ts +14 -8
  95. package/dist/tool-approval.js +3 -3
  96. package/dist/trace/core-keyset-guard.d.ts +135 -11
  97. package/dist/trace/engine-notice-wire.d.ts +1 -1
  98. package/dist/trace/engine-notice-wire.js +1 -0
  99. package/dist/trace/ledger-events.d.ts +22 -2
  100. package/dist/trace/ledger-events.js +6 -1
  101. package/dist/trace/ledger-sink.d.ts +10 -8
  102. package/dist/trace/ledger-sink.js +10 -2
  103. package/dist/trace/project.d.ts +24 -6
  104. package/dist/trace/project.js +5 -3
  105. package/dist/trace/redact.d.ts +28 -0
  106. package/dist/trace/redact.js +58 -30
  107. package/package.json +3 -3
@@ -55,6 +55,24 @@ export interface RetentionSweepConfig {
55
55
  * 那道拒之后它即刻生效 —— 留着是为了「那天不必有人再猜一个数」。
56
56
  */
57
57
  export declare const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
58
+ /**
59
+ * S-202 —— **首 token 看门狗的缺省上限(ms)**,全仓单一属主。
60
+ *
61
+ * 「SSE 已开但迟迟不吐第一个 delta」是唯一一条 core 的 idle 计时器够不着的挂死路(它只在首 token
62
+ * 之后才武装),所以这条看门狗是那条路上唯一的守卫。600s 而不是原来的 120s:自托管后端在长 prefill
63
+ * (大上下文 / 冷缓存 / 排队)下,首字合理地就是要等几分钟——120s 会把一次**正常**的慢启动判成
64
+ * `[network]` 失败去重试,而重试再交同一份 prompt 给同一个排队中的后端,只会更慢更贵。
65
+ *
66
+ * ⚠️ **`0 = 关`语义不变**,提高缺省不碰它;依赖 120s 快失败的部署把
67
+ * `MODEL_FIRST_TOKEN_TIMEOUT_MS=120000` 显式写回即可(env 恒赢)。
68
+ *
69
+ * 🔴 **常量在此、别处只许引用**:解析器腿(`config.ts` 的 `numEnv` 缺省)、目录腿
70
+ * (`config-catalog.ts` 的 `staticDefault`)与 `brain.ts` 的 `DEFAULT_RESILIENCE`(config 对象没带
71
+ * resilience 块时的同姿态兜底)此前各写一份 `120000` 字面量,改一处漏一处 = 对外宣告的缺省与真跑的
72
+ * 不是同一代。`test/config-catalog.test.ts` 的 S-202 两格咬:一格钉三腿同值,一格扫全 `src/` 判
73
+ * 「字面量」这一类写法为红(下一份镜像出生即红)。
74
+ */
75
+ export declare const MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT = 600000;
58
76
  /**
59
77
  * [ref] D1/D2 —— consolidation driver 阀门的三根旋钮(语义与三问见
60
78
  * {@link ServiceConfigFlat.memoryConsolidationDriver})。纯数据。
@@ -608,6 +626,18 @@ export interface ServiceConfigFlat {
608
626
  * snapshot). Self-hosted sandbox lane; auth = explicit K8S_* env or in-cluster service account.
609
627
  * - `ssh` → a real host over SSH (NOT isolated/suspendable; [ref] §5). AI-orchestrated batch deploy.
610
628
  * - `adb` → a real device over ADB (NOT isolated/suspendable). On-device APP testing. */
629
+ /**
630
+ * S-213④ —— `REMOTE_EXEC_HOME_DIR`:**执行环境的 home 目录**(core `ExecutionEnv.homeDir`),
631
+ * `~/…` 形权限规则(`Edit(~/.ssh/**)`)唯一的基,绝对路径。
632
+ *
633
+ * 部署级旋钮,**跨车道同一根**(不按 provider 分七根 —— 它回答的是同一个问题:「这条部署的手,
634
+ * 跑在哪个用户的 home 下」)。显式声明**恒赢**;缺席时只有 home 属于车道本身的那几条车道有答案
635
+ * (host/in-process = `os.homedir()`、e2b = 平台固定用户),k8s/local-docker(镜像属性)、
636
+ * ssh(目标机登录用户)、adb/device 一律缺席 ⇒ `~/` 规则在下单时被 422 响亮拒。
637
+ * 派生与逐车道理由的**单一属主** = `execution-lane-caps.ts` 的 `executionLaneHomeDir`。
638
+ * 非绝对路径 ⇒ **拒启**([ref] A 档;core 侧同判为 `config.execution_env_home_dir_invalid`)。
639
+ */
640
+ remoteExecHomeDir?: string;
611
641
  remoteExec?: {
612
642
  provider: "e2b";
613
643
  apiKey: string;
@@ -1721,7 +1751,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
1721
1751
  * config 这一层它只是一句声明。 */
1722
1752
  export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
1723
1753
  /** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
1724
- export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
1754
+ export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "remoteExecHomeDir" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
1725
1755
  /** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
1726
1756
  export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
1727
1757
  /** 组:observability(可观测)。 */
@@ -1,3 +1,4 @@
1
1
  export const RETENTION_MODES = ["audit-only", "enforce"];
2
2
  export const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
3
+ export const MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT = 600_000;
3
4
  //# sourceMappingURL=config-types.js.map
package/dist/config.js CHANGED
@@ -9,7 +9,7 @@ import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
9
9
  import { isV2ScopeKey } from "./memory-scope.js";
10
10
  import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
11
11
  import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
12
- import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
12
+ import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT } from "./config-types.js";
13
13
  import { buildWriteProtectionPosture } from "./write-protection.js";
14
14
  import { normalizeSshFingerprint } from "./ssh-host-key.js";
15
15
  import { readFaceForPosture } from "./read-face-posture.js";
@@ -856,7 +856,7 @@ function parseModelDomain() {
856
856
  : undefined,
857
857
  resilience: {
858
858
  connectTimeoutMs: numEnv("MODEL_CONNECT_TIMEOUT_MS", "30000"),
859
- firstTokenTimeoutMs: numEnv("MODEL_FIRST_TOKEN_TIMEOUT_MS", "120000"),
859
+ firstTokenTimeoutMs: numEnv("MODEL_FIRST_TOKEN_TIMEOUT_MS", String(MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT)),
860
860
  idleTimeoutMs: numEnv("MODEL_IDLE_TIMEOUT_MS", "300000"),
861
861
  circuitBreaker: boolEnv("MODEL_CIRCUIT_BREAKER", false),
862
862
  failureThreshold: numEnv("MODEL_CB_FAILURE_THRESHOLD", "5"),
@@ -1551,6 +1551,18 @@ function parseOrchestrationDomain(ctx) {
1551
1551
  assertReadDenyBuiltinsResolvable(tiers, exclude);
1552
1552
  return { ...(tiers !== undefined ? { readDenyBuiltinTiers: tiers } : {}), ...(exclude !== undefined ? { readDenyBuiltinExclude: exclude } : {}) };
1553
1553
  })(),
1554
+ ...((h) => (h !== undefined ? { remoteExecHomeDir: h } : {}))((() => {
1555
+ const raw = process.env.REMOTE_EXEC_HOME_DIR;
1556
+ if (raw === undefined || raw === "")
1557
+ return undefined;
1558
+ if (!raw.startsWith("/")) {
1559
+ throw new Error(`REMOTE_EXEC_HOME_DIR="${raw}" is not an absolute path — it is the home directory of the user the EXECUTION ` +
1560
+ `environment runs as (the base a \`~/\` permission rule resolves against), so a relative value cannot be ` +
1561
+ `resolved and would silently guard nothing. Spell it absolutely (e.g. /home/user), or unset it to have ` +
1562
+ `\`~/\` rules refused loudly on this lane.`);
1563
+ }
1564
+ return raw;
1565
+ })()),
1554
1566
  remoteExec: process.env.REMOTE_EXEC === "e2b" && process.env.E2B_API_KEY
1555
1567
  ? {
1556
1568
  provider: "e2b",
@@ -1941,7 +1953,7 @@ const AUTH_GROUP_KEYS = [
1941
1953
  "principalJwtMaxTtlSec", "bindHost", "bindHostSource",
1942
1954
  ];
1943
1955
  const ORCHESTRATION_GROUP_KEYS = [
1944
- "remoteExec", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
1956
+ "remoteExec", "remoteExecHomeDir", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
1945
1957
  "selfOrchestrationModels", "selfOrchestrationWorkerIsolation", "forkEnabled", "experimentalObserverAgents",
1946
1958
  "schedulerEnabled", "schedulerSessionWakeup", "schedulerSessionLifetime", "schedulerStorePath", "planModeEnabled", "hooksTimeoutMs", "workflowRunStoreBackend",
1947
1959
  "workflowOrphanGraceMs", "workflowJournalRetentionMs", "workflowRunRetentionMs", "usageWindows", "workflowAgentsReadOnly",
@@ -59,8 +59,10 @@ export interface PathAdjudication {
59
59
  * 形上是一个永不创建的哨兵目录 —— 对写门 fail-safe,对规则 fail-open。理由逐字见
60
60
  * `task-settings.ts` 的 `FsWriteGateWiring.taskRoot`。缺席 ⇒ 需要基的规则拼法被 core 整表拒(fail-loud)。 */
61
61
  readonly taskRoot?: string;
62
- /** 🔴 S-177 / codex r2:**执行环境的 home**(`~/…` 形规则的基)。只在引擎与执行同机时在场 ——
63
- * 远端车道上 server 的 home 是另一个用户的目录,回落它 = 规则静默失效。逐字见
62
+ /** 🔴 S-177 / codex r2 + S-213④:**执行环境的 home**(`~/…` 形规则的基)。在**本部署的执行车道
63
+ * 真知道自己的 home 时**在场 —— 判据的唯一属主是 `execution-lane-caps.ts` 的 `executionLaneHomeDir`
64
+ * (adapter 的 `ExecutionEnv.homeDir` 座读的是同一个函数)。绝不回落引擎进程自己的 home:远端车道上
65
+ * 那是另一个用户的目录,拿它去匹配 = 一条收紧规则静默守错地方。逐字见
64
66
  * `task-settings.ts` 的 `FsWriteGateWiring.taskHome`。 */
65
67
  readonly taskHome?: string;
66
68
  }
@@ -85,7 +87,8 @@ export interface DurableApprovalSeat {
85
87
  * durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留([ref] 后无活流腿放行执行
86
88
  * 也不会产出空答:coordinator 无 ALS ctx ⇒ 冻结 `{kind:"unavailable"}`(src/question.ts),永不悬挂;
87
89
  * park 仍是把问题送到人面前的唯一那条腿,正当性不变、只是反事实前提换了)。
88
- * 工具名字面量与 server.ts 的 pre-CAS 守卫同源("AskUserQuestion",core 未根导出常量)。
90
+ * 工具名取 `approval-content-kind.ts` 的单一属主(它 re-export core 根导出的 `ASK_USER_QUESTION_TOOL_NAME`);
91
+ * server.ts 的 pre-CAS 守卫读同一枚(S-213①:此处旧注称该常量「core 未根导出」= 失实,曾据此手抄 13 处)。
89
92
  * ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
90
93
  * 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
91
94
  * 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
@@ -1,12 +1,13 @@
1
1
  import { join, posix } from "node:path";
2
2
  import { FileError, StubExecutionEnv, combinePolicies, createAllowDenyPolicy, createDurableQuestionPolicy, createSensitivePathPolicy, createTranscriptIntegrityPolicy, err, ok, } from "@sema-agent/core";
3
3
  import { createDurableAskPolicy } from "./approval.js";
4
+ import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
4
5
  export function createDurableQuestionGate(live) {
5
6
  if (live === undefined)
6
7
  return createDurableQuestionPolicy();
7
8
  return {
8
9
  check(req) {
9
- if (req.toolName !== "AskUserQuestion")
10
+ if (req.toolName !== ASK_USER_QUESTION_TOOL_NAME)
10
11
  return { action: "allow" };
11
12
  return live.hasLiveContext()
12
13
  ? { action: "allow" }
@@ -1,40 +1,3 @@
1
- /**
2
- * 执行车道**闭集**与 per-lane 能力表(design device-executor-lane-v2 §4.1 收敛件,车A-1)。
3
- *
4
- * ## 根本问题:lane 词表是开放字符串上的十几处独立判别式
5
- *
6
- * `config.remoteExec?.provider` 的静态型是 `string | undefined` 的判别联合,但消费面从来不是一处
7
- * switch —— 而是散在十几个文件里的**独立等式/集合/否定**:`cwdHonored` 比 `=== "host"`、
8
- * `isRemoteScratchpadLane` 查一个 `Set`、`isSandboxPathAdjudicationLane` 取 `!== "host"` 的否定、
9
- * `sandboxSendLaneEnabled` 是一串 if。于是「lane 词表加一个词」的病形不是编译错,是**每一处的默认
10
- * 答案静默错**:新词不在 Set 里 ⇒ 静默 false;不等于 `"host"` ⇒ 静默 true。没有一处会报,每一处都
11
- * 给出一个**没有人表过态**的答案(DISEASE-SHAPES V1/V2;[ref] 执行车道轴的同族病)。
12
- *
13
- * ## 本模块 = 那张表的单源(三件)
14
- *
15
- * ① {@link ExecutionLane}:闭集联合。含 `in-process` —— `REMOTE_EXEC` 未设的进程内 stub 形**是**一条
16
- * 真车道(`boot/resolve-spec.ts` 的 `?? "in-process"`、`config.ts` 的落点记账都已在用这个词),
17
- * 把它排除在闭集外,普查就少一列。
18
- * ② {@link executionLaneCaps}:**穷举 switch**(先例 = `capabilities/hands-lane.ts` 的
19
- * `pickHandsRunner`:无 `default` 臂 ⇒ 联合加词而此处不加臂 = 编译错,而不是运行期落一个默认行)。
20
- * ③ {@link ExecutionLaneCaps} 的每一位都点名它**今天的判别式坐标**,由对账测试
21
- * `test/execution-lane-caps.test.ts` 逐位跑真判别式与本表对拍。
22
- *
23
- * ## 🔴 本模块是表,不是执法者
24
- *
25
- * 消费点今天仍各自判别(把判别式改成读本表 = 分批的后续车)。所以本表的全部价值押在对账测试上:
26
- * 表与判别式一旦分叉,门红。**唯一**允许的分叉是 {@link PLANNED_LANE_DIVERGENCES} 里逐条登记的
27
- * 「表是目标态、判别式待某车补」;登记项之外的分叉是漂移,登记项被补齐而不销行同样是红(双向)。
28
- *
29
- * ## 能力位的口径
30
- *
31
- * 每一位回答的是**车道结构上能不能**,不是「这个部署开没开」:旋钮(`LSP_ENABLED` /
32
- * `SCHEDULER_ENABLED` / k8s 的 `s3Snapshot`)由消费点在表之上再合取。租户面是唯一的例外 —— 它与
33
- * 车道语义绑死(host 的单用户前提),所以进了 {@link LaneCapability} 的第三个词而不是靠调用方记住。
34
- *
35
- * 依赖纪律:本文件是**叶**(只 type-import config-types),不引 config.ts —— config.ts 反过来引它取
36
- * {@link RemoteExecProvider}([ref] 必需 env 表按同一联合键控)。
37
- */
38
1
  import type { ServiceConfigFlat } from "./config-types.js";
39
2
  /** 一位能力的三态。`single-user-only` = 车道支持,但只在单用户部署(`requirePrincipal !== true`)开 ——
40
3
  * 多租户下这一位必须关(host 家族的 cwd/scheduler/backgroundShell/LSP 都是这一档,理由同源:
@@ -149,4 +112,55 @@ export interface PlannedLaneDivergence {
149
112
  * 加行的门槛不变:一条分叉必须写清 today / owner / why 三件,并在补齐的**同一批**里销掉。
150
113
  */
151
114
  export declare const PLANNED_LANE_DIVERGENCES: readonly PlannedLaneDivergence[];
115
+ /**
116
+ * E2B 沙箱里 agent 跑成的那个用户的 home。
117
+ *
118
+ * 这是 **E2B 平台的事实**,不是本仓的猜:E2B 的基础模板建 `user` 这个用户,每个 sandbox 的进程都以它
119
+ * 运行,home 恒 `/home/user`;本仓 adapter 自己的工作区根默认值(`plugins/remote-env-e2b.ts` 的
120
+ * `DEFAULT_MOUNT_PATH`)用的就是同一个目录。**若某个部署的自定义模板换了用户**,运维用
121
+ * `REMOTE_EXEC_HOME_DIR` 显式声明覆盖 —— 那条旋钮恒赢,见 {@link executionLaneHomeDir}。
122
+ * ⚠️ 本值**未经真机实测**(本批无 e2b 真机),按平台约定 + 仓内既有默认常量登记。
123
+ */
124
+ export declare const E2B_SANDBOX_HOME = "/home/user";
125
+ /** {@link executionLaneHomeDir} 读的那两位部署配置(`ServiceConfig` 结构上满足)。 */
126
+ export interface LaneHomeDeployment {
127
+ readonly remoteExec?: {
128
+ readonly provider: RemoteExecProvider;
129
+ } | undefined;
130
+ /** `REMOTE_EXEC_HOME_DIR` —— 运维显式声明的执行环境 home(绝对路径;坏值在 config 层拒启)。 */
131
+ readonly remoteExecHomeDir?: string | undefined;
132
+ }
133
+ /**
134
+ * 「**这条部署的执行环境**以哪个 home 运行」—— `~/…` 形权限规则(`Edit(~/.ssh/**)`)唯一的基。
135
+ *
136
+ * ## 病(接入审计 M13 / DEBTS S-213④)
137
+ * core 的 `ExecutionEnv.homeDir` 是**由 adapter 声明**的座(`engine/harness/types.d.ts:511`,可选):
138
+ * 缺席 ⇒ core 的 `resolvePathPattern` 回 `{missingBase:"home"}` ⇒ 该规则对这条腿判 `unreadable`
139
+ * (fail-closed 的 ask)。四只远端 adapter(e2b/k8s/host/ssh)一个都没声明,而 server 侧的 422 前置门
140
+ * (`task-settings.ts` 的 `rejectUnmatchableSettingsNames`)在 home 未知时整表拒 `~/` 形 ——
141
+ * **于是远端部署根本写不出 `~/` 规则**,恒 422。
142
+ *
143
+ * ## 本函数 = 那个值的**单一属主**
144
+ * 两个消费面读同一份,不许各算各的(两处不同 = 一条规则在 422 门上被拒、却在引擎里编得出来,或反之):
145
+ * ① 每只 adapter 的 `homeDir` 座(`boot/execution-env.ts` 把本函数的答案递进 adapter 配置);
146
+ * ② 规则基 `PathRuleBases.home`(`boot/resolve-spec.ts` / `boot/parked-revive-gate.ts` 的
147
+ * `pathAdjudication.taskHome`)。
148
+ *
149
+ * ## 一条规则,不是七条特判
150
+ * **运维显式声明恒赢**(`REMOTE_EXEC_HOME_DIR`,部署级旋钮,不挂任何客户端表态);没有声明时,
151
+ * 只有「home 是**车道本身**的属性」的那几条车道才有答案:
152
+ * · `host` / `in-process` —— 命令就跑在本进程这台机器、这个用户下,`os.homedir()` 读的就是它
153
+ * (core 自己的 `NodeExecutionEnv` 同判);
154
+ * · `e2b` —— 平台固定用户,见 {@link E2B_SANDBOX_HOME};
155
+ * · `k8s` / `local-docker` —— home 是**镜像**的属性,boot 期无从得知;
156
+ * · `ssh` —— home 是**目标机上那个登录用户**的属性(root 是 `/root`、macOS 是 `/Users/<u>`),
157
+ * 连上之前无从得知,而 422 门要在下单那一刻回答;
158
+ * · `adb` —— Android 没有 POSIX 意义上的登录 home;
159
+ * · `device` —— 员工自己的机器,运行期才连进来。
160
+ * 这五条一律**缺席**(诚实:不猜一个目录去守)。缺席的后果是 `~/` 规则在下单时被 422 响亮拒,
161
+ * 与 core 的 `missingBase:"home"` 同向 —— 这正是负控要的那一格。
162
+ *
163
+ * 认不出的车道 ⇒ `undefined`(调用方 fail-closed)。生产链上不可达:`loadConfig` 的 [ref] 拒启门先咬。
164
+ */
165
+ export declare function executionLaneHomeDir(deployment: LaneHomeDeployment): string | undefined;
152
166
  //# sourceMappingURL=execution-lane-caps.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { homedir } from "node:os";
1
2
  const NO = "unsupported";
2
3
  const YES = "supported";
3
4
  const SU = "single-user-only";
@@ -163,4 +164,26 @@ export function laneCapabilityHolds(cap, deployment) {
163
164
  }
164
165
  }
165
166
  export const PLANNED_LANE_DIVERGENCES = Object.freeze([]);
167
+ export const E2B_SANDBOX_HOME = "/home/user";
168
+ export function executionLaneHomeDir(deployment) {
169
+ const declared = deployment.remoteExecHomeDir;
170
+ if (declared !== undefined)
171
+ return declared;
172
+ const lane = executionLaneOf(deployment.remoteExec?.provider);
173
+ if (lane === undefined)
174
+ return undefined;
175
+ switch (lane) {
176
+ case "in-process":
177
+ case "host":
178
+ return homedir();
179
+ case "e2b":
180
+ return E2B_SANDBOX_HOME;
181
+ case "k8s":
182
+ case "local-docker":
183
+ case "ssh":
184
+ case "adb":
185
+ case "device":
186
+ return undefined;
187
+ }
188
+ }
166
189
  //# sourceMappingURL=execution-lane-caps.js.map
@@ -1,5 +1,6 @@
1
1
  import type { TaskNotificationPayload, TerminalCause, WorkflowRun } from "@sema-agent/core";
2
- import type { RunRecord } from "../plugins/store-contracts.js";
2
+ import type { RunRecord, UnsuccessfulRunStatus } from "../plugins/store-contracts.js";
3
+ import type { AssertAllKeysHandled } from "../trace/core-keyset-guard.js";
3
4
  /** [ref] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
4
5
  * `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
5
6
  * 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
@@ -618,4 +619,24 @@ export declare function isTerminalFleetStatus(status: FleetTaskStatus): boolean;
618
619
  export declare function runStatusToFleet(status: RunStatusForFleet, opts?: {
619
620
  awaitingPlanApproval?: boolean;
620
621
  }): FleetTaskStatus;
622
+ /**
623
+ * S-213③ —— **编译期同源钉**:fleet 的 `"failed"` 桶必须恰好收下 `RUN_STATUS_CLASS` 判为「非成功终局」
624
+ * 的每一个 core 词。
625
+ *
626
+ * 这个词的病史:`blocked` 曾在本仓有**两个独立写者**且答案互斥 —— 本文件读它为「非成功终局(agent
627
+ * 放弃)⇒ 行离场」,而 `observability/run-terminal-log.ts` 读它为「治理判决,不是失败 ⇒ 不打行」。两处
628
+ * 各自穷举、各自正确地编译,结果是一条 `report_blocked` 的 run 在面板上离场、在引擎日志里一个字都没有。
629
+ * 语义收归 `plugins/store-contracts.ts` 的那张表之后,本行不再自持判断:表把某个词挪出/挪进
630
+ * `unsuccessful`(或 core 新增一个非成功终态)而上面的 switch 没跟上 ⇒ 下面这条收不敛到 `never` ⇒ tsc 红。
631
+ * (`"error"` 是 server 自有别名、不在那张表的辖域内,故不在本钉的两边出现。)
632
+ */
633
+ type FleetUnsuccessfulBucket = "failed" | "blocked";
634
+ type _GuardFleetUnsuccessfulBucket = AssertAllKeysHandled<Exclude<UnsuccessfulRunStatus, FleetUnsuccessfulBucket>>;
635
+ /** 反向门(与 `core-keyset-guard.ts` 的 `_Guard*Reverse` 同形):桶里留了一个表**不再**判为非成功终局的
636
+ * 词同样红 —— 只有正向门时,把 `blocked` 挪回 `succeeded` 而 switch 照旧塞进 failed 桶,并集覆盖、
637
+ * 差集门一声不吭(本批实测:只加正向门时该变异 tsc rc=0)。两道钉一起加。 */
638
+ type _GuardFleetUnsuccessfulBucketReverse = AssertAllKeysHandled<Exclude<FleetUnsuccessfulBucket, UnsuccessfulRunStatus>>;
639
+ /** 占位使用(防 noUnusedLocals 形 lint;类型别名本身零运行时)。 */
640
+ export type FleetVocabGuards = [_GuardFleetUnsuccessfulBucket, _GuardFleetUnsuccessfulBucketReverse];
641
+ export {};
621
642
  //# sourceMappingURL=fleet-bus.d.ts.map
@@ -634,9 +634,10 @@ export function runStatusToFleet(status, opts) {
634
634
  case "completed":
635
635
  return "completed";
636
636
  case "failed":
637
- case "error":
638
637
  case "blocked":
639
638
  return "failed";
639
+ case "error":
640
+ return "failed";
640
641
  case "suspended":
641
642
  return "waiting";
642
643
  case "needs_review":
@@ -8,6 +8,7 @@ import { principalFrom, decodeCheckpointScope, PRINCIPAL_TOKEN_HEADER, APPROVAL_
8
8
  import { verifyDirectDoorProof } from "../../principal-jwt.js";
9
9
  import { MAX_APPROVAL_REASON_CHARS } from "../../approval-hmac.js";
10
10
  import { redactedPreview, redactSecrets } from "../../trace/redact.js";
11
+ import { redactRiskDescriptor } from "../../observability/run-terminal-log.js";
11
12
  import { fleetRunLabels } from "../../fleet/fleet-bus.js";
12
13
  import { sleep } from "../sse-log.js";
13
14
  import { bindSseLifecycle } from "../sse-lifecycle.js";
@@ -546,18 +547,8 @@ function redactPendingDisclosures(row) {
546
547
  const rd = row.riskDescriptor;
547
548
  if (rd === null || rd === undefined)
548
549
  return row;
549
- const shadowedRule = typeof rd.shadowedRule === "string" ? redactSecrets(rd.shadowedRule) : undefined;
550
- const summary = typeof rd.summary === "string" ? redactSecrets(rd.summary) : undefined;
551
- if (shadowedRule === undefined && summary === undefined)
552
- return row;
553
- return {
554
- ...row,
555
- riskDescriptor: {
556
- ...rd,
557
- ...(shadowedRule !== undefined ? { shadowedRule } : {}),
558
- ...(summary !== undefined ? { summary } : {}),
559
- },
560
- };
550
+ const next = redactRiskDescriptor(rd);
551
+ return next === rd ? row : { ...row, riskDescriptor: next };
561
552
  }
562
553
  function projectPendingForWire(row, governance) {
563
554
  const redacted = redactPendingDisclosures(row);
@@ -10,21 +10,25 @@ export function buildServerWiringGates(input) {
10
10
  backend: input.backend,
11
11
  parkFacility: input.checkpointStore !== undefined,
12
12
  });
13
+ const noCenterArm = (knob) => `this knob has no config-center arm on this deployment — it is set by ${knob} on this machine, or derived from the deployment posture`;
13
14
  return {
14
15
  streamApproval: gate.active ? "active" : gate.reason,
15
16
  durableApproval: buildPostureKnobReading(input.durableApproval, {
16
17
  knob: "DURABLE_APPROVAL",
17
18
  source: input.durableApprovalSource,
19
+ centerMeaning: noCenterArm("DURABLE_APPROVAL"),
18
20
  postureMeaning: "derived from the single-user turnkey posture (REQUIRE_PRINCIPAL is not set) composed with a durable store being present: park/resume and the /v1/approvals routes are on by default — set DURABLE_APPROVAL=false to pin them off",
19
21
  }),
20
22
  streamAskWindowMs: buildPostureKnobReading(input.streamAskWindowMs, {
21
23
  knob: "STREAM_ASK_WINDOW_MS",
22
24
  source: input.streamAskWindowMsSource,
25
+ centerMeaning: noCenterArm("STREAM_ASK_WINDOW_MS"),
23
26
  postureMeaning: "derived from the single-user turnkey posture (REQUIRE_PRINCIPAL is not set): an approval card waits 24h instead of 5 minutes, so stepping away does not settle the run unattended — set STREAM_ASK_WINDOW_MS to pin your own window",
24
27
  }),
25
28
  sessionAutoTitle: buildPostureKnobReading(input.sessionAutoTitle, {
26
29
  knob: "SESSION_AUTO_TITLE",
27
30
  source: input.sessionAutoTitleSource,
31
+ centerMeaning: noCenterArm("SESSION_AUTO_TITLE"),
28
32
  postureMeaning: "this knob has no posture arm — its default is the same on every deployment shape",
29
33
  }),
30
34
  checkpointStore: input.checkpointStore !== undefined,
@@ -24,7 +24,7 @@ import { normalizeRunEventType } from "../../trace/project.js";
24
24
  import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
25
25
  import { buildActiveRunConflict } from "../active-run-conflict.js";
26
26
  import { clearTurnActivity, readTurnActivityMs } from "../../turn-activity.js";
27
- import { buildCancelContext } from "../../run-cancel-context.js";
27
+ import { mintCancelledResult } from "../../run-cancel-context.js";
28
28
  import { buildCrossSliceUsage, readResourceWindow, recordResourceWindow } from "../../resource-window.js";
29
29
  import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
30
30
  import { ActorAssertionWire } from "@sema-agent/settings-schema";
@@ -331,8 +331,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
331
331
  }
332
332
  const err = note;
333
333
  try {
334
- const cancelContext = buildCancelContext(taskId, Date.parse(run.createdAt));
335
- const result = { taskId, sessionId: run.sessionId, terminal: { kind: "failed", code: "cancelled", message: err }, stats: { turns: 0, tokens: 0 }, ...(cancelContext ? { cancelContext } : {}) };
334
+ const result = mintCancelledResult({ taskId, sessionId: run.sessionId, startedAtMs: Date.parse(run.createdAt), message: err });
336
335
  let lastErr;
337
336
  for (let attempt = 0;; attempt++) {
338
337
  try {
@@ -9,7 +9,7 @@ import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotific
9
9
  import { createLedgerSink } from "../../trace/ledger-sink.js";
10
10
  import { registerEngineNoticeLeg } from "../../trace/engine-notice-wire.js";
11
11
  import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../../turn-activity.js";
12
- import { buildCancelContext } from "../../run-cancel-context.js";
12
+ import { mintCancelledResult } from "../../run-cancel-context.js";
13
13
  import { recordResourceWindow } from "../../resource-window.js";
14
14
  import { redactSecrets } from "../../trace/redact.js";
15
15
  import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
@@ -190,17 +190,13 @@ async function handleTasksBody(req, res, url, ctx, miss) {
190
190
  let runRowSettled = false;
191
191
  let durableTaskId;
192
192
  let durableHeartbeat;
193
- const mintCancelledResult = (errorMessage) => {
194
- const ctx = durableTaskId === undefined ? undefined : buildCancelContext(durableTaskId, streamLegStartedAt);
195
- return {
196
- taskId: durableTaskId ?? "",
197
- sessionId: prepared.spec.sessionId ?? "",
198
- terminal: { kind: "failed", code: "cancelled", message: errorMessage },
199
- result: "",
200
- stats: { turns: 0, tokens: 0 },
201
- ...(ctx ? { cancelContext: ctx } : {}),
202
- };
203
- };
193
+ const cancelledResult = (errorMessage, engineResult) => mintCancelledResult({
194
+ taskId: durableTaskId ?? "",
195
+ sessionId: prepared.spec.sessionId ?? "",
196
+ startedAtMs: streamLegStartedAt,
197
+ message: errorMessage,
198
+ ...(engineResult !== undefined ? { engineResult } : {}),
199
+ });
204
200
  let userMsgEntryId;
205
201
  let anchorPut = false;
206
202
  const putRewindAnchor = () => {
@@ -398,12 +394,13 @@ async function handleTasksBody(req, res, url, ctx, miss) {
398
394
  break;
399
395
  if (ev.type === "message_committed" && ev.role === "user")
400
396
  userMsgEntryId = ev.entryId;
397
+ const cancelVerbAtDone = ev.type === "done" && durableTaskId !== undefined && cancelledViaVerb.has(durableTaskId);
401
398
  if (ev.type === "done") {
402
399
  finalResult = redactTerminalResult(stripResumeToken(await graftParkToolCallId(ev.result, deps.checkpointStore)));
403
400
  if (durableTaskId && finalResult)
404
401
  finalResult = { ...finalResult, taskId: durableTaskId };
405
- if (durableTaskId && cancelledViaVerb.has(durableTaskId) && finalResult?.terminal.kind === "failed" && !finalResult.terminal.code) {
406
- finalResult = { ...finalResult, terminal: { ...finalResult.terminal, code: "cancelled" } };
402
+ if (cancelVerbAtDone && finalResult !== undefined) {
403
+ finalResult = cancelledResult("cancelled by user", finalResult);
407
404
  }
408
405
  finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
409
406
  noteRunOutcome(finalResult);
@@ -644,7 +641,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
644
641
  }
645
642
  else {
646
643
  const dcErr = "stream client disconnected before completion (run aborted)";
647
- const dc = mintCancelledResult(dcErr);
644
+ const dc = cancelledResult(dcErr);
648
645
  if (ledgerSink && !ledgerTerminal) {
649
646
  ledgerTerminal = true;
650
647
  await ledgerSink.flush().catch(() => undefined);
@@ -696,7 +693,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
696
693
  }
697
694
  else if (ac.signal.aborted) {
698
695
  const err = "stream aborted before completion (run cancelled)";
699
- const c = mintCancelledResult(err);
696
+ const c = cancelledResult(err);
700
697
  if (ledgerSink && !ledgerTerminal) {
701
698
  ledgerTerminal = true;
702
699
  await ledgerSink.flush().catch(() => undefined);
@@ -745,6 +742,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
745
742
  else {
746
743
  const withDiscoveryNotice = (body) => prepared.verify || prepared.cascade ? { ...body, notice: MULTI_ATTEMPT_NO_EVENT_CHANNEL_NOTICE } : body;
747
744
  const runSyncSubmitLeg = async () => {
745
+ const syncLegStartedAt = Date.now();
748
746
  let durableTaskId;
749
747
  let durableHeartbeat;
750
748
  if (deps.runStore && prepared.spec.sessionId) {
@@ -808,8 +806,14 @@ async function handleTasksBody(req, res, url, ctx, miss) {
808
806
  cancelledViaVerb.delete(durableTaskId);
809
807
  }
810
808
  }
811
- if (cancelVerbLabel && result.terminal.kind === "failed" && !result.terminal.code) {
812
- result = { ...result, terminal: { ...result.terminal, code: "cancelled" } };
809
+ if (cancelVerbLabel) {
810
+ result = mintCancelledResult({
811
+ taskId: durableTaskId ?? "",
812
+ sessionId: prepared.spec.sessionId ?? "",
813
+ startedAtMs: syncLegStartedAt,
814
+ message: "cancelled by user",
815
+ engineResult: result,
816
+ });
813
817
  }
814
818
  const resumeAtStatus = resumeAtHttpStatus(result);
815
819
  if (resumeAtStatus) {
@@ -3,7 +3,7 @@ import { redactErrorMessage, redactHead } from "../observability/run-terminal-lo
3
3
  import { once } from "node:events";
4
4
  import { createHash } from "node:crypto";
5
5
  import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
6
- import { decideParkedAgent, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
6
+ import { decideParkedAgent, denyWorkflowParkIdentityLost, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
7
7
  import { isModelAllowlisted, matchCatalogModel } from "../model-select.js";
8
8
  import {} from "../config-center/facade.js";
9
9
  import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
@@ -17,6 +17,7 @@ import { normalizeApproachNotice, unknownTaskAgentModelRef, validateTaskAgents }
17
17
  import { isValidCwd, MAX_ADDITIONAL_DIRS } from "../task-cwd.js";
18
18
  import { runInBackground, evictIfConflict, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
19
19
  import { failureCodeOf, nestedPauseOf, stripResumeToken } from "../terminal.js";
20
+ import { mintCancelledResult } from "../run-cancel-context.js";
20
21
  import { personDecision } from "../host-decision.js";
21
22
  import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
22
23
  import { recordResumeResourceWindow } from "../resource-window.js";
@@ -32,7 +33,7 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
32
33
  import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
33
34
  import {} from "../observability/rate-limit.js";
34
35
  import { withPrincipal } from "../observability/principal-context.js";
35
- import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
36
+ import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
36
37
  import { redactSecrets } from "../trace/redact.js";
37
38
  import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
38
39
  import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
@@ -84,6 +85,7 @@ export { coarseStatusForState, errorCodeForExit };
84
85
  import { sendJson, sendError, httpErrorCode, msg, isSseResponse } from "./send.js";
85
86
  import { authorized, systemFor, gatedPrincipal, explicitOperatorOk, isOperator } from "./principal-gate.js";
86
87
  import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflict.js";
88
+ import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
87
89
  export { explicitOperatorOk, isOperator };
88
90
  export function flattenServiceDeps(deps) {
89
91
  const { stores, coordinators, seams, observability, governance, deployment, knobs, ...flat } = deps;
@@ -1142,7 +1144,7 @@ export function createHttpServer(rawDeps) {
1142
1144
  };
1143
1145
  }
1144
1146
  const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
1145
- const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !== "AskUserQuestion"
1147
+ const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME
1146
1148
  ? paForGrant.toolName
1147
1149
  : null;
1148
1150
  const boundGrant = onResumeCommitted
@@ -1207,6 +1209,10 @@ export function createHttpServer(rawDeps) {
1207
1209
  noteLateSettleIfAccepted(parked);
1208
1210
  return parked;
1209
1211
  }
1212
+ if (origin?.kind === "workflow_identity_lost") {
1213
+ void ctxPromise.catch(() => { });
1214
+ return denyWorkflowParkIdentityLost(origin);
1215
+ }
1210
1216
  if (origin?.kind === "workflow") {
1211
1217
  void ctxPromise.catch(() => { });
1212
1218
  const plan = planWorkflowParkedDecide(origin, parkedReq);
@@ -1255,10 +1261,10 @@ export function createHttpServer(rawDeps) {
1255
1261
  throw e;
1256
1262
  }
1257
1263
  const pendingTool = cp.pendingAction.toolName;
1258
- if (decision === "approve" && pendingTool === "AskUserQuestion" && !answer) {
1264
+ if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
1259
1265
  return { status: 400, body: { error: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", errorCode: "request.field_conflict" } };
1260
1266
  }
1261
- if (answer && (decision !== "approve" || pendingTool !== "AskUserQuestion")) {
1267
+ if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
1262
1268
  return { status: 400, body: { error: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, errorCode: "request.field_conflict" } };
1263
1269
  }
1264
1270
  const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
@@ -1306,6 +1312,7 @@ export function createHttpServer(rawDeps) {
1306
1312
  return await Promise.race([accepted, leg]);
1307
1313
  }
1308
1314
  async function driveResumeLeg(args, onAccepted) {
1315
+ const resumeLegStartedAt = Date.now();
1309
1316
  const { runner: legRunner, token, sessionId, principal, fleetScope, taskConfig, resumeObjective, outcome, verifyRounds } = args;
1310
1317
  const legKey = createHash("sha256").update(String(token)).digest("hex");
1311
1318
  const streamApprovalOn = resolveStreamApprovalGate({
@@ -1689,6 +1696,13 @@ export function createHttpServer(rawDeps) {
1689
1696
  await flush();
1690
1697
  await append("wiring_manifest", wiringManifestEventData(ev));
1691
1698
  break;
1699
+ case "tool_roster_delta": {
1700
+ await flush();
1701
+ const rosterDelta = toolRosterDeltaEventData(ev);
1702
+ if (rosterDelta !== undefined)
1703
+ await append("tool_roster_delta", rosterDelta);
1704
+ break;
1705
+ }
1692
1706
  case "human_input":
1693
1707
  await flush();
1694
1708
  await append("human_input", humanInputEventData(ev));
@@ -1797,7 +1811,7 @@ export function createHttpServer(rawDeps) {
1797
1811
  }
1798
1812
  }
1799
1813
  if (claimedRow && taskId && deps.runStore) {
1800
- const c = { taskId, sessionId, terminal: { kind: "failed", code: "cancelled", message: e.message }, stats: { turns: 0, tokens: 0 } };
1814
+ const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: e.message });
1801
1815
  await deps.runStore.setTerminal(taskId, "failed", c, e.message).catch(() => undefined);
1802
1816
  }
1803
1817
  settleFleet("failed");
@@ -1852,7 +1866,7 @@ export function createHttpServer(rawDeps) {
1852
1866
  }
1853
1867
  if (cancelCtrl.signal.aborted) {
1854
1868
  if (taskId && deps.runStore) {
1855
- const c = { taskId, sessionId, terminal: { kind: "failed", code: "cancelled", message: "cancelled by user" }, stats: { turns: 0, tokens: 0 } };
1869
+ const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user" });
1856
1870
  await deps.runStore.setTerminal(taskId, "failed", c, "cancelled by user").catch(() => undefined);
1857
1871
  }
1858
1872
  settleFleet("failed");
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { loadConfig, type ServiceConfig } from "./config.js";
2
2
  export { createBrain } from "./brain.js";
3
3
  export { createSessionStore } from "./plugins/session-store.js";
4
- export { createStoreBackend, type StoreBackend } from "./plugins/store-backend.js";
4
+ export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
5
5
  export { CachingSessionStore, type CachingOptions } from "./plugins/caching-session-store.js";
6
6
  export type { RunRecord, RunEvent } from "./plugins/store-contracts.js";
7
7
  export { runInBackground } from "./runs.js";