@sema-agent/server 7.56.0 → 7.58.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 (121) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +2 -2
  3. package/USAGE.md +101 -9
  4. package/dist/approval-ask-audit-store.d.ts +100 -1
  5. package/dist/approval-ask-audit-store.js +103 -2
  6. package/dist/approval-card.d.ts +148 -13
  7. package/dist/approval-card.js +82 -13
  8. package/dist/approval.d.ts +31 -0
  9. package/dist/approval.js +18 -0
  10. package/dist/auto-mode-face.d.ts +111 -0
  11. package/dist/auto-mode-face.js +99 -0
  12. package/dist/bench/s1/arms.js +5 -5
  13. package/dist/bench/s1/live-deps.js +11 -11
  14. package/dist/bench/s1/run-firm.js +3 -0
  15. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  16. package/dist/bench/s1/runner-ctx.js +7 -0
  17. package/dist/boot/config-center.js +23 -1
  18. package/dist/boot/coordinators.js +13 -1
  19. package/dist/boot/parked-revive-gate.d.ts +7 -2
  20. package/dist/boot/parked-revive-gate.js +12 -1
  21. package/dist/boot/resolve-spec.d.ts +3 -0
  22. package/dist/boot/resolve-spec.js +3 -1
  23. package/dist/boot/runner-deps.d.ts +12 -0
  24. package/dist/boot/runner-deps.js +28 -3
  25. package/dist/boot/runtime-caps.d.ts +19 -9
  26. package/dist/boot/runtime-caps.js +49 -21
  27. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  28. package/dist/boot/session-shell-gate-registry.js +21 -0
  29. package/dist/boot/stores.js +12 -5
  30. package/dist/config-catalog.js +14 -5
  31. package/dist/config-center/http-client.js +7 -11
  32. package/dist/config-center/read-warnings.d.ts +20 -0
  33. package/dist/config-center/read-warnings.js +36 -0
  34. package/dist/config-provider.d.ts +3 -0
  35. package/dist/config-provider.js +4 -8
  36. package/dist/config-types.d.ts +41 -10
  37. package/dist/config.d.ts +21 -0
  38. package/dist/config.js +40 -5
  39. package/dist/hooks/hook-runner.js +8 -8
  40. package/dist/http/route-ctx.d.ts +75 -0
  41. package/dist/http/routes/a2a-serve.js +5 -3
  42. package/dist/http/routes/approvals-assistant.js +19 -6
  43. package/dist/http/routes/capabilities.js +21 -4
  44. package/dist/http/routes/memory-origin.d.ts +2 -2
  45. package/dist/http/routes/rules.js +3 -2
  46. package/dist/http/routes/runs.d.ts +0 -14
  47. package/dist/http/routes/runs.js +20 -8
  48. package/dist/http/routes/tasks.js +34 -8
  49. package/dist/http/routes/workflows.js +19 -4
  50. package/dist/http/server.d.ts +11 -1
  51. package/dist/http/server.js +215 -43
  52. package/dist/http/wire-types.d.ts +9 -4
  53. package/dist/leader/diffout.js +2 -1
  54. package/dist/leader/endpoint.js +45 -12
  55. package/dist/leader/fanout.js +6 -5
  56. package/dist/leader/leader.js +17 -14
  57. package/dist/leader/merge.js +17 -12
  58. package/dist/leader/planner.js +3 -2
  59. package/dist/leader/repair-oracle.js +6 -4
  60. package/dist/leader/repair-wire.js +5 -3
  61. package/dist/leader/wire.d.ts +30 -1
  62. package/dist/leader/wire.js +36 -19
  63. package/dist/main.js +43 -7
  64. package/dist/observability/err-text.d.ts +6 -0
  65. package/dist/observability/err-text.js +10 -0
  66. package/dist/observability/fail-open.d.ts +40 -0
  67. package/dist/observability/fail-open.js +19 -0
  68. package/dist/observability/metrics.js +2 -2
  69. package/dist/observability/run-terminal-log.d.ts +120 -0
  70. package/dist/observability/run-terminal-log.js +360 -0
  71. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  72. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  73. package/dist/permission-rule-vocab.d.ts +40 -0
  74. package/dist/permission-rule-vocab.js +17 -0
  75. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  76. package/dist/plugins/checkpoint-store-sql.js +19 -3
  77. package/dist/plugins/file-run-store.d.ts +3 -34
  78. package/dist/plugins/file-run-store.js +19 -0
  79. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  80. package/dist/plugins/local-checkpoint-store.js +3 -0
  81. package/dist/plugins/mailbox-store-sql.d.ts +28 -9
  82. package/dist/plugins/mailbox-store-sql.js +72 -10
  83. package/dist/plugins/memory-run-store.d.ts +3 -15
  84. package/dist/plugins/memory-run-store.js +19 -0
  85. package/dist/plugins/permission-rule-store-file.js +8 -3
  86. package/dist/plugins/permission-rule-store-sql.js +16 -10
  87. package/dist/plugins/remote-scratchpad.js +3 -2
  88. package/dist/plugins/run-store-sql.d.ts +3 -42
  89. package/dist/plugins/run-store-sql.js +30 -1
  90. package/dist/plugins/store-backend.d.ts +1 -1
  91. package/dist/plugins/store-contracts.d.ts +66 -1
  92. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  93. package/dist/plugins/workflow-run-store-sql.js +10 -2
  94. package/dist/rules-consent.js +20 -13
  95. package/dist/run-cancel-context.d.ts +13 -0
  96. package/dist/run-cancel-context.js +15 -0
  97. package/dist/run-local.js +2 -2
  98. package/dist/runs.d.ts +4 -1
  99. package/dist/runs.js +41 -10
  100. package/dist/runtime-caps-resolver.d.ts +133 -17
  101. package/dist/runtime-caps-resolver.js +41 -3
  102. package/dist/task-settings.d.ts +57 -3
  103. package/dist/task-settings.js +78 -5
  104. package/dist/task-workflow.d.ts +32 -2
  105. package/dist/task-workflow.js +8 -3
  106. package/dist/tool-approval.d.ts +26 -3
  107. package/dist/tool-approval.js +96 -13
  108. package/dist/trace/core-keyset-guard.d.ts +7 -8
  109. package/dist/trace/engine-notice-wire.d.ts +1 -1
  110. package/dist/trace/engine-notice-wire.js +2 -0
  111. package/dist/trace/ledger-sink.d.ts +13 -4
  112. package/dist/trace/ledger-sink.js +14 -6
  113. package/dist/trace/project.d.ts +58 -3
  114. package/dist/trace/project.js +33 -1
  115. package/dist/trace/redact.d.ts +22 -11
  116. package/dist/trace/redact.js +660 -21
  117. package/dist/trace/sema-provenance.d.ts +35 -0
  118. package/dist/trace/sema-provenance.js +14 -0
  119. package/dist/turn-activity.d.ts +32 -2
  120. package/dist/turn-activity.js +29 -4
  121. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import { contentKindOf } from "./approval-content-kind.js";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
- import { uuidv7, MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
3
+ import { uuidv7, MAX_RULE_TEXT_CHARS, parseAllowRuleText } from "@sema-agent/core";
4
4
  import { precheckCardRuleText } from "./rules-consent.js";
5
5
  import { redactDeep, redactSecrets } from "./trace/redact.js";
6
6
  import { hasBidiControls } from "./text-bidi.js";
@@ -8,7 +8,7 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
8
8
  import { createLogger } from "./observability/logger.js";
9
9
  import { recordFailOpen } from "./observability/fail-open.js";
10
10
  import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
11
- import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, } from "./approval-card.js";
11
+ import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
12
12
  import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
13
13
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
14
14
  const defaultLogger = createLogger();
@@ -195,7 +195,7 @@ export function createApprovalCardEmitter(sinks) {
195
195
  }
196
196
  export function resolveApprovalLeg(input) {
197
197
  if (!input.streamApprovalOn)
198
- return { active: false, windowZero: false };
198
+ return { active: false, windowZero: input.windowMs === 0 };
199
199
  const tightDeadline = input.legWalltimeMs !== undefined && input.legWalltimeMs - input.windowMarginMs <= 0;
200
200
  if (input.windowMs === 0 || tightDeadline)
201
201
  return { active: true, windowZero: true };
@@ -344,32 +344,115 @@ export class ToolApprovalCoordinator {
344
344
  if (x.kind === "batch" && y.kind === "batch") {
345
345
  return (x.uncoveredSegments === y.uncoveredSegments &&
346
346
  x.rules.length === y.rules.length &&
347
- x.rules.every((r, j) => r.rule === y.rules[j].rule && r.match === y.rules[j].match && r.command === y.rules[j].command && r.segment === y.rules[j].segment));
347
+ x.rules.every((r, j) => ToolApprovalCoordinator.batchMemberEqual(r, y.rules[j])) &&
348
+ ToolApprovalCoordinator.uncoveredDetailEqual(x.uncoveredDetail, y.uncoveredDetail));
348
349
  }
349
350
  return false;
350
351
  });
351
352
  }
353
+ static batchMemberEqual(r, s) {
354
+ if (r.kind !== s.kind)
355
+ return false;
356
+ if (r.kind === "command" && s.kind === "command")
357
+ return r.rule === s.rule && r.match === s.match && r.command === s.command && r.segment === s.segment;
358
+ if (r.kind === "directoryRead" && s.kind === "directoryRead")
359
+ return r.rule === s.rule && r.directory === s.directory && r.segment === s.segment;
360
+ return false;
361
+ }
362
+ static uncoveredDetailEqual(a, b) {
363
+ if (a === undefined || b === undefined)
364
+ return a === b;
365
+ return a.length === b.length && a.every((d, i) => d.segment === b[i].segment && d.reason === b[i].reason);
366
+ }
367
+ static ruleOfferTextsOverSize(offer) {
368
+ const over = (s) => s.length > MAX_RULE_TEXT_CHARS;
369
+ if (offer.kind === "single")
370
+ return over(offer.rule) || over(offer.command);
371
+ return offer.rules.some((r) => {
372
+ switch (r.kind) {
373
+ case "command":
374
+ return over(r.rule) || over(r.command);
375
+ case "directoryRead":
376
+ return over(r.rule) || over(r.directory);
377
+ default:
378
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
379
+ }
380
+ });
381
+ }
382
+ static ruleOfferDerivedFactsAgree(offer) {
383
+ const derive = (rule) => {
384
+ const p = parseAllowRuleText(rule);
385
+ return "rule" in p ? p.rule : undefined;
386
+ };
387
+ if (offer.kind === "single") {
388
+ const d = derive(offer.rule);
389
+ return d !== undefined && d.rule === offer.rule && d.tool === "Bash" && d.match === offer.match && d.command === offer.command;
390
+ }
391
+ return offer.rules.every((m) => {
392
+ const d = derive(m.rule);
393
+ if (d === undefined || d.rule !== m.rule)
394
+ return false;
395
+ switch (m.kind) {
396
+ case "command":
397
+ return d.tool === "Bash" && d.match === m.match && d.command === m.command;
398
+ case "directoryRead":
399
+ return d.tool === "Read" && d.match === "subpath" && d.command === m.directory;
400
+ default:
401
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(m)}`);
402
+ }
403
+ });
404
+ }
352
405
  static boundRuleOfferTexts(offers) {
353
406
  const over = (s) => s.length > MAX_RULE_TEXT_CHARS;
407
+ const clipSegment = (s) => (over(s) ? s.slice(0, MAX_RULE_TEXT_CHARS) : s);
354
408
  const out = [];
355
- for (const offer of offers) {
409
+ for (const raw of offers) {
410
+ const parsed = RuleOfferRawSchema.safeParse(screenUncoveredDetailSeat(backfillLegacyBatchMemberKind(raw)));
411
+ if (!parsed.success) {
412
+ defaultLogger.warn("rule_offer_dropped_unreadable_shape", {
413
+ kind: typeof raw === "object" && raw !== null ? String(raw.kind) : typeof raw,
414
+ note: "this offer's shape is outside the closed vocabulary this build reads (member kind / match / reason) — dropped whole; sibling offers still render (core/dist skew?)",
415
+ });
416
+ continue;
417
+ }
418
+ const offer = parsed.data;
419
+ if (ToolApprovalCoordinator.ruleOfferTextsOverSize(offer))
420
+ return undefined;
421
+ if (!ToolApprovalCoordinator.ruleOfferDerivedFactsAgree(offer)) {
422
+ defaultLogger.warn("rule_offer_dropped_derived_facts_disagree", {
423
+ kind: offer.kind,
424
+ note: "an offer's kind/match/command/directory seats do not agree with what the shared validator derives from its rule text — dropped whole so the card never shows one grant while redeeming another (core/dist skew or forged material?)",
425
+ });
426
+ continue;
427
+ }
356
428
  switch (offer.kind) {
357
429
  case "single":
358
- if (over(offer.rule) || over(offer.command))
359
- return undefined;
360
- out.push(offer);
430
+ out.push({ kind: "single", rule: offer.rule, match: offer.match, command: offer.command });
361
431
  break;
362
- case "batch":
363
- if (offer.rules.some((r) => over(r.rule) || over(r.command)))
364
- return undefined;
432
+ case "batch": {
433
+ const rules = [];
434
+ for (const r of offer.rules) {
435
+ switch (r.kind) {
436
+ case "command":
437
+ rules.push({ kind: "command", rule: r.rule, match: r.match, command: r.command, segment: clipSegment(r.segment) });
438
+ break;
439
+ case "directoryRead":
440
+ rules.push({ kind: "directoryRead", rule: r.rule, directory: r.directory, segment: clipSegment(r.segment) });
441
+ break;
442
+ default:
443
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
444
+ }
445
+ }
365
446
  out.push({
366
447
  kind: "batch",
367
- rules: offer.rules.map((r) => (over(r.segment) ? { ...r, segment: r.segment.slice(0, MAX_RULE_TEXT_CHARS) } : r)),
448
+ rules,
368
449
  uncoveredSegments: offer.uncoveredSegments,
450
+ ...(offer.uncoveredDetail !== undefined ? { uncoveredDetail: offer.uncoveredDetail.map((d) => ({ segment: clipSegment(d.segment), reason: d.reason })) } : {}),
369
451
  });
370
452
  break;
453
+ }
371
454
  default:
372
- return undefined;
455
+ throw new Error(`unknown rule-offer kind: ${JSON.stringify(offer)}`);
373
456
  }
374
457
  }
375
458
  return out;
@@ -17,11 +17,11 @@
17
17
  * ⑨⑩([ref],2026-08-31)把两个最大的 wire 面也纳入:TaskSpec 按「server 有无写点」记 Wired/Unwired,
18
18
  * TaskResult 按「转发/摘除」记 Forwarded/Stripped —— 谁改 resolve-spec.ts / runs.ts 的发射点谁同步本文件。
19
19
  */
20
- import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxStore, TaskSpec, TaskResult } from "@sema-agent/core";
20
+ import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxAppendMessage, TaskSpec, TaskResult } from "@sema-agent/core";
21
21
  /** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。 */
22
22
  type AssertAllKeysHandled<T extends never> = T;
23
- type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure";
24
- type NotificationExcluded = "peer";
23
+ type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure" | "agentMessage" | "_sema_provenance";
24
+ type NotificationExcluded = "peer" | "crossSessionMessage";
25
25
  type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected | NotificationExcluded>>;
26
26
  type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId";
27
27
  type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "description" | "agentType" | "name" | "currentAction" | "currentTool" | "parentSessionId" | "startedAt" | "workflowRunId" | "progressTaskId" | "progressParentTaskId" | "model";
@@ -29,14 +29,13 @@ type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, Bg
29
29
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
30
30
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
31
31
  type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval";
32
- type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld";
32
+ type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
33
33
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
34
34
  type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end";
35
35
  type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
36
- type MailboxProjected = "seq" | "from" | "content" | "sentAt" | "hopChain";
36
+ type MailboxProjected = "seq" | "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
37
37
  type _GuardMailbox = AssertAllKeysHandled<Exclude<keyof MailboxMessage, MailboxProjected>>;
38
- type MailboxAppendMessage = Parameters<MailboxStore["append"]>[2];
39
- type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain";
38
+ type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
40
39
  type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
41
40
  type PermissionTraceKind = Extract<TraceEvent["kind"], `permission.${string}`>;
42
41
  type PermissionTraceProjected = "permission.persisted_rule_allowed" | "permission.rule_store_unreadable";
@@ -49,7 +48,7 @@ type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<Permiss
49
48
  type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls";
50
49
  type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
51
50
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
52
- type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends";
51
+ type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested";
53
52
  type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
54
53
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
55
54
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
@@ -16,7 +16,7 @@
16
16
  * 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
17
17
  * 刚立,立完就在下一次加码时自己咬住了)。
18
18
  */
19
- export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval"];
19
+ export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition"];
20
20
  export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
21
21
  /** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
22
22
  export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
@@ -15,6 +15,8 @@ export const ENGINE_NOTICE_WIRE_CODES = [
15
15
  "memory.capture_optout_unpersisted",
16
16
  "task.halt_unconsumed",
17
17
  "task.late_approval",
18
+ "config.durable_gate_unavailable",
19
+ "peer.inbound_disposition",
18
20
  ];
19
21
  const WIRE_CODES = new Set(ENGINE_NOTICE_WIRE_CODES);
20
22
  export function isEngineNoticeWireCode(code) {
@@ -58,8 +58,14 @@ export interface LedgerSink {
58
58
  onForwardEvent(e: {
59
59
  type?: string;
60
60
  }): void;
61
- /** Park event (durable F4 suspend / [ref] D-B review pause): ONLY the non-secret `checkpointGate` is
62
- * persisted — never the capability `checkpointToken` (search [18] Q1b). */
61
+ /** Park event (durable F4 suspend / [ref] D-B review pause): ONLY the `checkpointGate` is persisted —
62
+ * never the capability `checkpointToken` (search [18] Q1b).
63
+ * ⚠️ **"non-secret" was the old wording and it is not true** ([ref] R3, 2026-09-03 — wording only, behaviour
64
+ * unchanged; [ref] decides the arm): the gate carries NO capability token, but its `riskDescriptor.summary` /
65
+ * `shadowedRule` are model-/human-controlled display text that [ref] measured carrying a PEM private-key
66
+ * body verbatim. This durable row replays on every `GET /v1/runs/:id/events` reconnect and is NOT backfilled
67
+ * by core [ref]'s mint-point fix — it is the third face of the `checkpointGate` sister item recorded in
68
+ * `http/routes/approvals-assistant.ts` (`redactPendingDisclosures` header). */
63
69
  appendParked(kind: "suspended" | "needs_review", result: unknown): Promise<void>;
64
70
  /** Terminal `done` event. `result` must already be checkpoint-token-stripped (and, on the bg leg,
65
71
  * model-usage-attached) by the caller. */
@@ -101,7 +107,10 @@ export declare function createLedgerSink(opts: {
101
107
  * ——**text_delta/reasoning_delta 只进内存缓冲、到 flush 边界才 append**(codex S1-F1,2026-08-13:
102
108
  * 只打 append 会让长时间流式输出被误读成「无进展」,方向与本键的设计目的正好相反);②`append`
103
109
  * 入口——兜住不经 onEvent 的直写路径(appendParked/appendDone/onForwardEvent)。分诊提示性回调:
104
- * 抛错不得断链(打点失败丢的只是判别材料,账本不能陪葬)。 */
105
- onActivity?: () => void;
110
+ * 抛错不得断链(打点失败丢的只是判别材料,账本不能陪葬)。
111
+ * 🔴 [ref] 件3:回调带**事件类型**(`kind`)——`cancelContext.lastEventKind` 的数据源。两个打点位
112
+ * 各传自己看得见的那个词(位①=流事件 `ev.type`,位②=账本行 `type`);拿不到类型的打点方
113
+ * (core 的 onActivity 第五席、resume 腿手动打点)省参,`kind` 诚实缺席(见 turn-activity.ts)。 */
114
+ onActivity?: (kind?: string, statusData?: Record<string, unknown>) => void;
106
115
  }): LedgerSink;
107
116
  //# sourceMappingURL=ledger-sink.d.ts.map
@@ -17,15 +17,15 @@ export function createLedgerSink(opts) {
17
17
  let text = "";
18
18
  let reasoning = "";
19
19
  let textEventId;
20
- const beat = () => {
20
+ const beat = (kind, statusData) => {
21
21
  try {
22
- opts.onActivity?.();
22
+ opts.onActivity?.(kind, statusData);
23
23
  }
24
24
  catch {
25
25
  }
26
26
  };
27
27
  const append = (type, data) => {
28
- beat();
28
+ beat(type);
29
29
  const step = chain.then(() => opts.appendEvent(++seq, type, data));
30
30
  chain = step.then(() => undefined, () => undefined);
31
31
  return step;
@@ -41,7 +41,7 @@ export function createLedgerSink(opts) {
41
41
  }
42
42
  };
43
43
  const onEvent = async (ev) => {
44
- beat();
44
+ beat(ev.type);
45
45
  switch (ev.type) {
46
46
  case "text_delta": {
47
47
  const td = ev;
@@ -63,7 +63,11 @@ export function createLedgerSink(opts) {
63
63
  break;
64
64
  case "status":
65
65
  await flush();
66
- await append("status", brainStatusEventData(ev));
66
+ {
67
+ const statusData = brainStatusEventData(ev);
68
+ beat("status", statusData);
69
+ await append("status", statusData);
70
+ }
67
71
  break;
68
72
  case "context_usage":
69
73
  await flush();
@@ -134,7 +138,11 @@ export function createLedgerSink(opts) {
134
138
  void append("tool_end", toolEndEventData(e)).catch(() => undefined);
135
139
  }
136
140
  };
137
- const appendStatus = (ev) => append("status", brainStatusEventData(ev));
141
+ const appendStatus = (ev) => {
142
+ const statusData = brainStatusEventData(ev);
143
+ beat("status", statusData);
144
+ return append("status", statusData);
145
+ };
138
146
  const appendParked = (kind, result) => append(kind, {
139
147
  gate: result?.checkpointGate ?? null,
140
148
  ...(typeof result?.toolCallId === "string" ? { toolCallId: result.toolCallId } : {}),
@@ -16,6 +16,7 @@ import type { ApprovalSettledBy, TaskResult, SystemInjectionPriority } from "@se
16
16
  import { type AskDenyResolution } from "@sema-agent/core";
17
17
  import type { RunEvent, RunRecord } from "../plugins/store-contracts.js";
18
18
  import type { ModelUsageDelta, ModelUsageTracker, PromptManifestRecord } from "../budget.js";
19
+ import { type SemaProvenanceInput } from "./sema-provenance.js";
19
20
  import type { LedgerEventType } from "./ledger-events.js";
20
21
  /** 账本写口的统一签名 —— `type` 是**闭集**(`ledger-events.ts`):新型必须先登记,否则调用点编译红
21
22
  * (codex 交叉复审 F2:别名门 `ap("x")` 与动态首参让纯词法门结构性抓不全)。 */
@@ -214,6 +215,18 @@ export declare function taskNotificationEventData(ev: {
214
215
  status?: number;
215
216
  requestId?: string;
216
217
  };
218
+ /** [ref](a) [ref] 片6 d1(core 7.2.0):子代→主代 uplink 帧的**渲染判别**成员。在场 = 这一帧是
219
+ * `SendMessage("main")` 的 agent-message(cli 渲 agent-message 卡;父模型面读的是顶层 `<agent-message from=…>`
220
+ * 帧而非 task-notification 外壳);缺席 = 老形卡。`body` 是子代文本 = UNTRUSTED(与 `result` 同姿势 redact);
221
+ * `from` 是 core 铸的发送方标签(引擎值,verbatim)。铸造点唯一:core `agents/send-message-tool.ts` uplink 腿。 */
222
+ agentMessage?: {
223
+ from: string;
224
+ body: string;
225
+ };
226
+ /** 同批:typed 归因侧记(wire 键;core 契约「a host projects it, the model never sees it」)。成员逐个挑取,
227
+ * `kind` 过闭集(`trace/sema-provenance.ts` 单点,park 半场同一判据);`agentType` 本 mount 恒缺席(工具无
228
+ * agent-type 席),在场即透传(fleet spawn 帧按 taskId 可 join 时上游给)。 */
229
+ _sema_provenance?: SemaProvenanceInput;
217
230
  };
218
231
  eventId?: string;
219
232
  parentToolCallId?: string;
@@ -330,8 +343,47 @@ export declare function workspaceChangedEventData(ev: {
330
343
  * ⚠️ 与 `storeWired` **不同**:本仓这两条至今未接(`RunnerDeps.permissionRuleSyncWired` /
331
344
  * `permissionRuleOrg` 全树零声明点,2026-08-10 [ref] 复扫亲验)⇒ 今天恒 `false`,这是**真值**不是
332
345
  * 缺席。哪天接上其中任一条,连本句一起改 —— 这一行是它的登记处。
346
+ * · `modelGate`(可选段 `{ class, removed[], restore }`;core fc52b4d4 [ref] / [ref] 片2b,[ref] 提货件,
347
+ * 黑板 [ref] @server①)—— **裁:租户可见**。它是 per-session 的**读面**:「我这条 session 因为工具-模型门卸掉了
348
+ * 哪些工具」——与 operator 通告 `config.tool_model_gate_removed` **同决策同源**(core 从同一张 trim 表铸两面),
349
+ * 但通告归 operator 车道(engine_notice 白名单,附录 D),本段归租户:消费端(cli L-70 [ref])要在**首帧一次读**
350
+ * 里知道自己少了哪些工具、怎么恢复,否则只能等到调用被拒才发现。三键都是引擎派生值:`class` = 合并 gate 表的 class 行键
351
+ * (prepare-task 对表外 class 响亮拒),`removed` = 工具 wire 名(core 表键,非用户内容),`restore` = core 铸的一句恢复指引(引擎文案)
352
+ * ⇒ 不脱敏、逐字。多 class 同卸 ⇒ core 报首 class + removed **并集**(core 合并律;本仓不按 class 过滤)。
353
+ * 仅本 run 有门卸时在场:**缺席 = 本 run 没卸**,不铸空段、不铸 `removed: []`。畸形(三键任一缺席/形不合)
354
+ * ⇒ 整段按缺席 —— 不铸半段,半段会被读成「只卸了这些」。它不构成 governance 四布尔的侧信道(门卸由
355
+ * 模型/门 class 决定,与 lockedConfig/compliance/memoryAdmission/retention 无函数关系)。
356
+ * · `autoMode`(`{ armed: boolean, reason: AutoModeArmReason }`;core 7.3.1 [ref],147③ 提货件,黑板 [ref])
357
+ * —— **裁:租户可见**。它是 per-leg 的**读面**:「我这条腿的 auto 模式武装了吗?没武装是卡在哪一臂?」
358
+ * ① **同主体同受众**:本仓早已把同一件事投在租户面上 —— `/v1/capabilities.permissionModeAuto.{armed,reason}`
359
+ * ([ref],7.57.0 已发)答的是「**若此刻提交**会不会武装」,本段答的是「**这条真跑起来的腿**武装了吗」;
360
+ * 同一个主体(调用方自己的 run)、同一条轴,只差事前/事后。剥掉它,壳就只能拿事前预测冒充事后事实;
361
+ * ② **两个值都是引擎派生的闭形**:`armed` 布尔,`reason` 是 core 的闭集词(`AUTO_MODE_ARM_REASONS`:
362
+ * armed / no_intent / no_face / denied / resolver_fault / latch_open)—— 非用户内容,不脱敏、逐字;
363
+ * ③ **不是 governance 侧信道**:它描述的是**本腿**的武装结论,与 lockedConfig/compliance/memoryAdmission/
364
+ * retention 四位无函数关系;而 `denied` 这一词所披露的「组织把这一位关了」在 [ref] 读面上**本就已经**
365
+ * 对同一租户可见(`org_denied`),本段没有扩大任何受众。
366
+ * 🔴 **逐字透传,不映射**:core 的六词与本仓 [ref] 读面的六词(`AUTO_MODE_UNARMED_REASONS`)**刻意不归一**
367
+ * —— 两张表回答的是两个问题、粒度也不同(core 的 `denied` 一词对应本仓的 `org_denied` / `local_denied`
368
+ * 两词:core 只看合成后的 caps deny 位,本仓分得出这个 false 是 center 下发的还是本地 env 写的)。
369
+ * ⚠️ **`settings_denied` 不折进 `denied`,它折进 `no_intent`**(codex 车BO r1 [medium] 验真后纠):
370
+ * settings 层的 kill-switch 折的是**模式**(`effectivePermissionMode`:auto → default),于是 resolve-spec
371
+ * 阶段④ 压根不写意图座 `TaskSpec.autoModeRequested` ⇒ core 的武装谓词第一项就落空;而本地 env 腿折的是
372
+ * **caps**(`applyLocalAutoModeDeny`),模式不动、座照写 ⇒ core 才报 `denied`。两条腿在 manifest 上
373
+ * **分不开**,要分辨得读 `/v1/capabilities`。对照表与保鲜钉在 `test/wiring-manifest-projection.test.ts`
374
+ * 的 147③ 节,分岔的行为面格在 `test/permission-mode-shell-gate-translation.test.ts`;
375
+ * 本函数**只透传**,一个字都不换。
376
+ * ⚠️ 缺席 ≠ 「不适用」:core 顶注逐字「absence here means an older mint or an external derivation, never
377
+ * "not applicable"」⇒ 缺席时**不铸** `armed:false`(那会把「老 mint 写的行」谎报成「这条腿确实没武装」)。
378
+ * ⚠️ **自相矛盾按缺席**:core 把 `armed ⇔ reason === "armed"` 写成段内不变量并在 derive 点拒(`readAutoModeFact`
379
+ * 抛)。账本回放/第三方 producer 送来的矛盾对若原样上 wire,按 `armed` 读与按 `reason` 读的消费端会得到
380
+ * **相反**的答案 —— 这条轴上(@cli `sema doctor permissions` 正读它)两个答案比没答案更坏 ⇒ 整段不铸。
381
+ * 这不是改语义:它与本函数「畸形值一律按缺席」是同一条规则,而这条不变量是 core 自己写的段内契约。
333
382
  * 未来 core 加一段而它没有 audience 标签时,**先在这里补一条裁定再决定挑不挑键** —— 段级完备性钉
334
- * (test/wiring-manifest-projection.test.ts)会在那一刻先把人拦下来。
383
+ * (test/wiring-manifest-projection.test.ts)会在那一刻先把人拦下来 —— ⚠️ **但那条钉是夹具驱动的**:它只看
384
+ * `deriveWiringManifest(EFFECTIVE_FACTS)` 真铸出来的段,facts 不带的可选段(147③ 的 `autoMode` 就是)它一格
385
+ * 都红不了(2026-09-04 实测:该钉 26/26 全绿,红出 `autoMode` 的是编译期门)。段级 + 成员级的**编译期**门在
386
+ * `core-keyset-guard.ts` ⑥b —— 那才是「core 加了段/加了成员」的可靠红点,两道并用缺一不可。
335
387
  */
336
388
  export declare function wiringManifestEventData(ev: {
337
389
  manifest?: unknown;
@@ -397,8 +449,9 @@ export declare function humanInputEventData(ev: {
397
449
  }): Record<string, unknown>;
398
450
  /** S4 (SILENT-FALLBACK P0-a): the durable `brain_status` observation row, shared by the bg + resume append
399
451
  * sites. core marks the live `status` frame EPHEMERAL (never replayed by core on resume) — this row is the
400
- * SERVICE's own observation record (why a turn stalled: rate_limited/retrying/reconnecting/circuit_open),
401
- * not a core event replay. Whitelist: the closed phase union + neutral bounded detail (redacted) + retryInSec. */
452
+ * SERVICE's own observation record (why a turn stalled: rate_limited/retrying/reconnecting/circuit_open, and
453
+ * since core 7.3.1 / [ref] the pre-failure `waiting_first_token` wait), not a core event replay.
454
+ * Whitelist: the closed phase union verbatim + neutral bounded detail (redacted) + the bounded numeric seats. */
402
455
  export declare function brainStatusEventData(ev: {
403
456
  phase: string;
404
457
  detail?: string;
@@ -409,6 +462,8 @@ export declare function brainStatusEventData(ev: {
409
462
  errClass?: string;
410
463
  retryAtMs?: number;
411
464
  errorStatus?: number;
465
+ elapsedMs?: number;
466
+ timeoutMs?: number;
412
467
  eventId?: string;
413
468
  parentToolCallId?: string;
414
469
  }): Record<string, unknown>;
@@ -1,6 +1,8 @@
1
+ import { redactErrorMessage } from "../observability/run-terminal-log.js";
1
2
  import { isApprovalSettledBy, isAskDenyResolution } from "@sema-agent/core";
2
3
  import { redactSecrets, redactDeep } from "./redact.js";
3
4
  import { injectionTierOrAbsent } from "./injection-tier.js";
5
+ import { semaProvenanceOrAbsent } from "./sema-provenance.js";
4
6
  export async function appendModelUsageDelta(append, modelUsage, taskId) {
5
7
  const delta = modelUsage?.drain(taskId);
6
8
  if (delta) {
@@ -151,6 +153,13 @@ export function taskNotificationEventData(ev) {
151
153
  ...(n.result !== undefined ? { result: redactSecrets(n.result) } : {}),
152
154
  ...(n.output_file !== undefined ? { output_file: redactSecrets(n.output_file) } : {}),
153
155
  ...(n.usage !== undefined ? { usage: n.usage } : {}),
156
+ ...(n.agentMessage !== undefined ? { agentMessage: { from: n.agentMessage.from, body: redactSecrets(n.agentMessage.body) } } : {}),
157
+ ...(() => {
158
+ if (n._sema_provenance === undefined)
159
+ return {};
160
+ const p = semaProvenanceOrAbsent(n._sema_provenance);
161
+ return p !== undefined ? { _sema_provenance: p } : {};
162
+ })(),
154
163
  ...(() => {
155
164
  const tier = injectionTierOrAbsent(ev.priority);
156
165
  return tier !== undefined ? { priority: tier } : {};
@@ -241,6 +250,25 @@ export function wiringManifestEventData(ev) {
241
250
  ...(bool(rulesIn.syncWired) !== undefined ? { syncWired: rulesIn.syncWired } : {}),
242
251
  ...(bool(rulesIn.orgGoverned) !== undefined ? { orgGoverned: rulesIn.orgGoverned } : {}),
243
252
  });
253
+ const gateIn = sec(m.modelGate);
254
+ const modelGate = (() => {
255
+ if (gateIn === undefined)
256
+ return undefined;
257
+ const cls = str(gateIn.class);
258
+ const restore = str(gateIn.restore);
259
+ const removed = Array.isArray(gateIn.removed) && gateIn.removed.every((r) => typeof r === "string") ? [...gateIn.removed] : undefined;
260
+ return cls !== undefined && restore !== undefined && removed !== undefined ? { class: cls, removed, restore } : undefined;
261
+ })();
262
+ const autoIn = sec(m.autoMode);
263
+ const autoMode = (() => {
264
+ if (autoIn === undefined)
265
+ return undefined;
266
+ const armed = bool(autoIn.armed);
267
+ const reason = str(autoIn.reason);
268
+ if (armed === undefined || reason === undefined)
269
+ return undefined;
270
+ return armed === (reason === "armed") ? { armed, reason } : undefined;
271
+ })();
244
272
  return {
245
273
  ...(num(m.schemaVersion) !== undefined ? { schemaVersion: m.schemaVersion } : {}),
246
274
  ...(str(legIn.kind) !== undefined ? { leg: { kind: legIn.kind } } : {}),
@@ -252,6 +280,8 @@ export function wiringManifestEventData(ev) {
252
280
  ...(str(sessionIn.store) !== undefined ? { session: { store: sessionIn.store } } : {}),
253
281
  ...(fleet !== undefined ? { fleet } : {}),
254
282
  ...(permissionRules !== undefined ? { permissionRules } : {}),
283
+ ...(modelGate !== undefined ? { modelGate } : {}),
284
+ ...(autoMode !== undefined ? { autoMode } : {}),
255
285
  ...identityFields(ev),
256
286
  };
257
287
  }
@@ -305,6 +335,8 @@ export function brainStatusEventData(ev) {
305
335
  ...(num(ev.maxRetries) ? { maxRetries: ev.maxRetries } : {}),
306
336
  ...(num(ev.retryInMs) ? { retryInMs: ev.retryInMs } : {}),
307
337
  ...(num(ev.retryAtMs) ? { retryAtMs: ev.retryAtMs } : {}),
338
+ ...(num(ev.elapsedMs) ? { elapsedMs: ev.elapsedMs } : {}),
339
+ ...(num(ev.timeoutMs) ? { timeoutMs: ev.timeoutMs } : {}),
308
340
  ...(num(ev.errorStatus) ? { errorStatus: ev.errorStatus } : {}),
309
341
  ...(typeof ev.errClass === "string" && ev.errClass.length > 0 ? { errClass: ev.errClass } : {}),
310
342
  ...identityFields(ev),
@@ -522,7 +554,7 @@ function mapTraceEventInner(type, seq, data) {
522
554
  case "needs_review":
523
555
  return { event: "done", data: { seq, suspended: true } };
524
556
  case "failed":
525
- return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message: redactSecrets(String(data.errorMessage ?? "run failed")) } };
557
+ return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message: redactErrorMessage(String(data.errorMessage ?? "run failed")) ?? "run failed" } };
526
558
  case "context_usage":
527
559
  case "compaction_outcome":
528
560
  case "wiring_manifest":
@@ -1,17 +1,28 @@
1
- export declare function setRedactionObserver(fn: ((patternLabel: string, count: number) => void) | undefined): void;
2
- /**
3
- * `opts.observe: false` = **照常脱敏,但这一遍不计数**(合并码重扫)
4
- *
5
- * 为什么需要它:本函数对自己的输出**值幂等、计数不幂等** —— URL-userinfo JSON 秘密字段两条 pattern
6
- * 会命中自己已经替换过的输出并再计一次。于是任何**重扫已脱敏文本**的读路径(耐久待批队列的回读腿,
7
- * `/v1/approvals/stream` 3s 一拍)都会持续给 `redactions_applied_total` 加数,把那个用来识别
8
- * 「过度脱敏 / pattern 从不触发」的指标灌成不可读。
1
+ /** 测试可见:扫描器 indexOf 实际扫过的字节数(摊还线性 常数 × 输入长) */
2
+ export declare function ppkScanBytesForTest(): number;
3
+ export declare function resetPpkScanBytesForTest(): void;
4
+ /** Redact unambiguous secret formats from reasoning/CoT before it is persisted to the durable trace log. */
5
+ /** S24 (SILENT-FALLBACK P1): optional redaction observer rewrites previously had zero fingerprint, so
6
+ * over-redaction (mangling legit content) and under-redaction (a pattern that never fires) were both
7
+ * invisible. main.ts wires this to `redactions_applied_total{pattern}`. Hot path: the observer only runs
8
+ * when a pattern actually matched; the label is derived once per pattern at module load. */
9
+ /** [ref]:第三参 `meta.arm` = 命中的是哪一组臂。加参而不是改参 —— 只读两参的既有观察者原样可用
10
+ * (TS 允许少形参),main.ts 把它铺成 `redactions_applied_total{pattern,arm}` 的第二个维度。
9
11
  *
10
- * 🔴 关的是**计数**,不是脱敏:重扫这一遍仍是纵深防御(手改过的行、未来某条写路径漏了脱敏,读面照样
11
- * 兜住)—— 少脱一次的代价是把凭证投上跨租户可见的运维队列,那条永远不能省。
12
- */
12
+ * 🔴 **词义如实**(codex r1 [medium] 三,采纳):这一维说的是「命中的是**哪一条臂**」,**不是**
13
+ * 「这段文本真的被上游截过」—— server 收到的是纯字节,core 的 `clip()` 是 `slice(0, 80)` **不留标记**
14
+ * (亲读 `@sema-agent/core/dist/agents/subagent-steps.js:9-11`),截过与天生就短在 wire 上不可区分。
15
+ * 所以一个 `arm="fragment"` 计数可能来自残片,也可能来自一个恰好落在阈值之上的 look-alike;
16
+ * core [ref] 落地后这一支**趋零是证据、不归零不是证明**。别把它当截断事实的度量用。 */
17
+ export type RedactionArm = "full" | "fragment";
18
+ export interface RedactionHitMeta {
19
+ /** `"fragment"` = {@link FRAGMENT_SECRET_PATTERNS} 里的前缀残片臂;`"full"` = 完整臂。闭集两词。 */
20
+ arm: RedactionArm;
21
+ }
22
+ export declare function setRedactionObserver(fn: ((patternLabel: string, count: number, meta: RedactionHitMeta) => void) | undefined): void;
13
23
  export declare function redactSecrets(text: string, opts?: {
14
24
  observe?: boolean;
25
+ json?: boolean;
15
26
  }): string;
16
27
  /**
17
28
  * 🔴 环 + 深度预算(2026-07-25 补)。原实现是纯递归、**既不记已访问节点也不限深度**,而它的调用位置是