@sema-agent/server 7.89.0 → 7.91.1

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 (72) hide show
  1. package/dist/adoption/plan.js +11 -0
  2. package/dist/boot/fatal-exit.d.ts +3 -0
  3. package/dist/boot/fatal-exit.js +5 -0
  4. package/dist/boot/resolve-spec.js +2 -0
  5. package/dist/boot/runner-deps.d.ts +6 -0
  6. package/dist/boot/runner-deps.js +11 -0
  7. package/dist/boot/session-faces.d.ts +6 -0
  8. package/dist/boot/session-faces.js +10 -2
  9. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  10. package/dist/boot/stage-03-store-backend.js +2 -2
  11. package/dist/boot/stage-04-stores.d.ts +1 -0
  12. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  13. package/dist/boot/stage-06-runners.d.ts +1 -0
  14. package/dist/boot/stage-06-runners.js +2 -2
  15. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  16. package/dist/boot/stage-08-reapers.d.ts +1 -0
  17. package/dist/boot/stage-09-leader.d.ts +1 -0
  18. package/dist/boot/stage-09-leader.js +2 -2
  19. package/dist/boot/stage-10-http-server.d.ts +1 -0
  20. package/dist/boot/stage-10-http-server.js +1 -0
  21. package/dist/boot/stores.d.ts +2 -0
  22. package/dist/boot/stores.js +11 -3
  23. package/dist/brain.js +32 -19
  24. package/dist/capabilities/memory-notice.d.ts +7 -1
  25. package/dist/capabilities/memory-notice.js +6 -6
  26. package/dist/capabilities/team.d.ts +37 -0
  27. package/dist/config-catalog.js +6 -18
  28. package/dist/config.d.ts +1 -1
  29. package/dist/config.js +5 -5
  30. package/dist/cross-session-settings.d.ts +6 -2
  31. package/dist/hooks/hook-llm.js +10 -6
  32. package/dist/hooks/hook-runner.js +19 -2
  33. package/dist/http/admission.js +9 -5
  34. package/dist/http/routes/capabilities.js +6 -3
  35. package/dist/http/routes/rules.d.ts +11 -0
  36. package/dist/http/routes/rules.js +54 -5
  37. package/dist/http/routes/session-sync.js +8 -2
  38. package/dist/http/routes/sessions-list.js +6 -1
  39. package/dist/http/routes/sessions.js +16 -14
  40. package/dist/http/server.d.ts +12 -0
  41. package/dist/http/wire-types.d.ts +12 -0
  42. package/dist/main.js +2 -1
  43. package/dist/model-route-url.d.ts +8 -0
  44. package/dist/model-route-url.js +17 -8
  45. package/dist/peer-directory.d.ts +114 -0
  46. package/dist/peer-directory.js +136 -0
  47. package/dist/permission-rule-vocab.d.ts +27 -1
  48. package/dist/permission-rule-vocab.js +8 -0
  49. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +86 -0
  50. package/dist/plugins/mailbox-recipient-lifecycle.js +10 -0
  51. package/dist/plugins/mailbox-store-sql.d.ts +18 -2
  52. package/dist/plugins/mailbox-store-sql.js +77 -1
  53. package/dist/plugins/memory-embedder.d.ts +0 -6
  54. package/dist/plugins/memory-embedder.js +10 -16
  55. package/dist/plugins/store-contracts.d.ts +36 -1
  56. package/dist/plugins/store-contracts.js +10 -0
  57. package/dist/request-key-closure.js +2 -1
  58. package/dist/rules-consent.d.ts +74 -1
  59. package/dist/rules-consent.js +81 -1
  60. package/dist/runtime-governance.d.ts +16 -7
  61. package/dist/runtime-governance.js +27 -7
  62. package/dist/session-policy-wire.d.ts +103 -0
  63. package/dist/session-policy-wire.js +66 -0
  64. package/dist/task-settings.d.ts +12 -2
  65. package/dist/task-settings.js +7 -5
  66. package/dist/trace/core-keyset-guard.d.ts +2 -2
  67. package/dist/trace/project.d.ts +7 -0
  68. package/dist/trace/project.js +1 -0
  69. package/dist/trace/redact.d.ts +96 -11
  70. package/dist/trace/redact.js +155 -11
  71. package/dist/trace/sema-provenance.d.ts +7 -6
  72. package/package.json +3 -3
@@ -256,6 +256,17 @@ export const REBIND_LEGS = [
256
256
  residualKey: ["handle"],
257
257
  why: "信箱行的 scope 分区。`scope_key`(字节列)与 `scope`(可读列)存的是同一个值,必须一起改",
258
258
  },
259
+ {
260
+ leg: "mailbox_tombstone#scope_key",
261
+ table: "mailbox_tombstone",
262
+ kind: "bulk-rebind",
263
+ action: "bucket-rebind",
264
+ columns: ["scope_key"],
265
+ matchColumn: "scope_key",
266
+ encoding: "verbatim",
267
+ residualKey: ["handle"],
268
+ why: "S-72 收件人墓碑(预删除态)。**必须与 `mailbox` 同迁**:漏迁它 = 收编后新身份下的会话盒**永久静默拒收**(append 撞一条属于旧身份的墓碑),而那正是墓碑这个机制唯一的失效方向;本表无 `scope` 可读列(与 mailbox_message 同形)",
269
+ },
259
270
  {
260
271
  leg: "mailbox_message#scope_key",
261
272
  table: "mailbox_message",
@@ -0,0 +1,3 @@
1
+ /** `err` 的 fatal 日志文本:`String(err)` 过形状律一次。纯函数,零副作用。 */
2
+ export declare function buildFatalErrorText(err: unknown): string;
3
+ //# sourceMappingURL=fatal-exit.d.ts.map
@@ -0,0 +1,5 @@
1
+ import { redactSecrets } from "../trace/redact.js";
2
+ export function buildFatalErrorText(err) {
3
+ return redactSecrets(String(err));
4
+ }
5
+ //# sourceMappingURL=fatal-exit.js.map
@@ -303,6 +303,7 @@ export function createResolveSpec(ctx) {
303
303
  ...(retainBackgroundProcessesFromBody(body.retainBackgroundProcesses, config.requirePrincipal, (event, fields) => logger.warn(event, { ...fields, sessionId: auth?.sessionId ?? null })) === true
304
304
  ? { retainBackgroundProcesses: true }
305
305
  : {}),
306
+ ...(body.excludeAllTools === true ? { excludeAllTools: true } : {}),
306
307
  ...(() => { const v = toolNameListFromBody(body.excludeTools); return v ? { excludeTools: v } : {}; })(),
307
308
  ...(() => { const v = toolNameListFromBody(body.deferTools); return v ? { deferTools: v } : {}; })(),
308
309
  ...(() => { const v = promptProfileFromBody(body.promptProfile); return v ? { promptProfile: v } : {}; })(),
@@ -541,6 +542,7 @@ export function createResolveSpec(ctx) {
541
542
  declaredCapable: effectiveMemoryPersistenceCapable(config),
542
543
  hands: gated.hands,
543
544
  handsReadOnly: governed.handsReadOnly === true,
545
+ excludeAllTools: governed.excludeAllTools === true,
544
546
  excludeTools: governed.excludeTools,
545
547
  remoteExecutionEnv: config.remoteExec !== undefined,
546
548
  })) {
@@ -27,6 +27,7 @@ import type { QuestionCoordinator } from "../question.js";
27
27
  import type { ToolApprovalCoordinator } from "../tool-approval.js";
28
28
  import type { MemoryBackend } from "@sema-agent/core";
29
29
  import type { MemorySyncRunner } from "../memory-sync-client.js";
30
+ import type { MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
30
31
  /**
31
32
  * `RunnerDeps.onAsk` 的装配([ref] 件4:从内联闭包提为具名导出,**纯 testability 重构、零行为变化**)。
32
33
  *
@@ -86,6 +87,11 @@ export interface RunnerDepsCtx {
86
87
  rosterStore: RunnerDeps["rosterStore"];
87
88
  backgroundAgentStore: RunnerDeps["backgroundAgentStore"];
88
89
  mailboxStore: RunnerDeps["mailboxStore"];
90
+ /**
91
+ * [ref]:mailbox 的**收件人生命周期面**(预删除态)。与 `mailboxStore` **成对**(`boot/stores.ts` 同段铸,
92
+ * 两键同在场/同缺席);是 peer 目录席三个合取项之一,也是会话删除级联落墓碑的那一口。
93
+ */
94
+ mailboxRecipientLifecycle: MailboxRecipientLifecycle | undefined;
89
95
  /** [ref]-T1:治理窗账本店(USAGE_WINDOWS 配置时才在场;与 config.usageWindows 成对进 deps)。 */
90
96
  usageWindowStore: RunnerDeps["usageWindowStore"];
91
97
  brain: ReturnType<typeof createBrain>;
@@ -13,6 +13,7 @@ import { WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js"
13
13
  import { makeLoadProjectMemory, makeProbeInstructionSources } from "../project-memory.js";
14
14
  import { workflowModelAllowlistFor } from "../task-workflow.js";
15
15
  import { buildCrossSessionInboundLayers, crossSessionDialogExpiryOf } from "../cross-session-settings.js";
16
+ import { createPeerDirectory } from "../peer-directory.js";
16
17
  export function createRunnerDepsOnAsk(toolApproval) {
17
18
  if (!toolApproval)
18
19
  return undefined;
@@ -101,6 +102,7 @@ export function createSharedRunnerDeps(ctx) {
101
102
  }
102
103
  export function createRunnerDeps(ctx) {
103
104
  const { config, logger, metrics, localRoot, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine, sessionCaptureRecords, memorySyncRunner, runtimeCapsResolver, fileHistoryStore, permissionRuleStore, fleetBus, workflowRunStore, workflowJournalStore, workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, getRunStore, sessionShellGateExplicitlyOff, } = ctx;
105
+ const peerSessionLister = sessionStore.listSessions?.bind(sessionStore);
104
106
  const memoryEngineIncident = createMemoryEngineIncidentSink({ logger, metrics });
105
107
  const sharedRunnerDeps = createSharedRunnerDeps(ctx);
106
108
  const workflowModelAllowlist = workflowModelAllowlistFor(config);
@@ -156,6 +158,15 @@ export function createRunnerDeps(ctx) {
156
158
  : {};
157
159
  const runnerDeps = {
158
160
  ...sharedRunnerDeps,
161
+ peerDirectory: peerSessionLister !== undefined && ctx.mailboxStore !== undefined && ctx.mailboxRecipientLifecycle !== undefined
162
+ ? createPeerDirectory({
163
+ lister: peerSessionLister,
164
+ lifecycle: ctx.mailboxRecipientLifecycle,
165
+ requirePrincipal: config.requirePrincipal,
166
+ crossSessionInbound: buildCrossSessionInboundLayers(config),
167
+ logger,
168
+ })
169
+ : undefined,
159
170
  onTaskOutcome: outcomeSink
160
171
  ? (o) => {
161
172
  metrics.inc("task_outcomes_total", { status: o.status, green: String(o.oracle?.green ?? "unknown") });
@@ -9,6 +9,8 @@ import type { OwnerAwareSessionStore } from "../security.js";
9
9
  import type { DeviceStore } from "../device-store.js";
10
10
  import { SessionWatchRegistry } from "../session-watch.js";
11
11
  import type { TaskListLane } from "./task-list-lane.js";
12
+ import { type RunnerDeps } from "@sema-agent/core";
13
+ import type { MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
12
14
  export interface SessionFacesCtx {
13
15
  config: ServiceConfig;
14
16
  logger: Logger;
@@ -31,6 +33,10 @@ export interface SessionFacesCtx {
31
33
  deviceStore: DeviceStore | undefined;
32
34
  /** per-session cwd/shellEnv 登记簿的清除口(E21 级联的一条腿,codex R1-F1)。缺席 = 装配未接。 */
33
35
  dropSessionScoped: ((sid: string) => void) | undefined;
36
+ /** [ref]:会话盒(`session.<id>`)的信箱 + 它的收件人生命周期面(E21 级联的一条腿)。**成对**在场
37
+ * (`boot/stores.ts` 同段铸);缺席 = 本部署无耐久 mailbox ⇒ 无箱可退役。 */
38
+ mailboxStore: RunnerDeps["mailboxStore"];
39
+ mailboxRecipientLifecycle: MailboxRecipientLifecycle | undefined;
34
40
  }
35
41
  export declare function createSessionFaces(ctx: SessionFacesCtx): {
36
42
  ownerAware: OwnerAwareSessionStore;
@@ -4,9 +4,9 @@ import { PlanCacheProbe } from "../plan-cache-probe.js";
4
4
  import { purgeScratchpadDir } from "../env-facts.js";
5
5
  import { setLeafAdvanceListener } from "../session-leaf-bus.js";
6
6
  import { SessionWatchRegistry, posIntEnv } from "../session-watch.js";
7
- import { defaultTaskRegistry } from "@sema-agent/core";
7
+ import { defaultTaskRegistry, peerSessionBoxHandle } from "@sema-agent/core";
8
8
  export function createSessionFaces(ctx) {
9
- const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, } = ctx;
9
+ const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, mailboxStore, mailboxRecipientLifecycle, } = ctx;
10
10
  const mysqlPool = backend?.mysqlPool();
11
11
  const auditPgPool = backend?.pgPool();
12
12
  const auditLocalRoot = backend?.kind === "local" ? (config.localDataRoot ?? localRoot) : undefined;
@@ -79,6 +79,12 @@ export function createSessionFaces(ctx) {
79
79
  }
80
80
  if (workflowCompletionInbox)
81
81
  await workflowCompletionInbox.purge(sessionId, owner ?? null).catch((err) => logger.warn("workflow_completion_inbox_purge_failed", { sessionId, err: String(err) }));
82
+ const peerBox = mailboxStore !== undefined && mailboxRecipientLifecycle !== undefined
83
+ ? { store: mailboxStore, lifecycle: mailboxRecipientLifecycle, scope: owner ?? "default", handle: peerSessionBoxHandle(sessionId) }
84
+ : undefined;
85
+ if (peerBox !== undefined) {
86
+ await peerBox.lifecycle.retireRecipient(peerBox.scope, peerBox.handle, Date.now());
87
+ }
82
88
  await purgeScratchpadDir(config.localDataRoot ?? localRoot, sessionId);
83
89
  try {
84
90
  const reaped = defaultTaskRegistry.reapSessionBackground(sessionId, owner ?? "default");
@@ -88,6 +94,8 @@ export function createSessionFaces(ctx) {
88
94
  catch (err) {
89
95
  logger.warn("session_background_reap_failed", { sessionId, err: String(err) });
90
96
  }
97
+ if (peerBox !== undefined)
98
+ await peerBox.store.drop(peerBox.scope, peerBox.handle);
91
99
  const historyDeleted = await ownerAware.deleteSession(sessionId, owner);
92
100
  sessionWatchRegistry?.dropSession(sessionId);
93
101
  return { deleted: historyDeleted || runs.removed > 0 };
@@ -4,6 +4,7 @@ export declare function runStage03(ctx: Awaited<ReturnType<typeof runStage02>>):
4
4
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
5
5
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
6
6
  instanceId: string;
7
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
7
8
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
8
9
  memoryEngine: {
9
10
  backend: import("@sema-agent/core").MemoryBackend;
@@ -2,10 +2,10 @@ import { uuidv7 } from "@sema-agent/core";
2
2
  import { openStores } from "./stores.js";
3
3
  export async function runStage03(ctx) {
4
4
  const { config, localRoot, logger, metrics } = ctx;
5
- const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
5
+ const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, mailboxRecipientLifecycle, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
6
6
  const instanceId = uuidv7();
7
7
  const sessionCaptureRecords = backend?.sessionCaptureRecords();
8
8
  const sessionCaptureRecordStore = sessionCaptureRecords?.();
9
- return { ...ctx, backend, backgroundAgentStore, breakerState, instanceId, mailboxStore, memoryEngine, memoryExportBackend, memoryPosture, memorySyncCursors, memorySyncRunner, rosterStore, sessionCaptureRecordStore, sessionCaptureRecords, sessionStore, sqlEngineFacts, storeBackendDegraded, taskAttachmentStore, taskListLane, usageWindowStore };
9
+ return { ...ctx, backend, backgroundAgentStore, breakerState, instanceId, mailboxRecipientLifecycle, mailboxStore, memoryEngine, memoryExportBackend, memoryPosture, memorySyncCursors, memorySyncRunner, rosterStore, sessionCaptureRecordStore, sessionCaptureRecords, sessionStore, sqlEngineFacts, storeBackendDegraded, taskAttachmentStore, taskListLane, usageWindowStore };
10
10
  }
11
11
  //# sourceMappingURL=stage-03-store-backend.js.map
@@ -41,6 +41,7 @@ export declare function runStage04(ctx: Awaited<ReturnType<typeof runStage03>>):
41
41
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
42
42
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
43
43
  instanceId: string;
44
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
44
45
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
45
46
  memoryEngine: {
46
47
  backend: import("@sema-agent/core").MemoryBackend;
@@ -54,6 +54,7 @@ export declare function runStage05(ctx: Awaited<ReturnType<typeof runStage04>>):
54
54
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
55
55
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
56
56
  instanceId: string;
57
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
57
58
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
58
59
  memoryEngine: {
59
60
  backend: import("@sema-agent/core").MemoryBackend;
@@ -115,6 +115,7 @@ export declare function runStage06(ctx: Awaited<ReturnType<typeof runStage05>>):
115
115
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
116
116
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
117
117
  instanceId: string;
118
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
118
119
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
119
120
  memoryEngine: {
120
121
  backend: import("@sema-agent/core").MemoryBackend;
@@ -21,7 +21,7 @@ import { createGovernanceSeams } from "./governance-seams.js";
21
21
  import { createSharedMemoryScopeAuthorizer } from "../shared-memory-scope-authorizer.js";
22
22
  import { parkedAskRedeem, runStore } from "./late-binds.js";
23
23
  export function runStage06(ctx) {
24
- const { backend, backgroundAgentStore, brain, config, configCenter, deploymentHooks, executionEnvFactory, fileHistoryStore, localRoot, logger, lspManager, mailboxStore, memoryEngine, memorySyncRunner, metrics, perSessionCwd, permissionRuleProvider, pricing, rosterStore, ruleConsent, sendUserFileTaskEnvs, sessionCaptureRecordStore, sessionCaptureRecords, sessionPolicyStore, sessionStore, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore } = ctx;
24
+ const { backend, backgroundAgentStore, brain, config, configCenter, deploymentHooks, executionEnvFactory, fileHistoryStore, localRoot, logger, lspManager, mailboxRecipientLifecycle, mailboxStore, memoryEngine, memorySyncRunner, metrics, perSessionCwd, permissionRuleProvider, pricing, rosterStore, ruleConsent, sendUserFileTaskEnvs, sessionCaptureRecordStore, sessionCaptureRecords, sessionPolicyStore, sessionStore, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore } = ctx;
25
25
  const { sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion, workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink, workflowRecoverOpts, workflowAgentRegistry, subagentSteerRegistry, workflowAgentSessionIndex, } = createWorkflowOrchestration({ config, logger, metrics, localRoot, backend, getRunStore: () => runStore });
26
26
  const { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, approvalAskAudit, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec } = createLiveCoordinators({
27
27
  config, logger, backend, sendUserFileTaskEnvs, ruleConsent,
@@ -116,7 +116,7 @@ export function runStage06(ctx) {
116
116
  hands: commitHands,
117
117
  mcpRevocations,
118
118
  sessionShellGateExplicitlyOff: sessionShellGateRegistry.explicitlyOff,
119
- config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
119
+ config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, mailboxRecipientLifecycle, usageWindowStore, brain,
120
120
  pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
121
121
  sessionCaptureRecords,
122
122
  memorySyncRunner, toolResultStore: runnerOffloadStore, checkpointStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
@@ -130,6 +130,7 @@ export declare function runStage07(ctx: Awaited<ReturnType<typeof runStage06>>):
130
130
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
131
131
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
132
132
  instanceId: string;
133
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
133
134
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
134
135
  memoryEngine: {
135
136
  backend: import("@sema-agent/core").MemoryBackend;
@@ -145,6 +145,7 @@ export declare function runStage08(ctx: Awaited<ReturnType<typeof runStage07>>):
145
145
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
146
146
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
147
147
  instanceId: string;
148
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
148
149
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
149
150
  memoryEngine: {
150
151
  backend: import("@sema-agent/core").MemoryBackend;
@@ -177,6 +177,7 @@ export declare function runStage09(ctx: Awaited<ReturnType<typeof runStage08>>):
177
177
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
178
178
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
179
179
  instanceId: string;
180
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
180
181
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
181
182
  memoryEngine: {
182
183
  backend: import("@sema-agent/core").MemoryBackend;
@@ -6,13 +6,13 @@ import { createLeaderFace } from "./leader.js";
6
6
  import { assertStaticWiringConsistent } from "../http/routes/diagnostics.js";
7
7
  import { buildDisplaySafeUrl } from "../trace/redact.js";
8
8
  export function runStage09(ctx) {
9
- const { approvalExemptionStore, backend, brain, checkpointStore, config, configCenter, deviceLane, dropSessionScoped, durableEnabled, executionEnvFactory, fileHistoryStore, governanceSeams, localRoot, logger, metrics, pricing, resumeAnchorStore, runStore, runnerDeps, sessionPolicyStore, sessionStore, streamApprovalGate, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore, workflowCompletionInbox } = ctx;
9
+ const { approvalExemptionStore, backend, brain, checkpointStore, config, configCenter, deviceLane, dropSessionScoped, durableEnabled, executionEnvFactory, fileHistoryStore, governanceSeams, localRoot, logger, mailboxRecipientLifecycle, mailboxStore, metrics, pricing, resumeAnchorStore, runStore, runnerDeps, sessionPolicyStore, sessionStore, streamApprovalGate, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore, workflowCompletionInbox } = ctx;
10
10
  const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
11
11
  deviceStore: deviceLane?.store,
12
12
  dropSessionScoped,
13
13
  config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore,
14
14
  resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore,
15
- workflowCompletionInbox, taskListLane,
15
+ workflowCompletionInbox, taskListLane, mailboxStore, mailboxRecipientLifecycle,
16
16
  });
17
17
  const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend, tracer, usageWindowStore, promptSource: configCenter.promptSource, onError: runnerDeps.onError });
18
18
  const drainState = { draining: false };
@@ -177,6 +177,7 @@ export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>):
177
177
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
178
178
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
179
179
  instanceId: string;
180
+ mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
180
181
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
181
182
  memoryEngine: {
182
183
  backend: import("@sema-agent/core").MemoryBackend;
@@ -37,6 +37,7 @@ export async function runStage10(ctx) {
37
37
  taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
38
38
  backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
39
39
  sessionStorage: ownerAware,
40
+ peerDirectory: runnerDeps.peerDirectory,
40
41
  workflowRunStore: workflowRunStore ? workflowRunStore : undefined,
41
42
  workflowAgentSessionIndex: workflowAgentSessionIndex ? workflowAgentSessionIndex : undefined,
42
43
  workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined,
@@ -4,6 +4,7 @@ import { type MemorySyncRunner } from "../memory-sync-client.js";
4
4
  import type { Logger } from "../observability/logger.js";
5
5
  import type { Metrics } from "../observability/metrics.js";
6
6
  import { type MemorySyncStore } from "../plugins/memory-sync-store-pg.js";
7
+ import { type MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
7
8
  import { type TaskAttachmentStore } from "../plugins/task-attachment-store.js";
8
9
  import { type StoreBackend } from "../plugins/store-backend.js";
9
10
  export interface OpenStoresCtx {
@@ -22,6 +23,7 @@ export declare function openStores(ctx: OpenStoresCtx): Promise<{
22
23
  memorySyncCursors: MemorySyncStore | undefined;
23
24
  rosterStore: import("@sema-agent/core").RosterStore | undefined;
24
25
  backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
26
+ mailboxRecipientLifecycle: MailboxRecipientLifecycle | undefined;
25
27
  taskAttachmentStore: TaskAttachmentStore | undefined;
26
28
  mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
27
29
  memoryExportBackend: MemoryBackend | undefined;
@@ -10,6 +10,7 @@ import { TiDBMemoryHistoryStore, TiDBMemorySyncStore, ensureTiDBMemoryHistorySch
10
10
  import { MinioBlobBackend } from "../plugins/blob-backend.js";
11
11
  import { PgBackgroundAgentStore, TiDBBackgroundAgentStore, ensurePgBackgroundAgentSchema, ensureTiDBBackgroundAgentSchema } from "../plugins/background-agent-store-sql.js";
12
12
  import { PgMailboxStore, TiDBMailboxStore, ensurePgMailboxSchema, ensureTiDBMailboxSchema } from "../plugins/mailbox-store-sql.js";
13
+ import { createDropOnlyRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
13
14
  import { PgUsageWindowStore, TiDBUsageWindowStore, ensurePgUsageWindowSchema, ensureTiDBUsageWindowSchema } from "../plugins/usage-window-store-sql.js";
14
15
  import { PgRosterStore, TiDBRosterStore, ensurePgRosterSchema, ensureTiDBRosterSchema } from "../plugins/roster-store-sql.js";
15
16
  import { LocalTaskAttachmentStore } from "../plugins/local-task-attachment-store.js";
@@ -285,27 +286,34 @@ export async function openStores(ctx) {
285
286
  logger.info("task_attachment_store_enabled", { backend: pgPool ? "pg+minio" : mysqlPool ? "tidb+minio" : "file", maxBytes: config.attachmentMaxBytes });
286
287
  }
287
288
  let mailboxStore;
289
+ let mailboxRecipientLifecycle;
288
290
  {
289
291
  const mysqlPool = backend?.mysqlPool?.();
290
292
  const pgPool = backend?.pgPool?.();
291
293
  if (pgPool) {
292
294
  await ensurePgMailboxSchema(async (text, params) => pgPool.query(text, params));
293
- mailboxStore = new PgMailboxStore(pgPool, logger);
295
+ const store = new PgMailboxStore(pgPool, logger);
296
+ mailboxStore = store;
297
+ mailboxRecipientLifecycle = store;
294
298
  }
295
299
  else if (mysqlPool) {
296
300
  await ensureTiDBMailboxSchema(mysqlPool);
297
- mailboxStore = new TiDBMailboxStore(mysqlPool, logger);
301
+ const store = new TiDBMailboxStore(mysqlPool, logger);
302
+ mailboxStore = store;
303
+ mailboxRecipientLifecycle = store;
298
304
  }
299
305
  else if (backend?.kind === "local") {
300
306
  mailboxStore = new FileMailboxStore(config.localDataRoot ?? localRoot, {
301
307
  onCorruptRead: (info) => logger.warn("mailbox_corrupt_read", { path: info.path, reason: info.reason }),
302
308
  });
309
+ mailboxRecipientLifecycle = createDropOnlyRecipientLifecycle(mailboxStore);
303
310
  }
304
311
  if (mailboxStore) {
305
312
  const laneVerdict = mailboxCrossProcessMountVerdict(mailboxStore);
306
313
  logger.info("mailbox_store_enabled", {
307
314
  backend: pgPool ? "pg" : mysqlPool ? "tidb" : "file",
308
315
  crossProcessSafe: mailboxStore.crossProcessSafe,
316
+ recipientTombstones: pgPool !== undefined || mysqlPool !== undefined,
309
317
  peerLaneMountable: laneVerdict.ok,
310
318
  ...(laneVerdict.ok ? {} : { peerLaneRefusal: laneVerdict.code }),
311
319
  });
@@ -424,7 +432,7 @@ export async function openStores(ctx) {
424
432
  .startRefresh()
425
433
  : undefined;
426
434
  return {
427
- backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore,
435
+ backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, mailboxRecipientLifecycle,
428
436
  taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState,
429
437
  usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts,
430
438
  };
package/dist/brain.js CHANGED
@@ -1,22 +1,31 @@
1
1
  import { adjudicateModelRoute, createAnthropicBrain, createCircuitBreakerBrain, createDegradingBrain, createFailoverBrain, createOpenAIBrain, createRoutingBrain, hasAuthCarrier, resolveRouteCredential, routeRefusalText, } from "@sema-agent/core";
2
- import { normalizeBaseUrl } from "@sema-agent/core";
2
+ import { routeKeyOf } from "@sema-agent/core";
3
3
  import { buildDefaultResilience } from "./config-types.js";
4
4
  import { resolveModelApiKey } from "./key-resolver.js";
5
5
  import { catalogBrainLane } from "./model-provider.js";
6
- import { buildDisplaySafeUrl, redactSecrets } from "./trace/redact.js";
6
+ import { buildDisplaySafeRefusal, buildDisplaySafeUrl } from "./trace/redact.js";
7
7
  function isOffDeploymentRoot(baseUrl, config) {
8
8
  if (!baseUrl)
9
9
  return false;
10
- const entry = normalizeBaseUrl(baseUrl);
10
+ const entry = routeKeyOf(baseUrl);
11
11
  if (entry === "")
12
12
  return false;
13
- return ![config.gatewayBaseUrl, ...config.gatewayFallbackUrls].some((u) => normalizeBaseUrl(u) === entry);
13
+ return ![config.gatewayBaseUrl, ...config.gatewayFallbackUrls].some((u) => routeKeyOf(u) === entry);
14
14
  }
15
15
  function degradeDeclaredRoot(fallbackBaseUrl, config) {
16
16
  const armed = Boolean(config.gatewayApiKey) && isOffDeploymentRoot(fallbackBaseUrl, config);
17
17
  return armed ? config.gatewayBaseUrl : (fallbackBaseUrl || config.gatewayBaseUrl);
18
18
  }
19
19
  export const FAST_FAIL_MAX_RETRIES = 2;
20
+ function constructRouteBrain(label, sourceUrls, construct) {
21
+ try {
22
+ return construct();
23
+ }
24
+ catch (e) {
25
+ const text = buildDisplaySafeRefusal(e instanceof Error ? e.message : String(e), sourceUrls.map((u) => u ?? ""));
26
+ throw new Error(`${label}: ${text}`, { cause: undefined });
27
+ }
28
+ }
20
29
  const DEFAULT_RESILIENCE = buildDefaultResilience();
21
30
  export function createBrain(config, deps = {}) {
22
31
  const { fetchImpl, breakerState } = deps;
@@ -34,7 +43,7 @@ export function createBrain(config, deps = {}) {
34
43
  : {};
35
44
  const routes = [config.gatewayBaseUrl, ...config.gatewayFallbackUrls];
36
45
  const openaiBrains = routes.map((baseUrl, i) => {
37
- const brain = createOpenAIBrain({
46
+ const brain = constructRouteBrain(i === 0 ? "gateway route" : `gateway fallback route[${i - 1}]`, [baseUrl], () => createOpenAIBrain({
38
47
  baseUrl,
39
48
  get apiKey() {
40
49
  return config.gatewayApiKey;
@@ -42,7 +51,7 @@ export function createBrain(config, deps = {}) {
42
51
  ...retryCap,
43
52
  fetchImpl,
44
53
  ...timeouts,
45
- });
54
+ }));
46
55
  const isLast = i === routes.length - 1;
47
56
  return r.circuitBreaker && !isLast
48
57
  ? createCircuitBreakerBrain(brain, {
@@ -54,18 +63,19 @@ export function createBrain(config, deps = {}) {
54
63
  : brain;
55
64
  });
56
65
  const gatewayBrain = openaiBrains.length > 1 ? createFailoverBrain(openaiBrains) : openaiBrains[0];
57
- const anthropicBrain = config.anthropic
58
- ? createAnthropicBrain({
59
- ...(config.anthropic.authToken
60
- ? { headers: { Authorization: `Bearer ${config.anthropic.authToken}` } }
61
- : { apiKey: config.anthropic.apiKey }),
62
- baseUrl: config.anthropic.baseUrl ?? "https://api.anthropic.com",
63
- version: config.anthropic.version,
64
- cacheBreakpoints: config.anthropic.cacheBreakpoints,
65
- ...(config.anthropic.maxRetries !== undefined ? { maxRetries: config.anthropic.maxRetries } : {}),
66
+ const anthropicCfg = config.anthropic;
67
+ const anthropicBrain = anthropicCfg
68
+ ? constructRouteBrain("anthropic route", [anthropicCfg.baseUrl], () => createAnthropicBrain({
69
+ ...(anthropicCfg.authToken
70
+ ? { headers: { Authorization: `Bearer ${anthropicCfg.authToken}` } }
71
+ : { apiKey: anthropicCfg.apiKey }),
72
+ baseUrl: anthropicCfg.baseUrl ?? "https://api.anthropic.com",
73
+ version: anthropicCfg.version,
74
+ cacheBreakpoints: anthropicCfg.cacheBreakpoints,
75
+ ...(anthropicCfg.maxRetries !== undefined ? { maxRetries: anthropicCfg.maxRetries } : {}),
66
76
  fetchImpl,
67
77
  ...timeouts,
68
- })
78
+ }))
69
79
  : undefined;
70
80
  let brain = anthropicBrain
71
81
  ? createRoutingBrain({ anthropic: anthropicBrain }, { fallback: gatewayBrain })
@@ -81,7 +91,7 @@ export function createBrain(config, deps = {}) {
81
91
  const fallback = fallbackLane === "anthropic" && anthropicBrain
82
92
  ?
83
93
  anthropicBrain
84
- : createOpenAIBrain({
94
+ : constructRouteBrain(`degrade target "${degradeTo}"`, [fallbackModel.baseUrl, config.gatewayBaseUrl], () => createOpenAIBrain({
85
95
  get baseUrl() {
86
96
  return degradeDeclaredRoot(fallbackModel.baseUrl, config);
87
97
  },
@@ -91,7 +101,7 @@ export function createBrain(config, deps = {}) {
91
101
  ...(config.gatewayMaxRetries !== undefined ? { maxRetries: config.gatewayMaxRetries } : {}),
92
102
  fetchImpl,
93
103
  ...timeouts,
94
- });
104
+ }));
95
105
  const degradeEntryModel = {
96
106
  ...fallbackModel,
97
107
  get headers() {
@@ -158,7 +168,10 @@ export function brainSummary(config) {
158
168
  const verdict = adjudicateModelRoute({ id: target.id, baseUrl: target.baseUrl }, resolveRouteCredential({ ...(config.gatewayApiKey ? { configApiKey: config.gatewayApiKey } : {}) }), { baseUrl: degradeDeclaredRoot(target.baseUrl, config) });
159
169
  return verdict.ok
160
170
  ? { reactiveDegradeRoutePairing: `ok:${verdict.posture}`, reactiveDegradeRouteRefusal: null }
161
- : { reactiveDegradeRoutePairing: `broken:${verdict.code}`, reactiveDegradeRouteRefusal: redactSecrets(routeRefusalText(verdict)) };
171
+ : {
172
+ reactiveDegradeRoutePairing: `broken:${verdict.code}`,
173
+ reactiveDegradeRouteRefusal: buildDisplaySafeRefusal(routeRefusalText(verdict), [target.baseUrl ?? "", degradeDeclaredRoot(target.baseUrl, config)]),
174
+ };
162
175
  })()),
163
176
  };
164
177
  }
@@ -51,6 +51,12 @@ export interface MemoryFaceFacts {
51
51
  hands: ScenarioHands;
52
52
  /** 折完 governance+settings 后的最终 `handsReadOnly`(plan 模式 / autonomy 收窄)。手带只剩读腿。 */
53
53
  handsReadOnly: boolean;
54
+ /** 折完之后的最终 `TaskSpec.excludeAllTools`(S-493;core 7.8.0 [ref])。`true` = **整只**工具面卸空
55
+ * (core `prepareDeferClassify` 对它直接 `roster.unmountWhere(() => true)`)⇒ 手带在场也一条落盘路都不剩。
56
+ * 🔴 它与下面那张名单是**同一个语义面**(「哪些工具不在 roster 上」)的两种声明,判据合成一只
57
+ * {@link isUnmounted} —— 上游同版的姊妹谓词 `fullShellReachable` 也正是把 `spec.excludeAllTools !== true`
58
+ * 当一个合取项(`prepare-inherited-gate.js:174` 亲读),本仓与它同口径。 */
59
+ excludeAllTools: boolean;
54
60
  /** 折完之后的最终 `TaskSpec.excludeTools`(roster 真卸载,不是延迟披露)。**全部**已知落盘路都被排掉
55
61
  * ⇒ 手带在场也等于没有写通道 —— core 的 `rosterCanPersist` 也是按排除后的 roster 算的,这里跟上。 */
56
62
  excludeTools: readonly string[] | undefined;
@@ -71,7 +77,7 @@ export interface MemoryFaceFacts {
71
77
  * ① engine 在场 ⇒ **否**(引擎的座位,见文件头注);
72
78
  * ② 部署显式声明 ⇒ 声明说了算(`true` 撤回 / `false` 强制)—— operator 的部署事实压过我们的推断,
73
79
  * 与 core 对 `memoryPersistenceCapable` 的处置同向;
74
- * ③ 否则推断:有已知落盘路(手带在场且不是只读)⇒ 否;没有 ⇒ 是。
80
+ * ③ 否则推断:有已知落盘路(手带在场、不是只读、不在 remote 车道、且没被卸载决定卸光)⇒ 否;没有 ⇒ 是。
75
81
  */
76
82
  export declare function shouldDiscloseNoPersistentMemory(facts: MemoryFaceFacts): boolean;
77
83
  /**
@@ -2,11 +2,11 @@ import { NO_PERSISTENT_MEMORY_NOTICE } from "@sema-agent/core";
2
2
  import { hasConstitutionAnchors } from "../task-settings.js";
3
3
  export const KNOWN_PERSISTENCE_TOOL_NAMES = ["Write", "Edit", "NotebookEdit", "Bash"];
4
4
  export const NO_PERSISTENT_MEMORY_SECTION_ID = "sema/memory.no-persistent";
5
- function anyPersistenceToolSurvives(excludeTools) {
6
- if (excludeTools === undefined || excludeTools.length === 0)
7
- return true;
8
- const excluded = new Set(excludeTools);
9
- return KNOWN_PERSISTENCE_TOOL_NAMES.some((name) => !excluded.has(name));
5
+ function isUnmounted(facts, name) {
6
+ return facts.excludeAllTools || (facts.excludeTools?.includes(name) ?? false);
7
+ }
8
+ function anyPersistenceToolSurvives(facts) {
9
+ return KNOWN_PERSISTENCE_TOOL_NAMES.some((name) => !isUnmounted(facts, name));
10
10
  }
11
11
  export function shouldDiscloseNoPersistentMemory(facts) {
12
12
  if (facts.memoryFaceMounted)
@@ -17,7 +17,7 @@ export function shouldDiscloseNoPersistentMemory(facts) {
17
17
  return true;
18
18
  if (facts.remoteExecutionEnv)
19
19
  return true;
20
- return !anyPersistenceToolSurvives(facts.excludeTools);
20
+ return !anyPersistenceToolSurvives(facts);
21
21
  }
22
22
  const NOTICE_BLOCK = { id: NO_PERSISTENT_MEMORY_SECTION_ID, slot: "behavior", text: NO_PERSISTENT_MEMORY_NOTICE };
23
23
  function mountNotice(decls) {
@@ -20,6 +20,43 @@ import type { Logger } from "../observability/logger.js";
20
20
  * registerTeams` 那条消费腿——本文件旧注里「registry pushes more at runtime via GET /api/config/effective → teams」
21
21
  * 是 doc-rot。今天 `registerTeams` 只剩公共信任边界这一层意义(调用方=测试),注册表在生产上恒等于 BUILTIN_TEAMS;
22
22
  * 中心侧的多 agent 讨论配置面是 `collab` 域(0.7 继任者),投影成命名 workflow(capabilities/collab-workflows.ts)。
23
+ *
24
+ * 🔴 **成员重派阶梯收窄(core 7.24.0 [ref];本仓 7.90.0 提货批的读数说明,零改动)** —— 一次讨论**花的子 run
25
+ * 数掉一半**,上游实测:两成员一轮的讨论从 **6 个子 run 变 3 个**。原因:`runTurn` 此前对**每一个**没有
26
+ * 「completed 且有正文」落地的成员轮都重派一次(同目标、同提示词、同模型、同工具面),包括那些**结局上
27
+ * 不可能变**的终局;现在只对引擎能**肯定**认出是确定性的两种终局拒绝重派(`blocked` 因由 = 宿主
28
+ * `UserPromptSubmit` 的拒绝,照字面信;`failed` 且治理表判 TERMINAL 的码,读 `classifySubagentError`)。
29
+ *
30
+ * 🔴 **这不是「丢了重试」,读数变低也不是故障**(写在这里是因为它唯一会被误读的地方就是账单与子代行数):
31
+ * · 其余一切**照旧**保留第二次尝试 —— 那个分类器的 `logic`/`unknown` 桶是**else 臂**(它不认识的码全落
32
+ * 那里),把「永不重派」挂在 else 臂上会让**将来每一个新码**默默变成不可重派,所以那条判决留在表里。
33
+ * ⚠️ **本句在 7.90.0(core 7.24.2 / [ref])修了措辞**:上一版写的是「`stream_torn` 住在 `logic` 桶里
34
+ * (else 臂)…负控挑它最有判别力」—— core 本版给三个 in-band 终局输出码**各自开了行**
35
+ * (`stream_torn → network`(transient)/ `refusal → logic` / `length_empty → logic`),所以
36
+ * `stream_torn` **已经不在 else 臂上了**(本车亲跑差分实测:`classifySubagentError({code:"stream_torn"})`
37
+ * 从 7.24.1 的 `{errorKind:"logic", retryable:false}` 变 7.24.2 的 `{errorKind:"network", retryable:true}`
38
+ * —— 注意 `errorKind` 与 `retryable` **两个字段都翻了**,提货单只点了后者)。
39
+ * **结局没变**(`stream_torn` 照旧重派 ⇒ 仍 6):重派闸 `canDifferOnRetry` 判的是「`blocked` 因由 + 治理表
40
+ * 判 TERMINAL」,从不读桶名 —— 变的是**理由**(从「掉进 else 臂」变成「表里那一行说它 transient」)。
41
+ * ⇒ 拿 `stream_torn` 当 **else 臂**负控从本版起**没有判别力了**(它是一条有行的码);要挑 else 臂代表,
42
+ * 挑**未知码** / **无码的 `failed`** / **空正文的 `completed`** —— 那三形才真的落 else 臂。
43
+ * · `[unavailable: <status>/<code>]` 标记、讨论转录、预算记账的**形一个字节没变**。
44
+ * · **本仓没有任何按子 run 数计费 / 计量的座**(2026-09-19 车JG 亲扫:`billable` 是 `http/route-table.ts` 上
45
+ * 「这条路由算不算一次计费提交」的路由性质,与子 run 无关;`council_runs_total` 数的是**议会被调用了几次**,
46
+ * 不是子代;成员轮不建 run-store 行,它们是 TaskRegistry 上的 `a*` 句柄)。⇒ 本版在本仓**零改动**,
47
+ * 唯一会动的读数是 `GET /v1/runs/:id/subagents` 的**行数**与经 `stats` 透传的**用量/花费**(都变低),
48
+ * 两者都是上游事实的透传、本仓不据行数做任何裁决。
49
+ *
50
+ * 🔴 **判据按「前缀族」写,不按具体码字**(test 仓对 core 7.24.0 的实测②,本仓照此记账;将来真在本仓开
51
+ * 计量点时钉法同此):引擎读的是治理表对**码**的裁决,而裁决是按**前缀族**给的 —— 一个族里凭空合成的码
52
+ * (test 实测的 `usage.FAKE_prefix_only`)照样判 TERMINAL ⇒ **3**。所以正控/负控各挑**一个**代表就够,
53
+ * 逐字抄那张码表只会在上游加一行的那天变成谎:
54
+ * · **⇒ 3(不重派)**:`blocked` 因由,以及 7 个被治理表判 TERMINAL 的前缀族失败码(合成同前缀码同判);
55
+ * · **⇒ 仍 6(照旧两次)**:两个 transient 码 `memory.admission_required` / `config.compliance_required`,
56
+ * 以及 `stream_torn` / `refusal` / `length_empty` / 未知码 / 无码的 `failed` / 空正文的 `completed`。
57
+ * 🔴 **这六形自 core 7.24.2([ref])起不再同源**:前三形在分类器里**各有一行**
58
+ * (`stream_torn → network` transient / `refusal`、`length_empty` → `logic`),后三形才是真的 **else 臂**。
59
+ * 结局(仍 6)对六形都没变,但**负控要挑真 else 臂**:未知码 / 无码的 `failed` / 空正文的 `completed`。
23
60
  */
24
61
  type TeamFn = typeof runTeamDiscussion;
25
62
  export interface TeamMemberTemplate {