@sema-agent/server 7.137.0 → 7.138.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 (209) hide show
  1. package/MIGRATION.md +328 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +67 -20
  5. package/dist/approval-card.d.ts +21 -14
  6. package/dist/approval-card.js +1 -0
  7. package/dist/audit.js +1 -0
  8. package/dist/background-agent-name.d.ts +5 -0
  9. package/dist/background-agent-name.js +5 -0
  10. package/dist/boot/budget-tracing.d.ts +1 -12
  11. package/dist/boot/deployment-mods.d.ts +46 -16
  12. package/dist/boot/deployment-mods.js +82 -6
  13. package/dist/boot/governance-seams.d.ts +10 -17
  14. package/dist/boot/governance-seams.js +5 -17
  15. package/dist/boot/leader.d.ts +3 -0
  16. package/dist/boot/leader.js +1 -0
  17. package/dist/boot/org-delivery.js +15 -2
  18. package/dist/boot/resolve-spec.d.ts +3 -0
  19. package/dist/boot/resolve-spec.js +23 -12
  20. package/dist/boot/retention-lane.d.ts +11 -5
  21. package/dist/boot/retention-lane.js +2 -2
  22. package/dist/boot/runner-deps.d.ts +7 -3
  23. package/dist/boot/runner-deps.js +1 -0
  24. package/dist/boot/stage-04-stores.d.ts +1 -12
  25. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  26. package/dist/boot/stage-05-execution-env.js +7 -4
  27. package/dist/boot/stage-06-runners.d.ts +3 -12
  28. package/dist/boot/stage-06-runners.js +5 -2
  29. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  30. package/dist/boot/stage-07-capability-layer.js +2 -2
  31. package/dist/boot/stage-08-reapers.d.ts +3 -12
  32. package/dist/boot/stage-09-leader.d.ts +3 -12
  33. package/dist/boot/stage-09-leader.js +2 -2
  34. package/dist/boot/stage-10-http-server.d.ts +3 -12
  35. package/dist/boot/stage-10-http-server.js +4 -2
  36. package/dist/boot/web-admin.d.ts +5 -2
  37. package/dist/boot/web-admin.js +3 -3
  38. package/dist/brain.js +5 -5
  39. package/dist/budget.d.ts +17 -15
  40. package/dist/budget.js +2 -2
  41. package/dist/capabilities/mcp-probe.d.ts +36 -11
  42. package/dist/capabilities/mcp-probe.js +44 -5
  43. package/dist/capability-facts.d.ts +6 -0
  44. package/dist/capability-facts.js +5 -0
  45. package/dist/checkpoint-identity.d.ts +13 -6
  46. package/dist/config-catalog.js +7 -1
  47. package/dist/config-types.d.ts +15 -7
  48. package/dist/config.js +8 -5
  49. package/dist/deployment-shape.d.ts +6 -6
  50. package/dist/gate-settlement.d.ts +71 -0
  51. package/dist/gate-settlement.js +40 -0
  52. package/dist/hooks/classic-stations.d.ts +1 -0
  53. package/dist/hooks/classic-stations.js +1 -0
  54. package/dist/hooks/command-spawn.d.ts +2 -2
  55. package/dist/hooks/hook-runner.js +14 -14
  56. package/dist/http/admission.js +4 -2
  57. package/dist/http/dispatch.d.ts +4 -0
  58. package/dist/http/dispatch.js +3 -0
  59. package/dist/http/durable-run-leg.js +4 -2
  60. package/dist/http/memory-compliance-failure-reply.js +3 -0
  61. package/dist/http/resume-legs.d.ts +5 -11
  62. package/dist/http/resume-legs.js +136 -91
  63. package/dist/http/route-ctx.d.ts +30 -51
  64. package/dist/http/route-ctx.js +2 -5
  65. package/dist/http/routes/a2a-serve.js +10 -4
  66. package/dist/http/routes/agents-roster.d.ts +1 -1
  67. package/dist/http/routes/agents-roster.js +5 -1
  68. package/dist/http/routes/approvals-assistant.js +6 -6
  69. package/dist/http/routes/capabilities.js +12 -10
  70. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  71. package/dist/http/routes/mcp-reconnect.js +7 -5
  72. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  73. package/dist/http/routes/memory-origin.d.ts +1 -1
  74. package/dist/http/routes/rules.js +3 -3
  75. package/dist/http/routes/runs.js +11 -7
  76. package/dist/http/routes/sessions.js +6 -4
  77. package/dist/http/routes/side-query.js +2 -3
  78. package/dist/http/routes/tasks.js +792 -780
  79. package/dist/http/server.d.ts +31 -25
  80. package/dist/http/wire-types.d.ts +14 -6
  81. package/dist/identity-source.d.ts +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +1 -0
  84. package/dist/leader/wire.d.ts +7 -0
  85. package/dist/leader/wire.js +5 -4
  86. package/dist/mcp-assertion-header.d.ts +51 -40
  87. package/dist/mcp-assertion-header.js +10 -11
  88. package/dist/mcp-authorization-face.d.ts +27 -0
  89. package/dist/mcp-authorization-face.js +15 -0
  90. package/dist/memory-operator-faces.d.ts +13 -4
  91. package/dist/memory-operator-faces.js +1 -17
  92. package/dist/memory-sync-client.js +72 -57
  93. package/dist/memory-sync.d.ts +1 -1
  94. package/dist/model-route-gap.d.ts +6 -22
  95. package/dist/model-route-gap.js +7 -5
  96. package/dist/mods-declaration.d.ts +110 -0
  97. package/dist/mods-declaration.js +109 -0
  98. package/dist/observability/err-text.d.ts +5 -0
  99. package/dist/observability/err-text.js +9 -0
  100. package/dist/observability/fail-open.d.ts +5 -1
  101. package/dist/observability/fail-open.js +5 -1
  102. package/dist/observability/secret-env-scrub.d.ts +1 -1
  103. package/dist/observability/secret-env-scrub.js +1 -1
  104. package/dist/parked-decide.d.ts +15 -2
  105. package/dist/parked-decide.js +14 -8
  106. package/dist/peer-seat.d.ts +150 -0
  107. package/dist/peer-seat.js +239 -0
  108. package/dist/permission-rule-vocab.d.ts +7 -7
  109. package/dist/permission-rule-vocab.js +3 -3
  110. package/dist/plain-string-record.d.ts +38 -0
  111. package/dist/plain-string-record.js +38 -0
  112. package/dist/plugins/background-agent-store-sql.js +6 -3
  113. package/dist/plugins/caching-session-store.d.ts +5 -0
  114. package/dist/plugins/caching-session-store.js +6 -0
  115. package/dist/plugins/checkpoint-store-sql.d.ts +15 -20
  116. package/dist/plugins/checkpoint-store-sql.js +8 -8
  117. package/dist/plugins/file-history-store-sql.js +3 -2
  118. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  119. package/dist/plugins/k8s-pod-reaper.js +3 -3
  120. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  121. package/dist/plugins/local-checkpoint-store.js +0 -1
  122. package/dist/plugins/local-session-store.d.ts +66 -27
  123. package/dist/plugins/local-session-store.js +60 -28
  124. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  125. package/dist/plugins/mailbox-store-sql.js +103 -15
  126. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  127. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  128. package/dist/plugins/one-time-migrations.d.ts +6 -3
  129. package/dist/plugins/one-time-migrations.js +3 -4
  130. package/dist/plugins/permission-rule-store-sql.js +21 -13
  131. package/dist/plugins/pg-session-storage.d.ts +10 -2
  132. package/dist/plugins/pg-session-storage.js +18 -5
  133. package/dist/plugins/remote-env-adb.d.ts +3 -1
  134. package/dist/plugins/remote-env-adb.js +1 -1
  135. package/dist/plugins/remote-env-device.d.ts +3 -1
  136. package/dist/plugins/remote-env-device.js +1 -1
  137. package/dist/plugins/remote-env-e2b.d.ts +2 -0
  138. package/dist/plugins/remote-env-e2b.js +1 -1
  139. package/dist/plugins/remote-env-host.d.ts +3 -1
  140. package/dist/plugins/remote-env-host.js +1 -1
  141. package/dist/plugins/remote-env-k8s.d.ts +3 -1
  142. package/dist/plugins/remote-env-k8s.js +1 -1
  143. package/dist/plugins/remote-env-local-docker.d.ts +2 -0
  144. package/dist/plugins/remote-env-local-docker.js +1 -1
  145. package/dist/plugins/remote-env-ssh.d.ts +3 -1
  146. package/dist/plugins/remote-env-ssh.js +1 -1
  147. package/dist/plugins/retention-store-sql.d.ts +14 -2
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/session-fork.d.ts +10 -13
  150. package/dist/plugins/session-fork.js +5 -25
  151. package/dist/plugins/session-ownership.d.ts +7 -0
  152. package/dist/plugins/session-ownership.js +5 -0
  153. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  154. package/dist/plugins/session-workspace-cell.js +38 -0
  155. package/dist/plugins/store-backend.js +7 -7
  156. package/dist/plugins/tidb-pool.js +6 -0
  157. package/dist/plugins/tidb-session-store.d.ts +17 -2
  158. package/dist/plugins/tidb-session-store.js +16 -5
  159. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  160. package/dist/plugins/tool-result-store-sql.js +8 -7
  161. package/dist/plugins/worktree-isolation.d.ts +3 -2
  162. package/dist/plugins/worktree-isolation.js +1 -7
  163. package/dist/project-identity.d.ts +1 -14
  164. package/dist/project-identity.js +0 -4
  165. package/dist/rules-consent.js +6 -4
  166. package/dist/run-local.js +12 -6
  167. package/dist/runs.d.ts +7 -1
  168. package/dist/runs.js +6 -1
  169. package/dist/sandbox-git-plane.d.ts +2 -2
  170. package/dist/server-secret-env.d.ts +6 -3
  171. package/dist/server-secret-env.js +4 -2
  172. package/dist/session-incarnation.d.ts +4 -5
  173. package/dist/session-incarnation.js +2 -2
  174. package/dist/session-workspace.d.ts +130 -0
  175. package/dist/session-workspace.js +83 -0
  176. package/dist/spec-fields.d.ts +1 -1
  177. package/dist/standing-answer-grant.d.ts +2 -13
  178. package/dist/standing-answer-grant.js +0 -7
  179. package/dist/task-a2a.d.ts +44 -14
  180. package/dist/task-a2a.js +46 -20
  181. package/dist/task-mcp.d.ts +33 -20
  182. package/dist/task-mcp.js +46 -22
  183. package/dist/tool-approval.d.ts +32 -8
  184. package/dist/tool-approval.js +49 -12
  185. package/dist/trace/core-keyset-guard.d.ts +33 -18
  186. package/dist/trace/engine-notice-wire.d.ts +1 -1
  187. package/dist/trace/engine-notice-wire.js +2 -0
  188. package/dist/trace/exit-seat.d.ts +20 -1
  189. package/dist/trace/exit-seat.js +3 -0
  190. package/dist/trace/extension-call-face.d.ts +33 -0
  191. package/dist/trace/extension-call-face.js +39 -0
  192. package/dist/trace/project.d.ts +25 -7
  193. package/dist/trace/project.js +29 -3
  194. package/dist/trace/redact.js +42 -13
  195. package/dist/trace/self-minted-wire.d.ts +129 -10
  196. package/dist/trace/self-minted-wire.js +62 -3
  197. package/dist/trace/wire-projection-faces.d.ts +6 -6
  198. package/dist/trace/wire-projection-faces.js +6 -5
  199. package/dist/user-authored-text.d.ts +2 -2
  200. package/dist/user-authored-text.js +16 -1
  201. package/dist/web-admin-client/center-request.d.ts +3 -2
  202. package/dist/web-admin-client/center-request.js +2 -3
  203. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  204. package/dist/web-admin-client/skills-mcp.js +31 -18
  205. package/dist/web-admin-client/types.d.ts +5 -0
  206. package/package.json +3 -3
  207. package/wire-changes/server@7.138.0.json +446 -0
  208. package/dist/http/mcp-remote-text.d.ts +0 -5
  209. package/dist/http/mcp-remote-text.js +0 -7
@@ -31,7 +31,7 @@ export function withWorktreeIsolation(baseFactory, opts) {
31
31
  return baseFactory(ctx);
32
32
  }
33
33
  let innerDestroy;
34
- const { env, worktreeDir } = await serialize(() => addWorktree(opts.baseEnvForGit, {
34
+ const { env, worktreeDir, baseSha } = await serialize(() => addWorktree(opts.baseEnvForGit, {
35
35
  repoRoot,
36
36
  sessionId: ctx.sessionId,
37
37
  rootEnvAt: async (dir) => {
@@ -43,12 +43,6 @@ export function withWorktreeIsolation(baseFactory, opts) {
43
43
  },
44
44
  ...(opts.commit ? { commit: opts.commit } : {}),
45
45
  }));
46
- let baseSha;
47
- {
48
- const r = await opts.baseEnvForGit.exec("git rev-parse HEAD", { cwd: worktreeDir });
49
- if (r.ok && r.value.exitCode === 0)
50
- baseSha = r.value.stdout.trim();
51
- }
52
46
  const isChanged = async () => {
53
47
  const status = await opts.baseEnvForGit.exec("git status --porcelain", { cwd: worktreeDir });
54
48
  if (!status.ok || status.value.exitCode !== 0)
@@ -4,19 +4,6 @@ import { type ProjectLayerIdentity } from "./project-layer-key.js";
4
4
  export type { MemoryProjectKey };
5
5
  /** 本仓会说的缺席词。`not_git` 刻意排除:裁 D 起非 git 目录按目录键控,不是缺席(core 闭集仍收它,本仓不铸)。 */
6
6
  export type ProjectIdentityAbsentReason = Exclude<MemoryProjectKeyAbsentReason, "not_git">;
7
- /** 一个任务根的项目身份:canonical 项目根,或说得出理由的缺席。 */
8
- export type ProjectIdentity = {
9
- readonly root: string;
10
- } | {
11
- readonly absent: ProjectIdentityAbsentReason;
12
- };
13
- /**
14
- * 任务根 → 项目身份([ref] 四例;见文件头)。纯文件系统读,零子进程。
15
- * `taskRoot` 必须是**本进程读得到**的路径(调用方按车道判:根在沙箱/设备盘上就别调,直接给 `unreadable`)。
16
- *
17
- * 一次向上走,规则三条按优先序:最近的 `.git`(① ② ③)> 链上最近的裸仓布局(④ bare)> 非 git 按目录键控(④)。
18
- */
19
- export declare function resolveProjectIdentity(taskRoot: string): ProjectIdentity;
20
7
  /**
21
8
  * 退役 `repo:` 键的**唯一**铸点([ref] ①:`repo:` + sha256 hex 前 24;`root` 为 realpath 字符串,不加尾斜杠、不折大小写)。
22
9
  * [ref] 起它只是一次性迁移的来源(`memory-project-layer.ts` 把这一层的条目折进新键;窗见 `plugins/one-time-migrations.ts` 登记表)。
@@ -39,7 +26,7 @@ export declare function assertProjectLayerOwnable(scope: string, envName: string
39
26
  export declare function normalizeGitRemoteUrl(url: string): string | undefined;
40
27
  /**
41
28
  * 任务根 → 项目层身份([ref]):当前档(`r` 优先,没有 origin 退 `p`)+ 本 checkout 的低档(`r` 档时 = 它的 `p` 档)。
42
- * 缺席形与 {@link resolveProjectIdentity} 同;git 配置在而读不了 ⇒ `unreadable`。
29
+ * 缺席形 = 找根的缺席词(`bare` / `unreadable`);git 配置在而读不了 ⇒ `unreadable`。
43
30
  */
44
31
  export declare function projectLayerIdentityOf(taskRoot: string): {
45
32
  readonly root: string;
@@ -87,10 +87,6 @@ function canonicalTaskRoot(taskRoot) {
87
87
  p = parent;
88
88
  }
89
89
  }
90
- export function resolveProjectIdentity(taskRoot) {
91
- const located = locateProject(taskRoot);
92
- return "root" in located ? { root: located.root } : located;
93
- }
94
90
  function locateProject(taskRoot) {
95
91
  const root = canonicalTaskRoot(taskRoot);
96
92
  if (root === "unreadable")
@@ -5,7 +5,7 @@ import { createLogger } from "./observability/logger.js";
5
5
  import { ABSOLUTE_HOST_PATH_MAX, hasControlOrBidi } from "@sema-agent/settings-schema";
6
6
  import { isDirectWritableRuleBehavior } from "./permission-rule-vocab.js";
7
7
  import { hasBidiControls } from "./text-bidi.js";
8
- import { confirmRuleApproval, RULE_BEHAVIORS, parseRuleText, ruleReachesProgramRun, ruleToolGrammarOf, precheckEditedRuleText, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, addPersistedRule, removePersistedRule, isValidDurableScope, effectiveOrThrow, readRowBehavior, readRuleOwner, } from "@sema-agent/core";
8
+ import { confirmRuleApproval, RULE_BEHAVIORS, parseRuleText, ruleReachesProgramRun, precheckEditedRuleText, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, addPersistedRule, removePersistedRule, isValidDurableScope, effectiveOrThrow, readRowBehavior, readRuleOwner, } from "@sema-agent/core";
9
9
  import { buildRulePayloadHash } from "./plugins/permission-rule-store-sql.js";
10
10
  export const RULE_IMPORT_TICKET_TTL_MS = 10 * 60_000;
11
11
  export const RULE_IMPORT_RETRY_AFTER_SEC = 2;
@@ -87,7 +87,7 @@ function storeForBucket(provider, ref) {
87
87
  }
88
88
  }
89
89
  }
90
- function isSupportedRuleMatch(match) {
90
+ function isCommandRuleMatch(match) {
91
91
  switch (match) {
92
92
  case "exact":
93
93
  case "prefix":
@@ -104,6 +104,8 @@ function isSupportedRuleMatch(match) {
104
104
  case "host":
105
105
  case "subdomain":
106
106
  return false;
107
+ case "tool":
108
+ return false;
107
109
  default: {
108
110
  const unknown = match;
109
111
  void unknown;
@@ -205,7 +207,7 @@ function screenLocalImportRow(row) {
205
207
  const written = parseRuleText(row.rule, row.behavior);
206
208
  if ("reject" in written)
207
209
  return { reason: "rule_rejected", detail: `${written.reject.code}: ${written.reject.message}` };
208
- if (ruleToolGrammarOf(written.rule.tool) !== "command")
210
+ if (!isCommandRuleMatch(written.rule.match))
209
211
  return consistent(written.rule);
210
212
  if (written.rule.behavior !== "allow") {
211
213
  if (!ruleReachesProgramRun(written.rule, row.command)) {
@@ -354,7 +356,7 @@ export function createRuleConsentLane(stores, opts) {
354
356
  if ("reject" in canonical) {
355
357
  return { ok: false, reason: "edit-rejected", detail: `the canonical spelling the engine minted does not re-parse: ${canonical.reject.message}` };
356
358
  }
357
- if (!isSupportedRuleMatch(canonical.rule.match)) {
359
+ if (!isCommandRuleMatch(canonical.rule.match)) {
358
360
  return { ok: false, reason: "edit-rejected", detail: `the edited rule resolves to an unsupported match form (${String(canonical.rule.match)})` };
359
361
  }
360
362
  ticket = minted.ticket;
package/dist/run-local.js CHANGED
@@ -45,14 +45,16 @@ import { createMemoryEngineIncidentSink } from "./memory-incident-sink.js";
45
45
  import { fileMemoryEngineRoot, memoryEngineBackendFor, memorySpecForRequest } from "./memory-scope.js";
46
46
  import { createMemoryProjectLayers } from "./memory-project-layer.js";
47
47
  import { assertProjectLayerOwnable } from "./project-identity.js";
48
+ import { declaredCredential } from "./model-route-gap.js";
48
49
  import { judgeServedModelRoute } from "./model-route-endpoint.js";
49
50
  import { createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync-client.js";
50
51
  import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
51
52
  import { createSharedRunnerDeps } from "./boot/runner-deps.js";
53
+ import { createPeerDesk } from "./peer-seat.js";
52
54
  import { createGovernanceSeams } from "./boot/governance-seams.js";
53
55
  import { assertRetentionLaneWirable } from "./boot/retention-lane.js";
54
56
  import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
55
- import { assertDeploymentModsAdmitted, createDeploymentMods } from "./boot/deployment-mods.js";
57
+ import { assembleDeploymentMods } from "./boot/deployment-mods.js";
56
58
  import { createKeyResolver } from "./key-resolver.js";
57
59
  import { createLogger } from "./observability/logger.js";
58
60
  import { fatalExit } from "./boot/fatal-exit.js";
@@ -121,7 +123,7 @@ export function missingGatewayRequirements(config) {
121
123
  if (config.gatewayBaseUrl === undefined)
122
124
  return missing;
123
125
  const perModelEnvs = Object.values(config.modelApiKeyEnv ?? {});
124
- const haveGatewayKey = !!config.gatewayApiKey;
126
+ const haveGatewayKey = declaredCredential(config.gatewayApiKey) !== undefined;
125
127
  const catalogModelNames = new Set(Object.values(config.models ?? {}).map((m) => m.name));
126
128
  const allModelsHaveOwnKey = catalogModelNames.size > 0 &&
127
129
  [...catalogModelNames].every((n) => {
@@ -164,6 +166,7 @@ export const NO_TTY_ASK_CLASS_ROW = {
164
166
  shell_floor: { remedy: "shell_gate", autoBudgetReleases: false },
165
167
  target_unresolved: { remedy: "terminal_only", autoBudgetReleases: false },
166
168
  probe: { remedy: "terminal_only", autoBudgetReleases: false },
169
+ write_protection_on_disk: { remedy: "terminal_only", autoBudgetReleases: false },
167
170
  write_protection: { remedy: "terminal_only", autoBudgetReleases: false },
168
171
  tool_marks_mcp: { remedy: "terminal_only", autoBudgetReleases: false },
169
172
  tool_marks_a2a: { remedy: "terminal_only", autoBudgetReleases: false },
@@ -363,6 +366,8 @@ export async function runLocal(argv, deps = {}) {
363
366
  return 2;
364
367
  }
365
368
  const configProvider = createConfigProvider(providerInput);
369
+ const peerDesk = createPeerDesk({ log: logger });
370
+ const rosterLease = peerDesk.lease("roster");
366
371
  let effective;
367
372
  let candidatePrompts;
368
373
  let applying = false;
@@ -387,9 +392,9 @@ export async function runLocal(argv, deps = {}) {
387
392
  effective = r.effective;
388
393
  candidatePrompts = verdict.prompts;
389
394
  if (effective?.mcp)
390
- config.mcpServers = resolveMcpServers(effective.mcp, logger);
395
+ config.mcpServers = resolveMcpServers(effective.mcp, peerDesk, rosterLease, logger);
391
396
  if (effective?.a2a)
392
- config.a2aPeers = resolveA2aPeers(effective.a2a, logger);
397
+ config.a2aPeers = resolveA2aPeers(effective.a2a, peerDesk, rosterLease, logger);
393
398
  applyCenterReadFace(config, r.effective, logger, { phase: "boot" });
394
399
  logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version });
395
400
  }
@@ -576,9 +581,9 @@ export async function runLocal(argv, deps = {}) {
576
581
  { name: "checkpointStore", store: fileBackend.checkpointStore },
577
582
  { name: "toolResultStore", store: fileBackend.toolResultStore },
578
583
  ],
584
+ retentionExecutor: null,
579
585
  });
580
- const deploymentMods = createDeploymentMods({ metrics, brandIdentity: true });
581
- await assertDeploymentModsAdmitted(deploymentMods);
586
+ const { registrations: deploymentMods } = await assembleDeploymentMods({ metrics, brandIdentity: true, declaration: config.modsDeclaration, logger });
582
587
  const sharedRunnerDeps = createSharedRunnerDeps({
583
588
  metrics,
584
589
  config,
@@ -603,6 +608,7 @@ export async function runLocal(argv, deps = {}) {
603
608
  orgMemoryAdmission,
604
609
  sharedMemoryStores: undefined,
605
610
  mcpRevocations: undefined,
611
+ peerDesk,
606
612
  });
607
613
  const runnerDeps = {
608
614
  ...sharedRunnerDeps,
package/dist/runs.d.ts CHANGED
@@ -11,6 +11,7 @@ import { type ApprovalFrameFamily, type ToolApprovalCoordinator } from "./tool-a
11
11
  import { type FleetRunPublisher } from "./fleet/fleet-bus.js";
12
12
  import { type WorkflowCompletionInbox } from "./orchestration/workflow-completion-inbox.js";
13
13
  import type { RunAcceptance } from "./run-acceptance.js";
14
+ import type { SessionWorkspaceRecorder } from "./session-workspace.js";
14
15
  import type { VerifyRoundsSpec } from "./http/verify-rounds.js";
15
16
  import type { UserInputLedger } from "./user-input-ledger.js";
16
17
  /**
@@ -374,7 +375,12 @@ userInputLedger?: UserInputLedger,
374
375
  * 这次提交的受理座(`src/run-acceptance.ts`):标题器只在引擎受理之后起。普通流形逐帧喂;只回结果的 verify / cascade 形(以及终局写口的
375
376
  * 重试腿)终局落行之后收尾一次。缺席 ⇒ 不起标题(起标题此前由路由层在建行时做)。
376
377
  */
377
- acceptance?: RunAcceptance): Promise<void>;
378
+ acceptance?: RunAcceptance,
379
+ /**
380
+ * 会话行的位置与项目那一格的记录座(`session-workspace.ts`;唯一写者):普通流形逐帧喂(这条腿自己的 `workspace_changed` 排一次写),
381
+ * `done` 时等写落定再落终局。只回结果的 verify / cascade 形宿主手里没有流 ⇒ 不记。缺席 ⇒ 不记行(与本参数存在之前逐字相同)。
382
+ */
383
+ workspaceRecorder?: SessionWorkspaceRecorder): Promise<void>;
378
384
  /** How often a running instance refreshes its run's updated_at (liveness, independent of events).
379
385
  * Must stay strictly below `runStaleSec` (asserted at startup AND on every hot config candidate) or the
380
386
  * reaper would race live runs. [ref]:数值的单源是 `config-invariants.ts`(那条不变量的属主),两个消费面
package/dist/runs.js CHANGED
@@ -84,6 +84,10 @@ const PREPARE_REFUSAL_HTTP_STATUS = {
84
84
  "config.legacy_tool_name": 400,
85
85
  "config.memory_capture_unsupported": 409,
86
86
  "prompt.assembled_role_refused": 400,
87
+ "config.project_id_invalid": 400,
88
+ "config.project_identity_mismatch": 409,
89
+ "config.memory_project_marker": 409,
90
+ "config.memory_project_marker_nested": 409,
87
91
  };
88
92
  export function resumeAtHttpStatus(result) {
89
93
  const code = failureCodeOf(result.terminal);
@@ -253,7 +257,7 @@ export async function graftParkToolCallId(result, cs) {
253
257
  const id = await parkToolCallId(result, cs);
254
258
  return id === undefined ? result : { ...result, toolCallId: id };
255
259
  }
256
- export async function runInBackground(runner, spec, runStore, taskId, metrics, principal, verify, cascade, instrumentDegenerate, planCacheProbe, persistThinking = false, inflight, preemptable, steerable, modelUsage, elicitation, elicitOwner, captureTurnAnchor, fleetPublisher, captureUserMessageAnchor, question, workflowCompletionInbox, subagentSteer, completionDiagLog, sendUserFile, promptManifests, approval, parkCheckpointReader, pendingEngineNotices, trustedInternals, userInputLedger, acceptance) {
260
+ export async function runInBackground(runner, spec, runStore, taskId, metrics, principal, verify, cascade, instrumentDegenerate, planCacheProbe, persistThinking = false, inflight, preemptable, steerable, modelUsage, elicitation, elicitOwner, captureTurnAnchor, fleetPublisher, captureUserMessageAnchor, question, workflowCompletionInbox, subagentSteer, completionDiagLog, sendUserFile, promptManifests, approval, parkCheckpointReader, pendingEngineNotices, trustedInternals, userInputLedger, acceptance, workspaceRecorder) {
257
261
  const startedAt = Date.now();
258
262
  metrics?.addGauge("runs_active", 1);
259
263
  fleetPublisher?.onStart();
@@ -453,6 +457,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
453
457
  fleetPublisher?.onEvent(ev);
454
458
  await userInputLedger?.observeLegEvent(stream, ev);
455
459
  acceptance?.observe(ev);
460
+ await workspaceRecorder?.observe(ev);
456
461
  await flushPromptManifest();
457
462
  switch (ev.type) {
458
463
  case "text_delta":
@@ -52,8 +52,8 @@ export declare function sandboxGitPlanePremisesOf(config: ServiceConfig, reading
52
52
  }): SandboxGitPlanePremises;
53
53
  /**
54
54
  * Wiring point — the effective `sandboxGit` block (the origins this deployment routes through the broker, canonical form, ≤ 16). Its shape
55
- * and its canonical-form judge belong to the settings-schema contract package (14.7.0); until that release is picked up there is no block to
56
- * read ⇒ `null` (the plane stays off, `no_origins`). Replaced by the package reader on pickup — no local copy of the shape.
55
+ * and its canonical-form judge belong to the settings-schema contract package (`SandboxGitConfig`, 14.7.0 — picked up: the block rides the effective
56
+ * wire verbatim); this reader is not wired to it yet ⇒ `null` (the plane stays off, `no_origins`). To be replaced by the package reader — no local copy of the shape.
57
57
  */
58
58
  export declare function readSandboxGitConfig(_config: ServiceConfig): {
59
59
  readonly origins: readonly string[];
@@ -13,7 +13,7 @@
13
13
  * 剥掉的键经一个登记过的 `SecretEnvScrubSite` 留痕(现场按**腿**命名);git 腿再叠 git 自己的两件({@link gitSubprocessEnv})。射程 = **每一条**宿主腿:
14
14
  * src 里每一只值引 `node:child_process` 的文件都是登记腿、其中每一次起子进程都显式交组合出的 env(普查门在 `test/secret-env-scrub-host-legs.test.ts`)。
15
15
  * 腿的继承词是调用点的事实,按「子进程里跑的是谁选的代码」定:模型命令(host shell,交任务自己的词)/ 仓里的钩子与凭据助手(三条 git 腿)/ 执行仓代码的
16
- * 语言服务器 / 配方脚本 ⇒ 形状剥 `"scrub"`;请求带来的 settings hooks 命令 ⇒ 严格名单(引擎的 `[names]` 臂);部署方装在宿主上的 CLI(`docker` / `adb`,命令本身
16
+ * 语言服务器 / 配方脚本 ⇒ 形状剥 `"scrub"`;请求带来的 settings hooks 命令 ⇒ 严格名单(十二名基础名单,`[names]` 臂;请求的继承词只给工具 shell、对 hook 不作用)、请求的有效环境层作授予叠在继承结果之上(引擎的 `hostChildEnv`);部署方装在宿主上的 CLI(`docker` / `adb`,命令本身
17
17
  * 在容器 / 设备里跑、那边的 env 由 `-e` 显式给)⇒ `"all"` —— 只扣本服务自有凭据,CLI 与它的凭据助手 / 插件要读的生态变量照交(形状剥会剥
18
18
  * `ADB_VENDOR_KEYS` / `AWS_SECRET_ACCESS_KEY` 一类,实测)。三个继承词只在引擎一处判:
19
19
  * · `"all"` = 整份环境 **减** 本进程声明的自有凭据名;`"scrub"`(缺省)= 形状剥 ∪ 该名单;`[names]` = 严格白名单,点到名单中的名 ⇒ 引擎响亮拒
@@ -94,18 +94,21 @@ export declare function ownCredentialsWithheld(): boolean;
94
94
  export declare function ownCredentialValuesWithheld(): boolean;
95
95
  /** 测试缝:撤回声明(引擎侧重声明为空表)、忘掉两份声明镜像、点名记账与活配置;生产代码从不调它。 */
96
96
  export declare function __resetServerWithheldEnvForTest(): void;
97
- /** 一条宿主腿要什么:现场(留痕)、继承策略(缺省 `"scrub"`;按头注「腿的继承词」一条由调用点定)、部署方点名放回的键。 */
97
+ /** 一条宿主腿要什么:现场(留痕)、继承策略(缺省 `"scrub"`;按头注「腿的继承词」一条由调用点定)、部署方点名放回的键、调用方显式交的授予(按序叠在继承之上,后者赢)。 */
98
98
  export interface HostSubprocessEnvRequest {
99
99
  readonly site: SecretEnvScrubSite;
100
100
  readonly policy?: InheritEnvPolicy | undefined;
101
101
  readonly allow: readonly string[];
102
+ /** 调用方显式交给子进程的变量(今天只有 settings hooks:有效环境层 = 请求 `settings.env` + hook 自己的变量)。引擎判形:名字空 / 带 `=` / NUL / 孤代理项、
103
+ * 值不是串或带 NUL / 孤代理项 ⇒ 组合**抛**,拒句点名授予与变量(不带值、不回显坏名);调用点按「起不来」处置。 */
104
+ readonly grants?: readonly Readonly<Record<string, string>>[] | undefined;
102
105
  }
103
106
  /**
104
107
  * 🔴 **宿主子进程 env 的唯一组合**:{@link composeHostSubprocessEnv} + 留痕。**没有按原样交给子进程**的名字(剥掉的)经 `site` 留痕;点名放回的键取回源值,
105
108
  * 不是剥除,不计数、不披露。源 env 恒为本进程 env(引擎只读 `process.env`,本仓没有第二个源)。
106
109
  *
107
110
  * 九条宿主腿都只经它(直接或经 {@link gitSubprocessEnv}):host shell(三个继承词原样交给 `policy`)、project-memory / center-plugins / leader 三条 git 腿、
108
- * 本地语言服务器、bake-runner 的每一只子进程组、local-docker 与 adb 两条 CLI 腿(`"all"`)、settings hooks 的命令腿(严格名单)。`allow` 只在**有部署级出路**的现场非空(今天 = `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW`,
111
+ * 本地语言服务器、bake-runner 的每一只子进程组、local-docker 与 adb 两条 CLI 腿(`"all"`)、settings hooks 的命令腿(十二名严格名单 + 有效环境层作授予)。`allow` 只在**有部署级出路**的现场非空(今天 = `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW`,
109
112
  * 它们的现场在 `SECRET_ENV_SCRUB_SITES` 里登记了 `remedy`,披露行因此是 warn + 放行名单 + 出路句);其余现场传 `[]`。
110
113
  * 返回 `NodeJS.ProcessEnv` 形(值可能是 `undefined` 的键本来就不会进子进程);调用方显式要交的键(`LC_ALL`、`cfg.env` …)叠在它之上。
111
114
  * 调用时机 = 每次要交 env 时现算一次(`process.env` 在本仓是 boot 期输入,各次同值;计数因此是「每组合一次」)。
@@ -1,4 +1,4 @@
1
- import { declareWithheldEnv, inheritedHostEnv } from "@sema-agent/core";
1
+ import { declareWithheldEnv, hostChildEnv, inheritedHostEnv } from "@sema-agent/core";
2
2
  import { CONFIG_CATALOG } from "./config-catalog.js";
3
3
  import { ENV_NAME_ALLOWLIST_KNOB_NAMES } from "./env-name-allowlist-knobs.js";
4
4
  import { recordSecretEnvScrub } from "./observability/secret-env-scrub.js";
@@ -84,7 +84,9 @@ export function hostSubprocessEnv(o) {
84
84
  }
85
85
  export function composeHostSubprocessEnv(o, findings = []) {
86
86
  const putBack = o.allow.length > 0 ? inheritedHostEnv([...o.allow], findings) : {};
87
- return { ...inheritedHostEnv(o.policy ?? "scrub", findings), ...putBack };
87
+ const child = hostChildEnv(o.policy ?? "scrub", putBack, ...(o.grants ?? []));
88
+ findings.push(...child.withheld);
89
+ return child.env;
88
90
  }
89
91
  export function withheldFromHostSubprocess(o, regranted) {
90
92
  const findings = [];
@@ -117,14 +117,13 @@ export declare function sessionFaceOfHead(sessionId: string, head: SessionHead |
117
117
  */
118
118
  export declare function headReadOf(store: object | undefined): HeadRead | undefined;
119
119
  /**
120
- * session-sync **整段换装**(replace)时化身的去留 —— 三只店共用的一句判据:换装删掉再插回的是**同一位住客**的那一行
121
- * (删除带属主门),所以行在、属主相同、那一格里有一只令牌(唯一解码 {@link incarnationFromCell} 的 `absent` 读法)⇒ 沿用;
122
- * 行不在(这是一次新建)或还是空的存量形 ⇒ 铸一只新的。
120
+ * session-sync **换装**(整段换装 / 暂存提交 / local 落在既有行上的导入)时化身的去留 —— 三只店共用的一句判据:换装只换内容,换的是**同一行**
121
+ * ⇒ 行在且那一格里有一只令牌(唯一解码 {@link incarnationFromCell} 的 `absent` 读法)⇒ 沿用(属主重戳与否不改这一判:同一行 = 同一位住客);
122
+ * 行不在(这是一次新建)或还是空的存量形 ⇒ 铸一只新的。属主不在这里判:每只店在自己的属主门上判过被换的那一行,读不中的换装一个字节不写。
123
123
  */
124
124
  export declare function carriedIncarnation(sessionId: string, prior: {
125
- owner?: unknown;
126
125
  incarnation?: unknown;
127
- } | undefined, owner: string | null): string;
126
+ } | undefined): string;
128
127
  /**
129
128
  * `session_meta.incarnation` 的列定义 —— 两方言各一句,**唯一属主**:中央 DDL(`tidb-pool.ts` / `pg-session-storage.ts` 的
130
129
  * `CREATE TABLE session_meta`)内插这同一串;存量库缺这一列时,起服列册探针(`schema-consistency.ts`)拒启,拒句里的 `ALTER TABLE … ADD COLUMN …`
@@ -67,8 +67,8 @@ export function headReadOf(store) {
67
67
  const read = store?.headOf;
68
68
  return typeof read === "function" ? read.bind(store) : undefined;
69
69
  }
70
- export function carriedIncarnation(sessionId, prior, owner) {
71
- const held = prior !== undefined && (prior.owner ?? null) === owner ? incarnationFromCell(sessionId, prior.incarnation, "absent") : undefined;
70
+ export function carriedIncarnation(sessionId, prior) {
71
+ const held = prior === undefined ? undefined : incarnationFromCell(sessionId, prior.incarnation, "absent");
72
72
  return held ?? mintSessionIncarnation();
73
73
  }
74
74
  export const SESSION_META_INCARNATION_COLUMN = {
@@ -0,0 +1,130 @@
1
+ /**
2
+ * **会话的位置与项目** —— 一个事实、一个写者、一种读法(引擎 7.58.0:`workspace_changed` 是腿的整份快照;
3
+ * 会话行 = 该会话上最后一帧)。
4
+ *
5
+ * ## 律(三句)
6
+ * · **判官只有引擎一只**:每条腿起点一帧 `workspace_changed`(`cwd` / `root` / `entered?` / `project?` / `remote`),之后每变一帧;
7
+ * 这条腿属于哪个项目由引擎对检出的 `.sema/project` 记号判一次(带了 `TaskSpec.projectId` 就必须等于记号,
8
+ * 否则整腿拒 `config.project_identity_mismatch`;没有记号时声明就是身份)。本仓**不判**项目、不读记号。
9
+ * · **会话行只存一格**:`workspace` = 该会话上一条腿**自己的**最后一帧(SQL `session_meta.workspace`;local 店
10
+ * `session-workspaces.json` 那一格)。成员由 {@link SESSION_WORKSPACE_DISPOSITIONS} 一张表定(引擎给帧加成员 ⇒ 表 tsc 红,
11
+ * 门在 `trace/core-keyset-guard.ts` ⑰ 读同一张表)。**唯一写者** = 腿的记录座 {@link createSessionWorkspaceRecorder}
12
+ * (持流的四条腿逐帧喂同一只;委派子代转上来的帧 —— 带 `parentToolCallId` / `sourceTaskId` / `bgAgentId` —— 不是这条腿的位置,不认)。
13
+ * 每一笔写**在腿的流循环里等完**(不排队、没有在飞的写:腿从任何出口离开时都没有它的写还悬着 ⇒ 写恒落在这条腿的 run 认领之内,
14
+ * 同一会话的下一条腿不会被一笔晚到的写盖掉),并且**只写受理时见证到的那一位住客**({@link stampLegSessionWitness}:resolveSpec
15
+ * 受理时读会话行的化身那一读,同一只值也是委派树根的见证)—— 会话被删掉又以同一 id 重建之后,上一位住客的腿写不进新住客的行。
16
+ * · **每条腿声明一个值**:`TaskSpec.projectId` = 这次提交自己带的 `projectId`(新提交的请求体;续跑腿重放的是持久化的同一个体)
17
+ * ?? 会话行上最后一帧的 `project.id` —— **只当它是调用方声明过的**(`source: "declared"`:没有记号的检出里声明就是身份,行存它就是为了接着声明);
18
+ * `source: "marker"` 是引擎对**那一次**检出的记号判决,不续声明(引擎每条腿对自己的检出再判 —— 记号改写 / 换检出之后续跑照跑);
19
+ * 都没有 ⇒ 不带(引擎在有记号的检出里自判)。唯一铸点 {@link legProjectIdOf},唯一调用点 `boot/resolve-spec.ts`(新提交与续跑同经那一只装配)。
20
+ *
21
+ * ## 什么因此变少
22
+ * 修前请求键 `projectId` 被提交口受理、却从不交给引擎(keyset 守卫记该引擎座「未接线」,与受理表相反 —— 编译门 ② 红着);
23
+ * 会话在没有记号的检出里续跑时,项目随每一次不带 `projectId` 的提交丢掉。现在:一个值进引擎、一格存引擎的答案、下一条腿读那一格。
24
+ *
25
+ * ## 不在这里的(如实)
26
+ * · **记忆项目层**(单用户 `userrepo:` 键,`project-identity.ts` 的 git 检出身份)不是这个事实:它在装配期(任何帧之前)交给
27
+ * `TaskSpec.memory.projectKey`,按 git 仓库(origin 远端 / 本机路径)键控,与 `.sema/project` 记号无关;引擎 7.58.0 自己也写明
28
+ * 「只声明的身份不改记忆的键」。两者是否该合成一个身份,是另一张设计票。
29
+ * · 多租户记忆分区(`proj:<租户>/<projectId>`)与登记簿种子读的是**这次提交**的 `projectId`(按提交挑盘,修前即如此),不读行。
30
+ * · 行上的 `root` / `cwd` / `entered` / `remote` 今天只存不读(会话面读它们归 wire 半场那一车);fork 出的新会话从「没有记录」起。
31
+ * · **只回结果的腿(verify / cascade,含续跑的 verify 臂)不记行**:引擎在组合入口里自己排空内层腿的流,宿主手里没有流,而引擎给组合入口开的
32
+ * 帧座(`onStatusEvent` / `onWiringManifest`)没有这一帧 ⇒ 这条腿的位置与项目到不了行,行停在更早一条腿的那一帧(没有记号的检出里,
33
+ * 下一条不带 `projectId` 的提交续声明的是那一帧声明过的项目)。本仓不替引擎铸「这条腿站在哪里」;要引擎为组合入口开这一帧的座。
34
+ * · **行读在 run 认领之前**(resolveSpec 的每一读都是):同一会话两次提交交叠、后一次的装配读行之后前一次才落定并放认领 ⇒ 后一次读到的是更早
35
+ * 一条腿的项目(有记号的检出里多一次 409,没有记号的检出里续声明旧项目)。修法要把声明的装配挪到认领之后,不在本模块补第二个写者。
36
+ */
37
+ import type { TaskEvent, TaskSpec } from "@sema-agent/core";
38
+ import type { DelegationRootWitness } from "./delegated-session-owner.js";
39
+ /**
40
+ * `workspace_changed` 帧在会话行上的样子(**唯一**一张表;表序 = 存储形的键序)。`forward` = 原值存进行;`strip` = 不进行。
41
+ * 腿身份四键(`eventId` / `parentToolCallId` / `sourceTaskId` / `bgAgentId`)是这一帧在流上的身份,不是位置;`type` 是臂名。
42
+ */
43
+ export declare const SESSION_WORKSPACE_DISPOSITIONS: {
44
+ readonly type: "strip";
45
+ readonly cwd: "forward";
46
+ readonly root: "forward";
47
+ readonly entered: {
48
+ readonly nested: {
49
+ readonly dir: "forward";
50
+ readonly byPath: "forward";
51
+ };
52
+ };
53
+ readonly project: {
54
+ readonly nested: {
55
+ readonly id: "forward";
56
+ readonly source: "forward";
57
+ };
58
+ };
59
+ readonly remote: "forward";
60
+ readonly eventId: "strip";
61
+ readonly parentToolCallId: "strip";
62
+ readonly sourceTaskId: "strip";
63
+ readonly bgAgentId: "strip";
64
+ };
65
+ type WorkspaceFrame = Extract<TaskEvent, {
66
+ type: "workspace_changed";
67
+ }>;
68
+ /** 表里存进行的键(`strip` 之外的)—— 存储形由表派生,不另写一份成员清单。 */
69
+ type KeptKeys = {
70
+ [K in keyof typeof SESSION_WORKSPACE_DISPOSITIONS]: (typeof SESSION_WORKSPACE_DISPOSITIONS)[K] extends "strip" ? never : K;
71
+ }[keyof typeof SESSION_WORKSPACE_DISPOSITIONS];
72
+ /** 会话行上那一格的值:最后一帧里本仓存下的成员(型从引擎的帧臂派生)。 */
73
+ export type SessionWorkspace = Pick<WorkspaceFrame, KeptKeys>;
74
+ /** 会话店的这一面(两只方法都在才算 —— 只有一半的店让读写不同源)。 */
75
+ export interface SessionWorkspaceStore {
76
+ /** 这只会话上最后一帧;没有记录 / 没有这一行 ⇒ `undefined`。格读不出 ⇒ 读作没有记录并计一次(`decodeSessionWorkspaceCell`)。 */
77
+ readSessionWorkspace(sessionId: string): Promise<SessionWorkspace | undefined>;
78
+ /**
79
+ * 整格覆写(最后一帧胜),**只写住客是 `incarnation` 那一位的行**:行不在、或此刻坐的是另一位住客 ⇒ 不写、不抛(那一行不是这条腿的)。
80
+ * 写**没有提交**必须抛。SQL 两孪生走店的一般查询面(与腿循环里的账本追加同一形,不设每查询的客户端超时 —— 客户端放弃等待而服务端照跑完的写,
81
+ * 正是能在这条腿离开之后落地的那一形);卡住的上界归服务端(锁等待超时 / 部署的 `DB_QUERY_TIMEOUT_MS`)。
82
+ */
83
+ recordSessionWorkspace(sessionId: string, incarnation: string, workspace: SessionWorkspace): Promise<void>;
84
+ }
85
+ /** 店有没有这一面 —— 结构判(包装店 `CachingSessionStore` 逐方法转发,判的是能力不是名字)。没有 ⇒ 不记、不读(core 的内存 dev 店)。 */
86
+ export declare function sessionWorkspaceStoreOf(store: unknown): SessionWorkspaceStore | undefined;
87
+ /**
88
+ * 这条腿的会话**在受理时**是哪一位住客(会话 id + 化身令牌)的 spec 字段键 —— 与 run 名册 / 执行车道字段同一形:symbol 键随 spread / rest 解构走
89
+ * (续跑腿的 `taskConfig` 是 rest 解构出来的),对引擎的键面不可见(JSON / 结构化克隆跳过 symbol 键)。铸点唯一 = resolveSpec 出口
90
+ * ({@link stampLegSessionWitness}),值 = 受理时读会话行的那**一**读(同一只值也交给场景层作委派树根的见证);读点唯一 = {@link legSessionWitnessOf}。
91
+ */
92
+ export declare const LEG_SESSION_WITNESS_FIELD: unique symbol;
93
+ /** 铸见证(**单一属主**:只许 resolveSpec 的出口调用)。`writable:false` + `configurable:false`:同一只 spec 被第二次铸 ⇒ 当场 TypeError。缺席 ⇒ 不铸。 */
94
+ export declare function stampLegSessionWitness<T extends TaskSpec>(spec: T, witness: DelegationRootWitness | undefined): T;
95
+ /** 读见证。`undefined` = 这只 spec 不是本仓装配出口产的,或这条腿没有会话 ⇒ 记录座不写(没有住客可认)。入参收 `object`:续跑腿手里是 rest 解构出来的 taskConfig。 */
96
+ export declare function legSessionWitnessOf(spec: object): DelegationRootWitness | undefined;
97
+ /**
98
+ * 一帧 → 会话行上那一格的值。只认这条腿**自己的**帧(腿身份三键任一在场 = 委派子代转上来的,不是这条腿站在哪里);
99
+ * 按表投影后过存储形的同一只读者(写得进的就是读得出的)。读不出(引擎不会铸出这种帧)⇒ 不记、计一次。
100
+ */
101
+ export declare function sessionWorkspaceOfFrame(ev: TaskEvent): SessionWorkspace | undefined;
102
+ /** 腿的记录座。 */
103
+ export interface SessionWorkspaceRecorder {
104
+ /**
105
+ * 持流的腿**逐帧**调用并等它(在本腿的取消成因闩之后)。这条腿自己的 `workspace_changed` ⇒ 这一笔写写完(或失败计一次)才兑现 ——
106
+ * 帧按到达序一笔一笔写,最后一帧胜;腿从任何出口(`done`、断连 `break`、抛)离开时没有写还悬着。其余帧当场兑现。**从不抛、从不拒**。
107
+ */
108
+ observe(ev: TaskEvent): Promise<void>;
109
+ }
110
+ /**
111
+ * 记录座的**唯一**铸点(四条持流腿各在开流时铸一只:后台腿 `runs.ts`、活流腿与同步腿 `http/routes/tasks.ts`、续跑腿 `http/resume-legs.ts`),
112
+ * `witness` = 这条腿 spec 上的受理见证({@link legSessionWitnessOf})。店没有这一面、或见证缺席 / 受理时这只会话还没有住客令牌 ⇒
113
+ * 一只什么都不做的座(诚实缺席:认不出住客就不写 —— 写错住客比少记一次坏)。
114
+ *
115
+ * 失败方向(F 类):写失败(库抖 / 盘满 / 锁等待超时)⇒ 行停在上一帧,计一次;下一帧照写。放行的最坏后果 = 下一条不带 `projectId` 的提交读到旧的
116
+ * (或没有)项目 —— 有记号的检出里引擎照判记号(声明不等就响亮拒),没有记号的检出里这只会话少一次续声明。不放行的代价是把一条在跑的腿
117
+ * 为一次记录摔掉。等一笔写的代价 = 这条腿的流晚一次主键 UPDATE 的时延(引擎不等宿主消费,只是帧晚到;一条腿的帧只有起点一帧加每次换位一帧)。
118
+ * 库卡住(池取不到连接)时这条腿交给调用方的结果跟着等:后台腿与续跑腿的循环本来每一帧就等店写(账本追加);同步腿与不落账的活流腿此前循环里没有店写,
119
+ * 现在多等这一笔 —— 但同一只池上引擎自己的会话条目写(每一轮 `appendEntry`,`plugins/store-backend.ts` 的 `session()` 与其余店共用一池)本来就在
120
+ * 这条腿的关键路径上,池饿死时这条腿本来就停;上界归部署(`DB_QUERY_TIMEOUT_MS`、MySQL 池的排队上界)。local 后端的写不经池。
121
+ */
122
+ export declare function createSessionWorkspaceRecorder(store: SessionWorkspaceStore | undefined, witness: DelegationRootWitness | undefined): SessionWorkspaceRecorder;
123
+ /**
124
+ * **这条腿交给引擎的 `TaskSpec.projectId`**(唯一铸点;见文件头第三句)。`submitted` = 这次提交自己带的、已过形状门且折成小写的值
125
+ * (新提交:鉴权口的 `resolvedProjectId`;续跑:持久化体的同一个键)。它在场就不读行。行读失败(库抖)原样上抛 —— 读不到「这只会话的项目」
126
+ * 不能悄悄折成「没有项目」(那会把一只登记在项目下的会话在没有记号的检出里改成无项目)。
127
+ */
128
+ export declare function legProjectIdOf(submitted: string | undefined, store: SessionWorkspaceStore | undefined, sessionId: string | undefined): Promise<string | undefined>;
129
+ export {};
130
+ //# sourceMappingURL=session-workspace.d.ts.map
@@ -0,0 +1,83 @@
1
+ import { recordFailOpen } from "./observability/fail-open.js";
2
+ import { readSessionWorkspaceCell, SESSION_WORKSPACE_DEGRADED } from "./plugins/session-workspace-cell.js";
3
+ import { projectByTable } from "./trace/projection-table.js";
4
+ export const SESSION_WORKSPACE_DISPOSITIONS = {
5
+ type: "strip",
6
+ cwd: "forward",
7
+ root: "forward",
8
+ entered: { nested: { dir: "forward", byPath: "forward" } },
9
+ project: { nested: { id: "forward", source: "forward" } },
10
+ remote: "forward",
11
+ eventId: "strip",
12
+ parentToolCallId: "strip",
13
+ sourceTaskId: "strip",
14
+ bgAgentId: "strip",
15
+ };
16
+ export function sessionWorkspaceStoreOf(store) {
17
+ const s = store;
18
+ if (typeof s?.readSessionWorkspace !== "function" || typeof s.recordSessionWorkspace !== "function")
19
+ return undefined;
20
+ return { readSessionWorkspace: s.readSessionWorkspace.bind(s), recordSessionWorkspace: s.recordSessionWorkspace.bind(s) };
21
+ }
22
+ export const LEG_SESSION_WITNESS_FIELD = Symbol("sema-server.legSessionWitness");
23
+ export function stampLegSessionWitness(spec, witness) {
24
+ if (witness !== undefined)
25
+ Object.defineProperty(spec, LEG_SESSION_WITNESS_FIELD, { value: Object.freeze({ ...witness }), enumerable: true, writable: false, configurable: false });
26
+ return spec;
27
+ }
28
+ export function legSessionWitnessOf(spec) {
29
+ return spec[LEG_SESSION_WITNESS_FIELD];
30
+ }
31
+ export function sessionWorkspaceOfFrame(ev) {
32
+ if (ev.type !== "workspace_changed")
33
+ return undefined;
34
+ if (ev.parentToolCallId !== undefined || ev.sourceTaskId !== undefined || ev.bgAgentId !== undefined)
35
+ return undefined;
36
+ const shape = readSessionWorkspaceCell(projectByTable("session_workspace", SESSION_WORKSPACE_DISPOSITIONS, ev, ev));
37
+ if (shape.problem !== undefined) {
38
+ recordFailOpen(SESSION_WORKSPACE_DEGRADED, `op=frame problem=${shape.problem.slice(0, 200)}`);
39
+ return undefined;
40
+ }
41
+ return shape.value;
42
+ }
43
+ const SETTLED = Promise.resolve();
44
+ const NO_RECORD = { observe: () => SETTLED };
45
+ export function createSessionWorkspaceRecorder(store, witness) {
46
+ if (store === undefined || witness === undefined || witness.incarnation === null)
47
+ return NO_RECORD;
48
+ const { sessionId, incarnation } = witness;
49
+ return {
50
+ async observe(ev) {
51
+ const snapshot = sessionWorkspaceOfFrame(ev);
52
+ if (snapshot === undefined)
53
+ return;
54
+ try {
55
+ await store.recordSessionWorkspace(sessionId, incarnation, snapshot);
56
+ }
57
+ catch (e) {
58
+ recordFailOpen(SESSION_WORKSPACE_DEGRADED, `op=write session=${sessionId} ${(e instanceof Error ? e.message : String(e)).slice(0, 200)}`);
59
+ }
60
+ },
61
+ };
62
+ }
63
+ export async function legProjectIdOf(submitted, store, sessionId) {
64
+ if (submitted !== undefined)
65
+ return submitted;
66
+ if (store === undefined || sessionId === undefined)
67
+ return undefined;
68
+ const project = (await store.readSessionWorkspace(sessionId))?.project;
69
+ return project === undefined ? undefined : replayedProjectIdOf(project);
70
+ }
71
+ function replayedProjectIdOf(project) {
72
+ switch (project.source) {
73
+ case "declared":
74
+ return project.id;
75
+ case "marker":
76
+ return undefined;
77
+ default: {
78
+ const unknownSource = project.source;
79
+ throw new Error(`session workspace: unknown project source word ${JSON.stringify(unknownSource)} (the row reader admits only marker / declared)`);
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=session-workspace.js.map
@@ -108,7 +108,7 @@ export interface TaskAgentsRefusal {
108
108
  * 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400 更危险,故整条拒。`allowTools` / `denyTools` 本门只判形与条数(`toolList`),
109
109
  * **名字归引擎一只判**(core 7.49.0 [ref] / [ref] 第二步起与根 run 的 `allowTools` / `excludeTools` 是同一只选择器,开跑前,三来源同判):core 7.51.0 [ref] 起
110
110
  * 没有任何工具答应的名字(无人挂的名、大小写孪生 / 退役名 `bash` / `BASH`)**引擎不拒 run** —— 条目原样保留、经 onError(config)告知一次(带该写的拼法);
111
- * 其中**写错现名的**(`denyTools:["BASH"]` 想禁的照用 = 静默放行;`allowTools:["BASH"]` 想放的什么也没放 = 静默收窄)由 `resolveSpec` 前置拒:**限制侧**(`denyTools`)四类全拒(近似名 · 退役名 · 文法读不出 · `*`),
111
+ * 其中**写错现名的**(`denyTools:["BASH"]` 想禁的照用 = 静默放行;`allowTools:["BASH"]` 想放的什么也没放 = 静默收窄)由 `resolveSpec` 前置拒:**限制侧**(`denyTools`)三类全拒(近似名 · 退役名 · 文法读不出);名内的 `*` 自 7.137.0 起是整名 glob、受理(孤 `*` = 每一只工具),`Tool(content)` 引擎读成整只工具、同样受理,
112
112
  * **放行侧**(`allowTools`)只拒近似名 —— 放行侧的内容形 / 没有后继的退役名 / 文法读不出照 7.130.0 受理、交引擎既有通告(有后继的退役名〔`bash` · `Task`〕与文法读不出的放行条目引擎在子代 prepare 整 run 拒 `config.agent.unknown_tool`,根 `allowTools` 拒 `config.tool_face_control`,同步腿两码同为 400〔`runs.ts` `PREPARE_REFUSAL_HTTP_STATUS`〕;`rule-entry-list.ts` `inertToolListEntryOf`:近似名提交 400 `config.agent.unknown_tool` 点名现名、续跑按方向分流;
113
113
  * 判官仍是引擎的条目读法,不是第二个判官);
114
114
  * **真未知名**(没有近似也没有退役对应:懒挂载的合法形)照旧只告运维、不拒。
@@ -1,12 +1,8 @@
1
- /** 卡载体:引擎铸的问 / 停驻行 / 它们的投影行(活的 `AskRequest`、停驻行的 `tool_approval` 臂、运维队列行都结构上满足)。 */
1
+ /** 载体:引擎给一次调用盖过绑定词的任何形(活的 `AskRequest`、停驻行的 `tool_approval` 臂、运维队列行、策略层的 `ToolCallRequest` 都结构上满足)。 */
2
2
  export interface StandingAnswerCard {
3
3
  readonly boundGrantDigest?: unknown;
4
4
  readonly boundGrantAbsence?: unknown;
5
5
  }
6
- /** 调用载体:策略层的 `ToolCallRequest`、豁免店的行(只有工具名)。 */
7
- export interface StandingAnswerCall {
8
- readonly toolName?: unknown;
9
- }
10
6
  /** 一问的授予绑定(闭集):`none` = 应答只覆盖这一次调用;`bound` = 授予、绑定可读;`unreadable` = 授予或说不清、绑定读不出。 */
11
7
  export type GrantBinding = {
12
8
  readonly kind: "none";
@@ -16,13 +12,6 @@ export type GrantBinding = {
16
12
  } | {
17
13
  readonly kind: "unreadable";
18
14
  };
19
- /** 卡上的授予绑定 —— 只读引擎的词(读法见文件头注):摘要是串 ⇒ bound;摘要不在且词是 `not_bindable` ⇒ none;其余 ⇒ unreadable。 */
15
+ /** 载体上的授予绑定 —— 只读引擎的词(读法见文件头注):摘要是串 ⇒ bound;摘要不在且词是 `not_bindable` ⇒ none;其余 ⇒ unreadable。 */
20
16
  export declare function grantBindingOf(card: StandingAnswerCard): GrantBinding;
21
- /** 调用上的授予绑定 —— 调用上没有引擎的词,读引擎绑定工厂的那一条规则(见文件头注;🚧 引擎在调用上盖词之后退役)。 */
22
- export declare function callGrantBindingOf(call: StandingAnswerCall): GrantBinding;
23
- /**
24
- * 一问的授予绑定的**唯一折点**:读哪个载体由**问的属主**定 —— 名单自己的问(`deploymentListQuestion`,即 `standingAnswerHome` 的 `server_store` 家)读调用口,
25
- * 引擎铸的问读卡口(载体与理由见文件头注)。所有把「一张卡」折成 {@link StandingAnswerFacts} 的读者(活的问、待决条目、停驻行、运维队列行)经它取绑定,不各自选口。
26
- */
27
- export declare function questionGrantBindingOf(carrier: StandingAnswerCard & StandingAnswerCall, deploymentListQuestion: boolean): GrantBinding;
28
17
  //# sourceMappingURL=standing-answer-grant.d.ts.map
@@ -1,4 +1,3 @@
1
- import { SKILL_TOOL_NAME } from "@sema-agent/core";
2
1
  const NOT_BINDABLE = "not_bindable";
3
2
  export function grantBindingOf(card) {
4
3
  const digest = card.boundGrantDigest;
@@ -6,10 +5,4 @@ export function grantBindingOf(card) {
6
5
  return { kind: "bound", digest };
7
6
  return digest === undefined && card.boundGrantAbsence === NOT_BINDABLE ? { kind: "none" } : { kind: "unreadable" };
8
7
  }
9
- export function callGrantBindingOf(call) {
10
- return call.toolName === SKILL_TOOL_NAME ? { kind: "unreadable" } : { kind: "none" };
11
- }
12
- export function questionGrantBindingOf(carrier, deploymentListQuestion) {
13
- return deploymentListQuestion ? callGrantBindingOf(carrier) : grantBindingOf(carrier);
14
- }
15
8
  //# sourceMappingURL=standing-answer-grant.js.map