@sema-agent/server 7.125.0 → 7.126.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 (248) hide show
  1. package/MIGRATION.md +199 -2
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +2 -2
  4. package/USAGE.md +263 -140
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +47 -0
  7. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +5 -1
  8. package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -2
  9. package/deploy/sema-up/chart/templates/server.yaml +1 -1
  10. package/deploy/sema-up/chart/values.yaml +12 -0
  11. package/deploy/sema-up/cluster-up.sh +10 -10
  12. package/deploy/sema-up/kube-up.sh +11 -10
  13. package/deploy/sema-up/lib/dataroot.sh +1 -1
  14. package/deploy/sema-up/lib/envf.sh +5 -5
  15. package/deploy/sema-up/lib/flagshape.sh +5 -5
  16. package/deploy/sema-up/lib/lanes.py +1 -1
  17. package/deploy/sema-up/lib/parse-machines.py +2 -2
  18. package/deploy/sema-up/lib/shell-access.sh +1 -1
  19. package/deploy/sema-up/preflight.sh +4 -4
  20. package/deploy/sema-up/sema-up.sh +33 -33
  21. package/deploy/sema-up/smoke.sh +3 -3
  22. package/deploy/sema-up/stack/compose.yaml +1 -1
  23. package/deploy/sema-up/stack/sema-stack-cluster.yaml +6 -2
  24. package/dist/approval-card.d.ts +20 -4
  25. package/dist/approval-content-kind.d.ts +38 -22
  26. package/dist/approval-content-kind.js +20 -4
  27. package/dist/audit.js +2 -0
  28. package/dist/bench/s1/arms.d.ts +9 -37
  29. package/dist/bench/s1/arms.js +4 -61
  30. package/dist/bench/s1/live-deps.d.ts +4 -10
  31. package/dist/bench/s1/live-deps.js +1 -222
  32. package/dist/bench/s1/oracle.d.ts +1 -1
  33. package/dist/bench/s1/reviewer.d.ts +1 -1
  34. package/dist/bench/s1/row.d.ts +5 -7
  35. package/dist/bench/s1/row.js +0 -2
  36. package/dist/bench/s1/run-firm.d.ts +2 -2
  37. package/dist/bench/s1/run-firm.js +1 -4
  38. package/dist/bench/s1/runner-ctx.d.ts +17 -29
  39. package/dist/bench/s1/runner-ctx.js +3 -7
  40. package/dist/bench/s1/tasks.d.ts +9 -46
  41. package/dist/bench/s1/tasks.js +4 -63
  42. package/dist/boot/execution-env.d.ts +38 -26
  43. package/dist/boot/execution-env.js +71 -38
  44. package/dist/boot/memory-boundary.d.ts +1 -1
  45. package/dist/boot/parked-revive-gate.d.ts +0 -3
  46. package/dist/boot/parked-revive-gate.js +2 -2
  47. package/dist/boot/resolve-spec.d.ts +0 -2
  48. package/dist/boot/resolve-spec.js +12 -17
  49. package/dist/boot/retention-lane.d.ts +12 -1
  50. package/dist/boot/retention-lane.js +2 -0
  51. package/dist/boot/runner-deps.d.ts +5 -5
  52. package/dist/boot/runner-deps.js +5 -16
  53. package/dist/boot/session-faces.d.ts +5 -1
  54. package/dist/boot/session-faces.js +37 -1
  55. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  56. package/dist/boot/stage-03-store-backend.js +2 -2
  57. package/dist/boot/stage-04-stores.d.ts +1 -0
  58. package/dist/boot/stage-05-execution-env.d.ts +2 -2
  59. package/dist/boot/stage-05-execution-env.js +7 -4
  60. package/dist/boot/stage-06-runners.d.ts +2 -3
  61. package/dist/boot/stage-06-runners.js +8 -7
  62. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  63. package/dist/boot/stage-07-capability-layer.js +3 -4
  64. package/dist/boot/stage-08-reapers.d.ts +2 -3
  65. package/dist/boot/stage-08-reapers.js +2 -2
  66. package/dist/boot/stage-09-leader.d.ts +2 -3
  67. package/dist/boot/stage-09-leader.js +2 -1
  68. package/dist/boot/stage-10-http-server.d.ts +2 -3
  69. package/dist/boot/stage-10-http-server.js +2 -2
  70. package/dist/boot/stores.d.ts +1 -0
  71. package/dist/boot/stores.js +4 -3
  72. package/dist/boot/web-admin.d.ts +5 -0
  73. package/dist/boot/web-admin.js +28 -14
  74. package/dist/brain.d.ts +2 -2
  75. package/dist/brain.js +1 -1
  76. package/dist/capabilities/builtin-tools.d.ts +2 -1
  77. package/dist/capabilities/composite-members.d.ts +1 -1
  78. package/dist/capabilities/composite-members.js +1 -1
  79. package/dist/capabilities/hands-lane.d.ts +9 -6
  80. package/dist/capabilities/hands-lane.js +2 -2
  81. package/dist/capabilities/memory-notice.d.ts +27 -22
  82. package/dist/capabilities/memory-notice.js +15 -17
  83. package/dist/capabilities/scenarios.d.ts +9 -14
  84. package/dist/capabilities/scenarios.js +5 -50
  85. package/dist/capability-facts.d.ts +22 -11
  86. package/dist/capability-facts.js +13 -6
  87. package/dist/config-catalog.js +11 -4
  88. package/dist/config-provider.d.ts +5 -1
  89. package/dist/config-provider.js +23 -4
  90. package/dist/config-types.d.ts +14 -1
  91. package/dist/config.js +39 -6
  92. package/dist/deployment-governance.d.ts +19 -21
  93. package/dist/deployment-governance.js +7 -8
  94. package/dist/env-name-allowlist-knobs.d.ts +5 -5
  95. package/dist/env-name-allowlist-knobs.js +1 -0
  96. package/dist/execution-lane-caps.js +2 -2
  97. package/dist/hooks/hook-runner.d.ts +84 -30
  98. package/dist/hooks/hook-runner.js +74 -41
  99. package/dist/http/admission.js +12 -9
  100. package/dist/http/durable-run-leg.d.ts +5 -4
  101. package/dist/http/durable-run-leg.js +1 -1
  102. package/dist/http/idempotency.d.ts +28 -6
  103. package/dist/http/idempotency.js +10 -2
  104. package/dist/http/principal-gate.d.ts +14 -0
  105. package/dist/http/principal-gate.js +7 -0
  106. package/dist/http/request-guards.d.ts +2 -1
  107. package/dist/http/request-guards.js +14 -14
  108. package/dist/http/resume-legs.d.ts +2 -2
  109. package/dist/http/resume-legs.js +5 -4
  110. package/dist/http/route-ctx.d.ts +8 -6
  111. package/dist/http/routes/a2a-serve.js +11 -7
  112. package/dist/http/routes/approvals-assistant.js +18 -3
  113. package/dist/http/routes/background.d.ts +1 -1
  114. package/dist/http/routes/background.js +1 -1
  115. package/dist/http/routes/capabilities.js +10 -2
  116. package/dist/http/routes/mcp-reconnect.js +1 -1
  117. package/dist/http/routes/runs.d.ts +4 -3
  118. package/dist/http/routes/runs.js +12 -13
  119. package/dist/http/routes/session-sync.js +14 -11
  120. package/dist/http/routes/sessions.js +9 -9
  121. package/dist/http/routes/side-query.js +6 -19
  122. package/dist/http/routes/tasks.js +12 -11
  123. package/dist/http/server.d.ts +4 -0
  124. package/dist/http/server.js +2 -2
  125. package/dist/http/submit-replay.d.ts +15 -8
  126. package/dist/http/submit-replay.js +6 -0
  127. package/dist/http/wire-types.d.ts +7 -9
  128. package/dist/index.d.ts +0 -1
  129. package/dist/index.js +0 -1
  130. package/dist/internal-run-env.d.ts +30 -0
  131. package/dist/internal-run-env.js +11 -0
  132. package/dist/leader/wire.js +3 -2
  133. package/dist/memory-scope.d.ts +19 -7
  134. package/dist/memory-scope.js +25 -8
  135. package/dist/observability/fail-open.d.ts +15 -3
  136. package/dist/observability/fail-open.js +15 -3
  137. package/dist/observability/metrics.d.ts +0 -1
  138. package/dist/observability/metrics.js +0 -3
  139. package/dist/observability/run-terminal.js +69 -4
  140. package/dist/permission-rule-vocab.d.ts +3 -2
  141. package/dist/permission-rule-vocab.js +1 -1
  142. package/dist/plan-cache-probe.d.ts +3 -2
  143. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  144. package/dist/plugins/caching-session-store.d.ts +3 -0
  145. package/dist/plugins/caching-session-store.js +3 -0
  146. package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
  147. package/dist/plugins/checkpoint-store-sql.js +57 -11
  148. package/dist/plugins/file-run-store.d.ts +25 -21
  149. package/dist/plugins/file-run-store.js +70 -34
  150. package/dist/plugins/local-checkpoint-store.d.ts +8 -5
  151. package/dist/plugins/local-checkpoint-store.js +5 -15
  152. package/dist/plugins/local-session-store.d.ts +66 -7
  153. package/dist/plugins/local-session-store.js +168 -101
  154. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
  155. package/dist/plugins/memory-run-store.d.ts +19 -3
  156. package/dist/plugins/memory-run-store.js +38 -3
  157. package/dist/plugins/one-time-migrations.d.ts +141 -39
  158. package/dist/plugins/one-time-migrations.js +32 -143
  159. package/dist/plugins/pg-pool.js +11 -1
  160. package/dist/plugins/pg-session-storage.d.ts +2 -0
  161. package/dist/plugins/pg-session-storage.js +9 -5
  162. package/dist/plugins/remote-env-adb.d.ts +3 -0
  163. package/dist/plugins/remote-env-adb.js +1 -0
  164. package/dist/plugins/remote-env-device.d.ts +3 -0
  165. package/dist/plugins/remote-env-device.js +1 -0
  166. package/dist/plugins/remote-env-e2b.d.ts +4 -0
  167. package/dist/plugins/remote-env-e2b.js +2 -1
  168. package/dist/plugins/remote-env-host.d.ts +12 -0
  169. package/dist/plugins/remote-env-host.js +11 -1
  170. package/dist/plugins/remote-env-k8s.d.ts +227 -39
  171. package/dist/plugins/remote-env-k8s.js +450 -75
  172. package/dist/plugins/remote-env-local-docker.d.ts +4 -0
  173. package/dist/plugins/remote-env-local-docker.js +2 -1
  174. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  175. package/dist/plugins/remote-env-ssh.js +1 -0
  176. package/dist/plugins/retention-store-sql.d.ts +4 -1
  177. package/dist/plugins/retention-store-sql.js +11 -9
  178. package/dist/plugins/run-store-sql.d.ts +44 -6
  179. package/dist/plugins/run-store-sql.js +92 -27
  180. package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
  181. package/dist/plugins/sql-driver.d.ts +2 -1
  182. package/dist/plugins/store-backend.js +1 -1
  183. package/dist/plugins/store-contracts.d.ts +17 -3
  184. package/dist/plugins/tidb-pool.js +15 -3
  185. package/dist/plugins/tidb-session-storage.js +3 -2
  186. package/dist/plugins/tidb-session-store.d.ts +2 -0
  187. package/dist/plugins/tidb-session-store.js +6 -2
  188. package/dist/request-key-closure.d.ts +12 -0
  189. package/dist/request-key-closure.js +3 -3
  190. package/dist/router/route-orchestration.d.ts +3 -11
  191. package/dist/router/route-orchestration.js +0 -4
  192. package/dist/rule-entry-list.js +0 -4
  193. package/dist/rules-consent.js +3 -0
  194. package/dist/run-local.d.ts +5 -4
  195. package/dist/run-local.js +23 -17
  196. package/dist/runs.js +1 -0
  197. package/dist/security.d.ts +7 -0
  198. package/dist/security.js +5 -0
  199. package/dist/server-secret-env.d.ts +10 -4
  200. package/dist/server-secret-env.js +4 -1
  201. package/dist/session-incarnation.d.ts +46 -2
  202. package/dist/session-incarnation.js +14 -3
  203. package/dist/session-sync-kernel.d.ts +10 -3
  204. package/dist/spec-fields.d.ts +24 -8
  205. package/dist/spec-fields.js +21 -2
  206. package/dist/task-workflow.d.ts +6 -4
  207. package/dist/task-workflow.js +3 -2
  208. package/dist/tool-approval.d.ts +78 -6
  209. package/dist/tool-approval.js +7 -3
  210. package/dist/trace/core-keyset-guard.d.ts +4 -4
  211. package/dist/trace/engine-notice-wire.d.ts +26 -1
  212. package/dist/trace/engine-notice-wire.js +7 -2
  213. package/dist/trace/project.d.ts +8 -3
  214. package/dist/trace/project.js +15 -1
  215. package/dist/trace/self-minted-wire.d.ts +104 -2
  216. package/dist/trace/self-minted-wire.js +35 -0
  217. package/dist/web-admin-client/apply-effective.d.ts +13 -7
  218. package/dist/web-admin-client/apply-effective.js +11 -26
  219. package/dist/web-admin-client/apply-ledger.d.ts +2 -2
  220. package/dist/web-admin-client/candidate-gate.d.ts +39 -4
  221. package/dist/web-admin-client/candidate-gate.js +9 -2
  222. package/dist/web-admin-client/facade.d.ts +2 -2
  223. package/dist/web-admin-client/http-client.d.ts +3 -1
  224. package/dist/web-admin-client/http-client.js +4 -3
  225. package/dist/web-admin-client/read-warnings.d.ts +33 -5
  226. package/dist/web-admin-client/read-warnings.js +41 -6
  227. package/dist/web-admin-client/types.d.ts +11 -5
  228. package/dist/workspace-cache.d.ts +45 -0
  229. package/dist/workspace-cache.js +30 -0
  230. package/dist/workspace-space-commit.d.ts +116 -0
  231. package/dist/workspace-space-commit.js +117 -0
  232. package/dist/workspace-space.d.ts +75 -9
  233. package/dist/workspace-space.js +52 -1
  234. package/package.json +4 -4
  235. package/dist/boot/session-incarnation-seat.d.ts +0 -41
  236. package/dist/boot/session-incarnation-seat.js +0 -69
  237. package/dist/capabilities/code-review-council.d.ts +0 -49
  238. package/dist/capabilities/code-review-council.js +0 -111
  239. package/dist/capabilities/collab-wire.d.ts +0 -36
  240. package/dist/capabilities/collab-wire.js +0 -2
  241. package/dist/capabilities/collab-workflows.d.ts +0 -47
  242. package/dist/capabilities/collab-workflows.js +0 -90
  243. package/dist/capabilities/prompts/code-review.d.ts +0 -25
  244. package/dist/capabilities/prompts/code-review.js +0 -39
  245. package/dist/capabilities/prompts/team.d.ts +0 -10
  246. package/dist/capabilities/prompts/team.js +0 -6
  247. package/dist/capabilities/team.d.ts +0 -113
  248. package/dist/capabilities/team.js +0 -70
@@ -1,12 +1,9 @@
1
1
  import { type Autonomy } from "../runtime-governance.js";
2
2
  import type { GovernanceOverrides } from "../governance-overrides.js";
3
- export type OrchestrationMode = "single" | "supervisor" | "team";
3
+ export type OrchestrationMode = "single" | "supervisor";
4
4
  /** The DETERMINISTIC signals the routing decision reads (extracted by {@link deriveRoutingSignals} from the
5
5
  * task's resolved config — no task-content LLM judgement). */
6
6
  export interface RoutingSignals {
7
- /** The caller explicitly chose an orchestration (a `council`/`debate`/`team` request). Honored verbatim —
8
- * a deliberate choice outranks automatic routing (back-compat + operator intent). */
9
- explicitMode?: OrchestrationMode;
10
7
  /** (a) The task can cause harm its sandbox does NOT contain ⇒ route to SUP prevention. */
11
8
  dangerousCapability: boolean;
12
9
  /** (b) The task delegates / fans out / is long-horizon ⇒ supervisor goal-keeping + ask-aggregation. */
@@ -14,19 +11,16 @@ export interface RoutingSignals {
14
11
  }
15
12
  export interface RoutingDecision {
16
13
  mode: OrchestrationMode;
17
- /** A short, audit-friendly machine reason (e.g. `explicit:team` / `danger:prevention-gate` / `default:single`).
14
+ /** A short, audit-friendly machine reason (e.g. `danger:prevention-gate` / `default:single`).
18
15
  * Emitted on the trace so a routing decision is always explainable (never a silent mode flip). */
19
16
  reason: string;
20
17
  }
21
18
  /**
22
- * The routing decision (pure). Order matters: explicit caller choice first, then (a) danger, then (b) structural,
23
- * else the single default. `team` is NEVER produced here — it only ever appears via {@link RoutingSignals.explicitMode}.
19
+ * The routing decision (pure). Order matters: (a) danger first, then (b) structural, else the single default.
24
20
  */
25
21
  export declare function routeOrchestration(s: RoutingSignals): RoutingDecision;
26
22
  /** The task facts {@link deriveRoutingSignals} reads — all DETERMINISTIC, resolved before the task runs. */
27
23
  export interface RoutingContext {
28
- /** Caller-explicit orchestration (council/debate/team) — undefined ⇒ the router decides. */
29
- explicitMode?: OrchestrationMode;
30
24
  /** The resolved autonomy for this task — `read-only` hands cannot mutate/egress ⇒ no danger escalation. */
31
25
  autonomy?: Autonomy;
32
26
  /** The mounted tools can WRITE the project or run a shell (edit_file / write_file / bash). */
@@ -68,8 +62,6 @@ export declare function routeServiceTask(input: {
68
62
  hasExecutionEnv: boolean;
69
63
  /** The resolved autonomy for the task. */
70
64
  autonomy?: Autonomy;
71
- /** The caller explicitly requested a fan-out (council/debate/team). */
72
- explicitTeam: boolean;
73
65
  }): RoutingDecision;
74
66
  /**
75
67
  * Resolve whether a task's execution env STRONGLY isolates side effects (a VM/kernel boundary the sandbox tears
@@ -3,8 +3,6 @@ import { autonomyOverrides } from "../runtime-governance.js";
3
3
  import { operatorAlwaysHumanAsk } from "../operator-ask.js";
4
4
  import { heldRuleName } from "../tool-rule-names.js";
5
5
  export function routeOrchestration(s) {
6
- if (s.explicitMode)
7
- return { mode: s.explicitMode, reason: `explicit:${s.explicitMode}` };
8
6
  if (s.dangerousCapability)
9
7
  return { mode: "supervisor", reason: "danger:prevention-gate" };
10
8
  if (s.needsGoalKeeping)
@@ -15,7 +13,6 @@ export function deriveRoutingSignals(ctx) {
15
13
  const readOnly = ctx.autonomy === "read-only";
16
14
  const dangerousCapability = !readOnly && (ctx.canEgress || (ctx.canWriteOrExec && !ctx.isolatedExecEnv));
17
15
  return {
18
- ...(ctx.explicitMode ? { explicitMode: ctx.explicitMode } : {}),
19
16
  dangerousCapability,
20
17
  needsGoalKeeping: ctx.delegatedOrLongHorizon,
21
18
  };
@@ -23,7 +20,6 @@ export function deriveRoutingSignals(ctx) {
23
20
  export function routeServiceTask(input) {
24
21
  const readOnlyHands = autonomyOverrides(input.autonomy).handsReadOnly === true;
25
22
  return routeOrchestration(deriveRoutingSignals({
26
- ...(input.explicitTeam ? { explicitMode: "team" } : {}),
27
23
  ...(input.autonomy ? { autonomy: input.autonomy } : {}),
28
24
  canWriteOrExec: input.hasExecutionEnv && !readOnlyHands,
29
25
  canEgress: false,
@@ -27,10 +27,6 @@ export function inertRuleEntryGuidance(i) {
27
27
  return "`*` names no tool (on an allow list it voids the whole list) — name the tools instead";
28
28
  case "namespace_wildcard":
29
29
  return "a server segment does not take a wildcard (`mcp__*`) — name the server: mcp__<server> or mcp__<server>__*";
30
- case "path_pattern":
31
- return "a write-path or file-pattern entry has no reading on this side — name the tool, or use Read(//<dir>/**) for a directory a Read may cover";
32
- case "domain":
33
- return "a domain entry has no reading on this side — name the tool";
34
30
  case "content_unsupported":
35
31
  return "this tool takes no content form here — name the tool itself, or use Bash(<command>) / Bash(<prefix>:*) for a shell command";
36
32
  case "grammar":
@@ -96,6 +96,9 @@ function isSupportedRuleMatch(match) {
96
96
  case "name":
97
97
  case "name_prefix":
98
98
  return false;
99
+ case "host":
100
+ case "subdomain":
101
+ return false;
99
102
  default: {
100
103
  const unknown = match;
101
104
  void unknown;
@@ -14,8 +14,9 @@ export interface RunLocalArgs {
14
14
  json: boolean;
15
15
  /** `--scenario <name>` — pick a capability bundle (default: config.defaultScenario). */
16
16
  scenario?: string;
17
- /** `--session <id>` — REUSE a session across invocations (durable multi-turn). Default: a fresh id (the
18
- * session + its events still persist to disk, just not continued). */
17
+ /** `--session <uuid>` — REUSE a session across invocations (durable multi-turn). Only a canonical uuid (any version) is
18
+ * accepted — the same one shape law as the HTTP submit door (`sessionIdShapeIssue`). Default: a fresh id (the session +
19
+ * its events still persist to disk, just not continued). */
19
20
  session?: string;
20
21
  /** `--workspace <dir>` — the FIXED dir the agent's tool calls operate in (CC-like). Default: the cwd. The
21
22
  * agent's file edits PERSIST there (never deleted) — this is the user's own directory, not a sandbox. */
@@ -96,8 +97,8 @@ export interface LocalGovernanceConfigView extends DeploymentGovernanceConfigVie
96
97
  * test/run-local.test.ts(该钉已由 🟡 特征化翻成 🔴 正控)。
97
98
  * · **禁 memoize**:四个旋钮都是热改字段,每次装配现读活 config(与另两条腿同一姿势)。
98
99
  */
99
- /** @param engineDataRoot 本腿已铸的引擎记忆根(`memoryEngineBackendFor(config, root)?.root`,S-133 单一写者);`undefined` = 引擎未接。 */
100
- export declare function applyLocalGovernance(base: TaskSpec, config: LocalGovernanceConfigView, workspaceDir: string, engineDataRoot: string | undefined): TaskSpec;
100
+ /** 豁免根(引擎数据根)不在这里:core 7.49.0 [ref] 起只从 `RunnerDeps.memoryEngineDir` 来(本腿 Runner 装配处,记忆引擎开不开都给)。 */
101
+ export declare function applyLocalGovernance(base: TaskSpec, config: LocalGovernanceConfigView, workspaceDir: string): TaskSpec;
101
102
  /** {@link createLocalApprover} 的 io 面(全部可注入,所以两条臂都测得动)。 */
102
103
  export interface LocalApproverIo {
103
104
  /** 这次调用是不是接在一个人面前(生产里 = `process.stdin.isTTY`)。 */
package/dist/run-local.js CHANGED
@@ -38,10 +38,10 @@ import { createRepoClient } from "./capabilities/repo-tools.js";
38
38
  import { webSearchConfigFromEnv, createWebSearchBackend } from "./plugins/web-search.js";
39
39
  import { buildScenarios, gateScenarioRequest, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
40
40
  import { createTaskListLane } from "./boot/task-list-lane.js";
41
- import { pickHandsRunner, withoutExecutionEnv } from "./capabilities/hands-lane.js";
42
- import { HttpError } from "./security.js";
41
+ import { pickHandsRunner, withoutHands } from "./capabilities/hands-lane.js";
42
+ import { HttpError, sessionIdShapeIssue } from "./security.js";
43
43
  import { createMemoryEngineIncidentSink } from "./memory-incident-sink.js";
44
- import { memoryEngineBackendFor, memoryProjectKeyFor, memorySpecForRequest } from "./memory-scope.js";
44
+ import { fileMemoryEngineRoot, memoryEngineBackendFor, memoryProjectKeyFor, memorySpecForRequest } from "./memory-scope.js";
45
45
  import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
46
46
  import { createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync-client.js";
47
47
  import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
@@ -68,9 +68,9 @@ export function parseArgs(argv) {
68
68
  let workspace;
69
69
  let user;
70
70
  let json = false;
71
- const valueFlag = (i, name) => {
71
+ const valueFlag = (i, name, opts = {}) => {
72
72
  const v = argv[i];
73
- if (v === undefined || v.trim() === "" || v.startsWith("--"))
73
+ if (v === undefined || (opts.blankIsValue !== true && v.trim() === "") || v.startsWith("--"))
74
74
  throw new UsageError(`${name} needs an argument`);
75
75
  return v;
76
76
  };
@@ -82,14 +82,18 @@ export function parseArgs(argv) {
82
82
  root = valueFlag(++i, "--root");
83
83
  else if (a === "--scenario")
84
84
  scenario = valueFlag(++i, "--scenario");
85
- else if (a === "--session")
86
- session = valueFlag(++i, "--session");
85
+ else if (a === "--session") {
86
+ session = valueFlag(++i, "--session", { blankIsValue: true });
87
+ const issue = sessionIdShapeIssue(session);
88
+ if (issue)
89
+ throw new UsageError(issue);
90
+ }
87
91
  else if (a === "--workspace")
88
92
  workspace = valueFlag(++i, "--workspace");
89
93
  else if (a === "--user")
90
94
  user = valueFlag(++i, "--user");
91
95
  else if (a === "--help" || a === "-h") {
92
- throw new UsageError('usage: run-local "<objective>" [--root <dir>] [--json] [--scenario <name>] [--session <id>] [--workspace <dir>] [--user <id>]');
96
+ throw new UsageError('usage: run-local "<objective>" [--root <dir>] [--json] [--scenario <name>] [--session <uuid>] [--workspace <dir>] [--user <id>]');
93
97
  }
94
98
  else if (a.startsWith("--")) {
95
99
  throw new UsageError(`unknown flag: ${a}`);
@@ -133,8 +137,8 @@ export function missingGatewayRequirements(config) {
133
137
  return missing;
134
138
  }
135
139
  const RUN_LOCAL_APPROVAL_SEAT = { kind: "live" };
136
- export function applyLocalGovernance(base, config, workspaceDir, engineDataRoot) {
137
- return applyRuntimeGovernance({ ...base, toolPolicy: createApprovalBaselinePolicy(config, RUN_LOCAL_APPROVAL_SEAT) }, createDeploymentGovernanceInputs(config, { env: new NodeExecutionEnv({ cwd: workspaceDir }), cwd: workspaceDir }, { engineDataRoot }));
140
+ export function applyLocalGovernance(base, config, workspaceDir) {
141
+ return applyRuntimeGovernance({ ...base, toolPolicy: createApprovalBaselinePolicy(config, RUN_LOCAL_APPROVAL_SEAT) }, createDeploymentGovernanceInputs(config, { env: new NodeExecutionEnv({ cwd: workspaceDir }), cwd: workspaceDir }));
138
142
  }
139
143
  const NO_TTY_REFUSAL_REASON = "this run-local invocation has no TTY, so there is no one to put this approval to; it is refused fail-closed instead of being asked.";
140
144
  const UNDISPLAYABLE_ARGS_REFUSAL_REASON = "the call's arguments cannot be displayed, so there is nothing a person could review; it is refused fail-closed instead of being asked.";
@@ -145,6 +149,7 @@ export const NO_TTY_ASK_CLASS_ROW = {
145
149
  ancestor: { remedy: "terminal_only", autoBudgetReleases: false },
146
150
  dangerous_delete: { remedy: "terminal_only", autoBudgetReleases: false },
147
151
  unverifiable_delete: { remedy: "terminal_only", autoBudgetReleases: false },
152
+ unverifiable_shell_text: { remedy: "terminal_only", autoBudgetReleases: false },
148
153
  transcript_write: { remedy: "terminal_only", autoBudgetReleases: false },
149
154
  org_rule: { remedy: "terminal_only", autoBudgetReleases: false },
150
155
  governance_unavailable: { remedy: "terminal_only", autoBudgetReleases: false },
@@ -154,7 +159,7 @@ export const NO_TTY_ASK_CLASS_ROW = {
154
159
  classifier_no_ruling: { remedy: "terminal_only", autoBudgetReleases: false },
155
160
  operator_forced: { remedy: "deployment_mandate", autoBudgetReleases: false },
156
161
  shell_floor: { remedy: "shell_gate", autoBudgetReleases: false },
157
- write_protection_unresolved: { remedy: "terminal_only", autoBudgetReleases: false },
162
+ target_unresolved: { remedy: "terminal_only", autoBudgetReleases: false },
158
163
  probe: { remedy: "terminal_only", autoBudgetReleases: false },
159
164
  write_protection: { remedy: "terminal_only", autoBudgetReleases: false },
160
165
  tool_marks_mcp: { remedy: "terminal_only", autoBudgetReleases: false },
@@ -441,6 +446,7 @@ export async function runLocal(argv, deps = {}) {
441
446
  : undefined;
442
447
  const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ? createHostLspManager(config, (event, fields) => logger.info(event, fields)) : undefined;
443
448
  const memoryEngine = memoryEngineBackendFor(config, root, memoryEngineIncident);
449
+ const engineDataRoot = fileMemoryEngineRoot(config, root);
444
450
  try {
445
451
  judgeConsolidationValve({ config, memoryEngine });
446
452
  }
@@ -547,6 +553,7 @@ export async function runLocal(argv, deps = {}) {
547
553
  const sharedRunnerDeps = createSharedRunnerDeps({
548
554
  metrics,
549
555
  config,
556
+ engineDataRoot,
550
557
  governanceSeams,
551
558
  logger,
552
559
  hands: { commitCoAuthor: "Sema <noreply@vivi-ai.com>" },
@@ -574,7 +581,7 @@ export async function runLocal(argv, deps = {}) {
574
581
  const runnerDeps = {
575
582
  ...sharedRunnerDeps,
576
583
  sessionStore,
577
- ...(memoryEngine ? { memoryBackend: memoryEngine.backend, memoryEngineDir: memoryEngine.root } : {}),
584
+ ...(memoryEngine ? { memoryBackend: memoryEngine.backend } : {}),
578
585
  onError: engineOnError,
579
586
  onAsk,
580
587
  ...(config.projectMemoryEnabled
@@ -587,15 +594,14 @@ export async function runLocal(argv, deps = {}) {
587
594
  const runner = new Runner(runnerDeps);
588
595
  const subRunnerDeps = { ...sharedRunnerDeps, sessionStore: new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 })), onError: engineOnError, onAsk };
589
596
  const subRunner = new Runner(subRunnerDeps);
590
- const handslessRunner = new Runner(withoutExecutionEnv(runnerDeps));
591
- const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
597
+ const handslessRunner = new Runner(withoutHands(runnerDeps));
592
598
  const skills = loadDeploymentSkills(config);
593
599
  const repoClient = config.gitApiBaseUrl ? createRepoClient(config.gitApiKind, config.gitApiBaseUrl, config.gitApiToken) : undefined;
594
600
  const webSearchCfg = webSearchConfigFromEnv();
595
601
  const webSearch = webSearchCfg ? createWebSearchBackend(webSearchCfg) : undefined;
596
602
  const taskListLane = createTaskListLane({ logger });
597
603
  const scenarioDeps = {
598
- runner, subRunner, handslessSubRunner, model: DEFAULT_MODEL_REF, skills, repoClient, metrics, logger,
604
+ runner, subRunner, model: DEFAULT_MODEL_REF, skills, repoClient, logger,
599
605
  ...(webSearch ? { webSearch } : {}),
600
606
  backgroundAgentStore: fileBackend.backgroundAgentStore,
601
607
  taskListStoreFor: taskListLane.storeFor,
@@ -634,7 +640,7 @@ export async function runLocal(argv, deps = {}) {
634
640
  }
635
641
  const scope = args.user ?? "local";
636
642
  const taskTimeoutSec = Math.floor(parseNumOrFailNonNegative("TASK_TIMEOUT_SEC", process.env.TASK_TIMEOUT_SEC || "0"));
637
- const timeoutSec = taskWallClockSec(taskTimeoutSec, false, scenarioName === "discuss");
643
+ const timeoutSec = taskWallClockSec(taskTimeoutSec, false);
638
644
  const mcp = mcpForScenario(config.mcpServers, scenarioName);
639
645
  const a2a = a2aForScenario(config.a2aPeers, scenarioName);
640
646
  const maxCostUsd = cappedCeiling(undefined, config.maxTaskCostUsd);
@@ -662,7 +668,7 @@ export async function runLocal(argv, deps = {}) {
662
668
  ...(mcp ? { mcp } : {}),
663
669
  ...(a2a ? { a2a } : {}),
664
670
  ...(Object.keys(limits).length > 0 ? { limits } : {}),
665
- }, config, workspaceDir, memoryEngine?.root), onAsk);
671
+ }, config, workspaceDir), onAsk);
666
672
  logger.info("run_local_start", { scenario: scenarioName, model: spec.model, sessionId, exec: executionLaneOf(config.remoteExec?.provider) });
667
673
  const settleSessionBackground = async () => {
668
674
  const reaped = defaultTaskRegistry.reapSessionBackground(sessionId, scope);
package/dist/runs.js CHANGED
@@ -83,6 +83,7 @@ const PREPARE_REFUSAL_HTTP_STATUS = {
83
83
  "config.skill_listing_budget_invalid": 400,
84
84
  "config.user_invoked_skill_unknown": 400,
85
85
  "config.image_invalid": 400,
86
+ "config.tool_face_control": 400,
86
87
  "config.memory_capture_unsupported": 409,
87
88
  };
88
89
  export function resumeAtHttpStatus(result) {
@@ -195,6 +195,13 @@ export type OwnerAwareSessionStore = SessionStore & {
195
195
  /** True iff `s` matches the canonical uuidv7 shape core mints (see {@link UUIDV7_RE}). */
196
196
  export declare function isUuidV7(s: string): boolean;
197
197
  export declare function isUuidShape(s: string): boolean;
198
+ /** The submit door's refusal of a caller-supplied session id that is not a canonical uuid, or `undefined` when the value
199
+ * is absent / not a string (the authorizer only ever reads a string `sessionId`; a non-string never enters the session
200
+ * slot) / a canonical uuid. The sentence opens with the store verbs' sentence (`invalid session id (must be a canonical
201
+ * uuid)`, same `request.id_invalid` code) and then names the remedy — a self-chosen name is not a session id; the caller
202
+ * keeps its own name → uuid mapping. HTTP admission answers it as `400 request.id_invalid`; `run-local --session` prints
203
+ * it as a usage error: ONE judge, ONE sentence, no second door-specific rule. */
204
+ export declare function sessionIdShapeIssue(sessionId: unknown): string | undefined;
198
205
  /** An HTTP error the server maps to a status code (instead of a generic 500). `code` (optional) is a stable
199
206
  * machine-readable identifier the HTTP layer echoes as `{code}` beside the human `{error}` message — the
200
207
  * "typed rejection" posture (structured code on the wire, prose stays with the shell/web).
package/dist/security.js CHANGED
@@ -25,6 +25,11 @@ const UUID_ANY_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}
25
25
  export function isUuidShape(s) {
26
26
  return UUID_ANY_RE.test(s);
27
27
  }
28
+ export function sessionIdShapeIssue(sessionId) {
29
+ if (typeof sessionId !== "string" || isUuidShape(sessionId))
30
+ return undefined;
31
+ return "invalid session id (must be a canonical uuid); a self-chosen session name is not accepted — use a uuid (any version) and keep your own name-to-uuid mapping";
32
+ }
28
33
  export class HttpError extends Error {
29
34
  status;
30
35
  code;
@@ -19,7 +19,7 @@
19
19
  * {@link scrubServerSecrets} = core `scrubSecretEnv`(按形)∘ 本仓按名两臂:
20
20
  * · 目录臂 —— 配置目录(`config-catalog.ts`)里 `type:"secret"` 的每一行,从目录机器派生,不手抄第二份名单;
21
21
  * · 名单型旋钮臂 —— `ENV_NAME_ALLOWLIST_KNOBS` 的每一只(`env-name-allowlist-knobs.ts`,表键派生):名单**原文**可能夹着一枚被错展开进来
22
- * 的令牌,按凭据类对待。服务侧三只(`LSP_ENV_ALLOW` S-699 / `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` S-713)同时是目录 secret 行,
22
+ * 的令牌,按凭据类对待。服务侧四只(`LSP_ENV_ALLOW` S-699 / `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` S-713 / `K8S_SANDBOX_ENV`)同时是目录 secret 行,
23
23
  * 目录臂先认;本臂接住的是**不在服务目录里**的那一只 —— bake-runner 的 `BAKE_ENV_ALLOW`(它不是服务进程的配置)。
24
24
  * core 形状规则的缺口由 core 源头修 —— 7.30.1 #1072 补 `KEYS|TOKENS|PASSWORDS`(S-616),7.33.7 #1180 按名认 OTLP 头变量一族(7.103.2 车PM 提货;目录臂照旧点名
25
25
  * `OTEL_EXPORTER_OTLP_HEADERS` = 双保险,互证钉在 `test/secret-env-scrub-disclosure.test.ts`),目录臂独担的只剩名单型旋钮(值是变量名单,core 本来就不该认它们)。
@@ -32,10 +32,10 @@
32
32
  * ## 点名放行(S-699 / S-713,一只谓词各处用)
33
33
  * {@link allowlistedEnv} 是本仓「一张名单点到哪些 env 键,就把它们从源 env **原样**交给子进程」的唯一实现。调用:
34
34
  * host shell 的 `inheritEnv:[names]`(`plugins/remote-env-host.ts`,hermetic 形)、{@link hostSubprocessEnv}(剥后放回形:本地语言服务器的
35
- * `LSP_ENV_ALLOW`、bake-runner 的 `BAKE_ENV_ALLOW`)、沙箱带外 env 的 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV`(`config.ts`,S-713 起)。
35
+ * `LSP_ENV_ALLOW`、bake-runner 的 `BAKE_ENV_ALLOW`)、沙箱带外 env 的 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`(`config.ts`,S-713 起)。
36
36
  * 名单型旋钮的**名单**另由 `config.ts` 的同一只解析器 `parseEnvNameAllowlist` 判形(形错拒启 / 源里没有的名按序号 warn),本函数只管取值。
37
37
  * 「点名」在各处同义:点到的键原样带值(空串值照带),密钥形也照过(点名 = 部署方显式承担);源里没有的名不产生条目。
38
- * 沙箱两腿不继承本进程环境:进沙箱的本进程值只有点到的键(与 host shell 的 hermetic 形同一侧)。
38
+ * 沙箱三腿不继承本进程环境:进沙箱的本进程值只有点到的键(与 host shell 的 hermetic 形同一侧)。
39
39
  * host shell 与「剥后放回」形**只在没点到的键上**不同,这是有意保留的差别:host shell 的 `string[]` 形是 hermetic 白名单(与 core
40
40
  * `NodeExecutionEnv` 同名选项同义,没点到的一律不继承);语言服务器 / bake-runner 在 {@link scrubServerSecrets} 的结果之上放回点名键
41
41
  * (没点到的照缺省剥,`PATH` / `HOME` 这类非密钥键照常在 —— hermetic 形会让子进程当场找不到自己的工具链)。
@@ -70,7 +70,7 @@ export declare const CATALOG_SECRET_ENV_NAMES: ReadonlySet<string>;
70
70
  * 各腿不直接调它 —— 经 {@link hostSubprocessEnv}(剥 + 放回 + 留痕一只口)。
71
71
  *
72
72
  * **只为宿主子进程组合**。本包跑在别的平台上的子进程(ssh / k8s / adb / e2b / docker 车道)的 env 不经本函数:那些车道不继承本进程 env,只交
73
- * 调用方显式给的 env 与部署方点名的键(`E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV`,经 {@link allowlistedEnv})。(core 7.33.6 #1159 的平台尾参随
73
+ * 调用方显式给的 env 与部署方点名的键(`E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`,经 {@link allowlistedEnv})。(core 7.33.6 #1159 的平台尾参随
74
74
  * core 7.35.0 #1201 删除 —— git 的 env 配置族成了豁免族,不再有「按子进程平台读记录」这件事,S-877。)
75
75
  */
76
76
  export declare function scrubServerSecrets(env: NodeJS.ProcessEnv, findings: ServerSecretEnvFinding[]): NodeJS.ProcessEnv;
@@ -100,6 +100,12 @@ export declare function hostSubprocessEnv(o: {
100
100
  site: SecretEnvScrubSite;
101
101
  allow: readonly string[];
102
102
  }, sourceEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
103
+ /**
104
+ * {@link hostSubprocessEnv} 的**组合本体**(不披露、不计数)—— 同一只组合,两个入口:真要交给子进程的现场经 `hostSubprocessEnv`(组合 + 披露);
105
+ * 只想知道「子进程**会**拿到什么」的读者(core 7.49.0 #1554:host 车道的 `ExecutionEnv.cdPath` 要报子进程真拿到的 `CDPATH`)读这一只 ——
106
+ * 既不另写一份组合(第二份会漂),也不把一次没发生的 spawn 记进剥除计数。
107
+ */
108
+ export declare function composeHostSubprocessEnv(allow: readonly string[], sourceEnv?: NodeJS.ProcessEnv, findings?: ServerSecretEnvFinding[]): NodeJS.ProcessEnv;
103
109
  /** git 子进程腿的现场(闭集里名字带 `.git-` 的那几只 —— 编译期筛;它们都没有点名放行面)。 */
104
110
  export type GitSubprocessSite = Extract<SecretEnvScrubSite, `${string}.git-${string}`>;
105
111
  /**
@@ -24,10 +24,13 @@ export function scrubServerSecrets(env, findings) {
24
24
  }
25
25
  export function hostSubprocessEnv(o, sourceEnv = process.env) {
26
26
  const scrubFindings = [];
27
- const composed = { ...scrubServerSecrets(sourceEnv, scrubFindings), ...allowlistedEnv(o.allow, sourceEnv) };
27
+ const composed = composeHostSubprocessEnv(o.allow, sourceEnv, scrubFindings);
28
28
  recordSecretEnvScrub(o.site, scrubFindings.filter((f) => composed[f.key] !== sourceEnv[f.key]), o.allow);
29
29
  return composed;
30
30
  }
31
+ export function composeHostSubprocessEnv(allow, sourceEnv = process.env, findings = []) {
32
+ return { ...scrubServerSecrets(sourceEnv, findings), ...allowlistedEnv(allow, sourceEnv) };
33
+ }
31
34
  export function gitSubprocessEnv(o) {
32
35
  return {
33
36
  ...hostSubprocessEnv({ site: o.site, allow: [] }),
@@ -1,3 +1,33 @@
1
+ /**
2
+ * S-790 / S-506([ref] §2)—— **会话化身**:一个会话 id 是座位,化身是这一位住客。
3
+ *
4
+ * 会话 id 由调用方自选,删掉之后同一个 id 可以被别人(或同一个人)重新登记(`boot/session-faces.ts` 的级联删除注自认)。
5
+ * 本仓大量持久状态只按会话 id 分区,于是「上一位住客留下的东西」会在下一位名下继续起作用。化身令牌给「是不是同一位住客」
6
+ * 一个可比的答案 —— 今天只能比属主,同一个人删了重建就比不出来。
7
+ *
8
+ * ## 规则(全仓唯一处写)
9
+ * · **铸点只在新建行**:会话店的 `register` / 无 id 创建 / fork / session-sync 的导入新行。已存在的行一个字节都不动
10
+ * (与 `owner` 同一条 first-write 律);session-sync 的整段换装(replace / staged commit)换的是**同一位住客**的日志,
11
+ * 化身沿用。删除随行消失,同 id 重新登记铸新的。
12
+ * · **值是不透明随机串**(`randomUUID()`,v4:没有时间戳、没有机器信息 —— 刻意不用 uuidv7,那一形带铸造时刻)。只拿来比较。
13
+ * · **一格、一个解码**:住客令牌是会话**行**上的一格 —— SQL `session_meta.incarnation`(两方言)、local 后端的会话元数据边档那一格
14
+ * (`plugins/local-session-store.ts` 的 `session-incarnations.json`)。每只店对它只有**一次读**(`sessionFaceOf`),行上的值只经
15
+ * {@link incarnationFromCell} 解码;别的读法都从这一次读派生(下一节),没有第二份。
16
+ * · **读口唯一**:会话店的 `sessionFaceOf(sessionId) → { owner, incarnation } | undefined`,一次读同时给两样
17
+ * (两次独立读在删除 / 重登记的缝里能拼出混世代组合,与 `getHead` 同一条理由)。`undefined` = 没有这只会话。
18
+ * · **读到空化身 ⇒ 响亮拒**({@link SessionIncarnationMissingError}):一次性迁移(`plugins/one-time-migrations.ts` 登记表 M5)
19
+ * 之后,空值只可能来自没铸化身的写腿或人为改库 —— 把它当「通配」就是让任何人都能和这一行比成同一位住客。
20
+ * · **不上 wire、不进模型上下文、不进日志正文**:日志里要点名时只打 {@link incarnationLogPrefix}(前 8 位)。
21
+ *
22
+ * ## 谁读它(全部派生自那一格)
23
+ * · `/decide` 的 parked 赎回腿(`parked-decide.ts`):claim 前读一次、claim 后再读一次,会话不在或化身变了 ⇒ 回滚 claim、
24
+ * 答既有的 `409 decide.not_parked`;赎回面(`parked-revive-surface.ts`)只在属主相等**且**两次读是同一位住客时交继承座。
25
+ * · core 的记忆引擎:Runner 读**它取到的那只会话**的 `getMetadata().incarnation`(SQL 两店的 wake 带出这一列;local 店交出的会话
26
+ * 在元数据上答边档那一格),不再有宿主座。
27
+ * · 按 id 读的消费方(没取会话的那一侧 —— k8s 工作区空间的分区,`boot/execution-env.ts`):core 的 `SessionStore.incarnationOf`,
28
+ * 三只店都实装为 `sessionFaceOf` 的化身那一半;缓存包装层原样转发。「店有没有这只读」只判一处:{@link incarnationReadOf}。
29
+ */
30
+ import type { SessionStore } from "@sema-agent/core";
1
31
  /** 会话元数据的一次读:属主 + 化身(同一行、同一次读)。 */
2
32
  export interface SessionFace {
3
33
  /** `null` = 登记为无主的会话。 */
@@ -31,10 +61,24 @@ export declare class SessionIncarnationMissingError extends Error {
31
61
  constructor(sessionId: string);
32
62
  }
33
63
  /**
34
- * 店把一行原始列读成 {@link SessionFace} 的唯一口(三只店共用 —— 空值判据只写这一处)。
35
- * `incarnation` 不是非空字符串 ⇒ {@link SessionIncarnationMissingError}。
64
+ * 行上化身那一格的**唯一解码**(三只店的 `sessionFaceOf` 与 SQL 两店 wake 带出的会话元数据都经它 —— 空值判据只写这一处)。
65
+ * 不是非空字符串 ⇒ {@link SessionIncarnationMissingError}。
36
66
  */
67
+ export declare function incarnationFromCell(sessionId: string, cell: unknown): string;
68
+ /** 店把一行原始列读成 {@link SessionFace} 的唯一口(三只店共用;化身那一格经 {@link incarnationFromCell})。 */
37
69
  export declare function sessionFaceFromRow(sessionId: string, owner: string | null, incarnation: unknown): SessionFace;
70
+ /** 按 id 读住客(core `SessionStore.incarnationOf` 的形):`undefined` = 没有这只会话;读抛 = 读故障,不是答案。 */
71
+ export type IncarnationRead = (sessionId: string) => Promise<string | undefined>;
72
+ /**
73
+ * 「这只会话店能不能按 id 答住客」—— 全仓**只判这一处**:起服门(`assertWorkspaceSpaceReadable`)与 k8s 工作区空间的每 run 读
74
+ * (`createK8sRunEnvFactory`)读的是同一只返回值(装配第 5 段判一次)。缺席 = 店没有实装 `incarnationOf`。
75
+ */
76
+ export declare function incarnationReadOf(store: Pick<SessionStore, "incarnationOf"> | undefined): IncarnationRead | undefined;
77
+ /**
78
+ * 「这只会话店能不能按 id 答住客脸(属主 + 化身一次读)」—— 与 {@link incarnationReadOf} 同一处判法、同一个「能不能」:
79
+ * 空间缓存卷的回收用它一次拿两位(属主要与这次删除验过的那位相符,化身是空间键)。店没有这只读口 ⇒ `undefined`(回收不做,不响)。
80
+ */
81
+ export declare function sessionFaceReadOf(store: object | undefined): SessionFaceReader | undefined;
38
82
  /**
39
83
  * session-sync **整段换装**(replace)时化身的去留 —— 三只店共用的一句判据:换装删掉再插回的是**同一位住客**的那一行
40
84
  * (删除带属主门),所以行在、属主相同、化身非空 ⇒ 沿用;行不在(这是一次新建)或还是空的存量形 ⇒ 铸一只新的。
@@ -24,10 +24,21 @@ export class SessionIncarnationMissingError extends Error {
24
24
  this.name = "SessionIncarnationMissingError";
25
25
  }
26
26
  }
27
- export function sessionFaceFromRow(sessionId, owner, incarnation) {
28
- if (typeof incarnation !== "string" || incarnation === "")
27
+ export function incarnationFromCell(sessionId, cell) {
28
+ if (typeof cell !== "string" || cell === "")
29
29
  throw new SessionIncarnationMissingError(sessionId);
30
- return { owner, incarnation };
30
+ return cell;
31
+ }
32
+ export function sessionFaceFromRow(sessionId, owner, incarnation) {
33
+ return { owner, incarnation: incarnationFromCell(sessionId, incarnation) };
34
+ }
35
+ export function incarnationReadOf(store) {
36
+ const read = store?.incarnationOf;
37
+ return typeof read === "function" ? read.bind(store) : undefined;
38
+ }
39
+ export function sessionFaceReadOf(store) {
40
+ const read = store?.sessionFaceOf;
41
+ return typeof read === "function" ? read.bind(store) : undefined;
31
42
  }
32
43
  export function carriedIncarnation(prior, owner) {
33
44
  if (prior !== undefined && (prior.owner ?? null) === owner && typeof prior.incarnation === "string" && prior.incarnation !== "") {
@@ -5,11 +5,12 @@
5
5
  * importSession/planSync) so the three DB session adapters (local/pg/tidb) and security.ts can depend on these
6
6
  * symbols WITHOUT touching the orchestration module — session-sync.ts value-imports StoreBackend and type-imports
7
7
  * security's OwnerAwareSessionStore, so importing it from security.ts formed the repo's only true type cycle
8
- * (security ↔ session-sync; lens2 §B/§E). This file imports NOTHING from the repo (core types only) — a leaf,
9
- * same shape as session-sync-content.ts. session-sync.ts re-exports everything here, so existing import sites
8
+ * (security ↔ session-sync; lens2 §B/§E). This file imports nothing from the repo but TYPES from leaves (core types, and
9
+ * the session-face type from `session-incarnation.ts`, itself a leaf) — a leaf, same shape as session-sync-content.ts. session-sync.ts re-exports everything here, so existing import sites
10
10
  * keep working unchanged.
11
11
  */
12
12
  import type { SessionTreeEntry } from "@sema-agent/core";
13
+ import type { SessionFace } from "./session-incarnation.js";
13
14
  /**
14
15
  * P1d-β (PUSH streaming, sema-internal server/docs/DESIGN-session-sync.md §15 + the persistence synthesis) — a TWO-PHASE STAGED import
15
16
  * handle. The whole-bundle `importSession` (session-sync.ts) buffers every entry in one JSON body (server memory ∝ session size);
@@ -39,11 +40,17 @@ export interface StagingHandle {
39
40
  appendBatch(entries: SessionTreeEntry[]): Promise<void>;
40
41
  /** ATOMICALLY swap the staged rows into the real session id (ONE txn): in-txn re-classify (TOCTOU close) → purge dst
41
42
  * → row-key rename staging→real → session_meta LAST (the commit point). `owner` is re-stamped (§9). Throws
42
- * {@link SyncConflictError} on an unconsented fork/stale flip. Returns the committed relation. */
43
+ * {@link SyncConflictError} on an unconsented fork/stale flip. Returns the committed relation **and the settled session
44
+ * face** — the occupant (owner + incarnation) the commit left in the real session id, read INSIDE the same txn (the local
45
+ * store: the same hold of the session's write lock), never re-read afterwards. The post-commit replays conditioned on the
46
+ * occupant (the session row's two bits) write on exactly this face — a session deleted and re-created after the commit,
47
+ * even by the same owner, is a different occupant and is not written. A row without an incarnation is refused at that read
48
+ * (`sessionFaceFromRow`, the same law as every face read). */
43
49
  commit(owner: string | null, opts?: {
44
50
  resolution?: "overwrite-dst";
45
51
  }): Promise<{
46
52
  relation: SyncRelation["relation"];
53
+ face: SessionFace;
47
54
  }>;
48
55
  /** Drop the staging rows (Phase B aborted before commit) — DELETE the staging-id session_event rows. Idempotent. */
49
56
  abort(): Promise<void>;
@@ -66,12 +66,12 @@ export declare function skillToolListRefusal(skill: Record<string, unknown>): st
66
66
  /** [ref] 单条 agent 定义的形状裁决:null=合法;否则一句可 400 的英文错误(不带索引,调用方自己加)。
67
67
  * 白名单外键 fail-loud —— 与 resilience/limits 的「容忍未知键」相反:agents 是能力定义,静默丢字段 =
68
68
  * 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400 更危险,故整条拒。`allowTools` / `denyTools` 本门只判形与条数(`toolList`),
69
- * **名字归引擎一只判**(core 7.45.0 #1377 / #1385,开跑前,三来源同判):没有工具能是的拼法(带空白 / 括号形坏 / 不可见字符)、非字符串条目 ⇒ 整 run 拒
70
- * `config.agent.unknown_tool`;无人挂的名 ⇒ 不拒、经 `onError`(phase `"config"`)告一次(本仓汇点 `runner_config_advisory`);大小写孪生 / 退役名(`bash` / `BASH`)
71
- * ⇒ 开跑前拒 `config.agent.unknown_tool` —— 主车道的委派工具带 extraTools 工厂,本仓在同一处声明它的名册(core 7.46.0 `extraToolNames`;工厂与名册读同一批工具对象,
72
- * `capabilities/builtin-tools.ts` `createSubagentExtraTools`),引擎因此列得出子代工具池。两张表管子代手里的**全部**工具:委派池与 hands 内建(`Read` / `Edit` / `Write` /
73
- * `Bash` …,core #1430)—— `denyTools:["Write"]` ⇒ 子代没有 `Write`;`allowTools` 只点了调用方工具 ⇒ 子代一只 hands 工具都没有。hands 行之外的引擎工具
74
- * (`Monitor` 等)仍不受这两张表管(引擎已知限制)。`denyTools` 的 `Tool(content)` ⇒ 整只工具对该子代藏起并告。本仓不在受理口另做名字对照(那会是同一问的第二个判官)。
69
+ * **名字归引擎一只判**(core 7.49.0 #1623 / #1433 第二步起与根 run 的 `allowTools` / `excludeTools` 是同一只选择器,开跑前,三来源同判):没有任何工具答应的名字
70
+ * (无人挂的名、大小写孪生 / 退役名 `bash` / `BASH`、坏拼法、非字符串条目)⇒ 整 run 拒 `config.agent.unknown_tool` —— 主车道的委派工具带 extraTools 工厂,本仓在同一处
71
+ * 声明它的名册(core 7.46.0 `extraToolNames`;工厂与名册读同一批工具对象,`capabilities/builtin-tools.ts` `createSubagentExtraTools`),引擎因此列得出子代工具池。
72
+ * 两张表管子代可挂载的**全部**工具:委派池、hands 内建、引擎自己的工具(`Monitor` / `Artifact` / `Skill` / 记忆工具 …)与父的 MCP 挂载 ——
73
+ * `denyTools:["Monitor"]` ⇒ 子代没有 `Monitor`;`allowTools` 只点了调用方工具 ⇒ 子代只剩那几只与它这条腿的协议工具。`denyTools` 的 `Tool(content)` ⇒ 整只工具。
74
+ * 本仓不在受理口另做名字对照(那会是同一问的第二个判官)。
75
75
  * ⚠️ 键判断必须 Object.hasOwn:`in` 走原型链,JSON 里的 "toString"/"constructor"/"__proto__" 键会被
76
76
  * 误判为白名单字段,随后 SHAPES[k].ok 不是函数 ⇒ TypeError(fresh 500 / resume 砖;cross-review 复现)。 */
77
77
  export declare function taskAgentIssue(item: unknown): string | null;
@@ -130,6 +130,22 @@ export declare function retainBackgroundProcessesFromBody(raw: unknown, requireP
130
130
  * DEFENSIVE(resume 重放不过 HTTP 400 门):非数组 ⇒ undefined;项非非空字符串 ⇒ 逐项 DROP;空数组 ⇒
131
131
  * undefined(不挂键=core 缺省,byte-compat)。HTTP 门另有 fail-loud 400(提交面邻居姿势)。 */
132
132
  export declare function toolNameListFromBody(raw: unknown): string[] | undefined;
133
+ /** 引擎从哪一版起认 `TaskSpec.allowTools`(core 7.49.0 #1623,[ref])。 */
134
+ export declare const ALLOW_TOOLS_MIN_ENGINE = "7.49.0";
135
+ /**
136
+ * S-1448(core 7.49.0 #1623 / [ref])—— body.allowTools → `TaskSpec.allowTools`:引擎的**可用性选择器**,原样透传。
137
+ *
138
+ * 一条规则:这一 run 能用哪些工具由引擎一只运算答(`universe ∩ ceiling ∩ allow − deny`,prepare 对真实挂载的一切算一次)。
139
+ * 本仓**零读法** —— 不算补集、不逐项过滤(`[]` = 什么都不给,是合法值,不折成缺席;非串条目与未知名由引擎在 prepare 拒,
140
+ * `config.tool_face_control`)、不与 allow 层 `allowedTools`(免问)混词。受理门只判 wire 形(与 `excludeTools` / `deferTools` 同一只门)。
141
+ * 续跑重放同经此处:存量体逐字再投,坏形交引擎拒(收窄键丢了 = 放宽,所以不 DROP)。
142
+ *
143
+ * 装着的引擎不认该座(< {@link ALLOW_TOOLS_MIN_ENGINE})⇒ **响亮拒** 400 `request.field_invalid`(点名键、所需版本与装着的版本),
144
+ * 不静默丢。`engineVersion` 缺省读装着的 core(`CORE_ENGINE_VERSION`);测试传假低版。
145
+ */
146
+ export declare function allowToolsSpecFragment(raw: unknown, engineVersion?: string): {
147
+ allowTools?: string[];
148
+ };
133
149
  /** [ref]/[ref] — body.promptProfile → `TaskSpec.promptProfile`(core 1.328 R2 主件,CC LT(model_id)
134
150
  * 同构双形轴)。语义:纯呈现轴不碰策略(simple=CC 212 短形面缺省;classic=长形面,可按任务/模型试分)
135
151
  * ⇒ 无租户门(deferTools 同姿势);全委托树继承由 core 管。缺省不挂键(引擎缺省 simple,byte-compat)。
@@ -177,10 +193,10 @@ export declare function normalizeApproachNotice(raw: unknown): false | {
177
193
  * [ref]④ — body.limits 与 env 墙钟的合成规则(resolveSpec 的唯一 limits 装配点;run-local 无 body 车道不经此):
178
194
  * - `timeoutSec`:body 给了就用 body(caller 明确配速;已在 normalizeLimits 里被 TASK_TIMEOUT_MAX_SEC 封顶)。
179
195
  * body 缺席保持既有姿势 = tenancy 墙钟 + env `TASK_TIMEOUT_SEC` 只抬不降(taskWallClockSec 内 Math.max,
180
- * 误配低值不能砍 council 预算)。注意两条纪律不叠加:「只抬不降」是 env 对内建缺省墙的规则,body 是
196
+ * 误配低值不能砍 tenancy 基值)。注意两条纪律不叠加:「只抬不降」是 env 对内建缺省墙的规则,body 是
181
197
  * caller 的显式意图,直接生效(可低于 env 墙 —— 交互短任务要更紧的配速正是本件诉求)。
182
198
  * - `maxOutputTokens` / `maxTurns`:直透传(各自可选 env 上限已 clamp)。
183
199
  * 全空 ⇒ undefined(spec 不挂 limits 键,byte-compat)。
184
200
  */
185
- export declare function resolveTaskLimits(bodyLimits: unknown, caps: TaskLimitCaps, envTimeoutSec: number, requirePrincipal: boolean | undefined, big: boolean, warn?: (event: string, fields: Record<string, unknown>) => void): TaskSpec["limits"];
201
+ export declare function resolveTaskLimits(bodyLimits: unknown, caps: TaskLimitCaps, envTimeoutSec: number, requirePrincipal: boolean | undefined, warn?: (event: string, fields: Record<string, unknown>) => void): TaskSpec["limits"];
186
202
  //# sourceMappingURL=spec-fields.d.ts.map
@@ -1,6 +1,8 @@
1
1
  import { isThinkingLevel } from "@sema-agent/core";
2
2
  import { taskWallClockSec } from "./task-workflow.js";
3
3
  import { isAbsoluteHostPath } from "@sema-agent/settings-schema";
4
+ import { CORE_ENGINE_VERSION } from "./prompts-domain-validate.js";
5
+ import { createCodedHttpError } from "./security.js";
4
6
  import { allowLayerEntryCountIssue, ruleEntryListShapeOk } from "./rule-entry-list.js";
5
7
  export function normalizeSuggestNextPrompts(v) {
6
8
  if (v === true)
@@ -270,6 +272,23 @@ export function toolNameListFromBody(raw) {
270
272
  const names = raw.filter((v) => typeof v === "string" && v.length > 0);
271
273
  return names.length > 0 ? names : undefined;
272
274
  }
275
+ export const ALLOW_TOOLS_MIN_ENGINE = "7.49.0";
276
+ function engineAtLeast(version, floor) {
277
+ const parse = (v) => v.split(/[-+]/, 1)[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
278
+ const [a, b] = [parse(version), parse(floor)];
279
+ for (let i = 0; i < 3; i++)
280
+ if ((a[i] ?? 0) !== (b[i] ?? 0))
281
+ return (a[i] ?? 0) > (b[i] ?? 0);
282
+ return true;
283
+ }
284
+ export function allowToolsSpecFragment(raw, engineVersion = CORE_ENGINE_VERSION) {
285
+ if (raw === undefined)
286
+ return {};
287
+ if (!engineAtLeast(engineVersion, ALLOW_TOOLS_MIN_ENGINE)) {
288
+ throw createCodedHttpError(400, "request.field_invalid", `allowTools needs engine ${ALLOW_TOOLS_MIN_ENGINE} or later; this server runs engine ${engineVersion}, which would drop the list and leave every tool available — send the request without allowTools, or upgrade the server`);
289
+ }
290
+ return { allowTools: raw };
291
+ }
273
292
  export function promptProfileFromBody(raw) {
274
293
  return raw === "simple" || raw === "classic" ? raw : undefined;
275
294
  }
@@ -318,10 +337,10 @@ export function normalizeApproachNotice(raw) {
318
337
  const frac = (n) => typeof n === "number" && Number.isFinite(n) && n > 0 && n <= 1;
319
338
  return frac(a) && frac(b) && a <= b ? { at: [a, b] } : undefined;
320
339
  }
321
- export function resolveTaskLimits(bodyLimits, caps, envTimeoutSec, requirePrincipal, big, warn) {
340
+ export function resolveTaskLimits(bodyLimits, caps, envTimeoutSec, requirePrincipal, warn) {
322
341
  const dropped = retiredLimitKeysPresent(bodyLimits);
323
342
  const requested = normalizeLimits(bodyLimits, caps);
324
- const wallSec = taskWallClockSec(envTimeoutSec, requirePrincipal, big);
343
+ const wallSec = taskWallClockSec(envTimeoutSec, requirePrincipal);
325
344
  const maxWalltimeMs = requested?.maxWalltimeMs ?? (wallSec !== undefined ? wallSec * 1000 : undefined);
326
345
  if (dropped.length > 0)
327
346
  warn?.("task_limits_legacy_key_dropped", { droppedKeys: dropped, effectiveMaxWalltimeMs: maxWalltimeMs ?? null });
@@ -113,10 +113,12 @@ export declare function normalizeRetainSubagentSessions(raw: unknown): boolean |
113
113
  * - SINGLE-USER turnkey(`requirePrincipal !== true`)= the user IS the superadmin → **NO wall clock**
114
114
  * (undefined; core arms none — its separate wedge safety-net still catches a stuck tool call, so "no wall"
115
115
  * never means "hangs forever"). CC has no whole-task decapitation either.
116
- * - MULTI-TENANT: base 2400s (big tasks — council/debate/discuss — 3600s). (PAIR-REVIEW F-4 附带修:此行
117
- * 曾写 600s/1500s 与代码漂移——真值以下方 base 为准。)
116
+ * - MULTI-TENANT: base 2400s (one base: the 3600s big-task lane existed only for council/debate/discuss, which
117
+ * left with the engine's team runtime in 7.49.0).
118
118
  * - Explicit `TASK_TIMEOUT_SEC` wins in ANY tenancy (the operator's deliberate wall) and keeps its documented
119
- * raise-only floor (`Math.max` with the base, so a misconfigured low value cannot shrink a council budget).
119
+ * raise-only floor (`Math.max` with the base, so a misconfigured low value cannot shrink the tenancy base).
120
120
  */
121
- export declare function taskWallClockSec(envTimeoutSec: number, requirePrincipal: boolean | undefined, big: boolean): number | undefined;
121
+ /** 多租户(requirePrincipal)任务墙钟基值(秒)—— 唯一来源:`taskWallClockSec` 与 resolve-spec 的 `server.limits.wallClockBaseSec` 声明都读它(3600 s 大任务档已随团队编排面退役删除)。 */
122
+ export declare const TENANCY_WALL_CLOCK_BASE_SEC = 2400;
123
+ export declare function taskWallClockSec(envTimeoutSec: number, requirePrincipal: boolean | undefined): number | undefined;
122
124
  //# sourceMappingURL=task-workflow.d.ts.map
@@ -39,8 +39,9 @@ export function normalizeRetainSubagentSessions(raw) {
39
39
  return undefined;
40
40
  return { ...(ttlMs !== undefined ? { ttlMs } : {}), ...(max !== undefined ? { max } : {}) };
41
41
  }
42
- export function taskWallClockSec(envTimeoutSec, requirePrincipal, big) {
43
- const base = big ? 3600 : 2400;
42
+ export const TENANCY_WALL_CLOCK_BASE_SEC = 2400;
43
+ export function taskWallClockSec(envTimeoutSec, requirePrincipal) {
44
+ const base = TENANCY_WALL_CLOCK_BASE_SEC;
44
45
  if (envTimeoutSec > 0)
45
46
  return Math.max(envTimeoutSec, base);
46
47
  return requirePrincipal === true ? base : undefined;