@sema-agent/server 7.69.0 → 7.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +4 -1
  3. package/USAGE.md +109 -10
  4. package/dist/approval-ask-machine.d.ts +1 -1
  5. package/dist/approval-card.d.ts +3 -2
  6. package/dist/approval-content-kind.d.ts +13 -7
  7. package/dist/approval-content-kind.js +3 -1
  8. package/dist/approval-reconciler.d.ts +0 -54
  9. package/dist/approval-reconciler.js +2 -2
  10. package/dist/approval.js +3 -2
  11. package/dist/bench/s1/live-deps.js +2 -0
  12. package/dist/boot/execution-env.js +12 -2
  13. package/dist/boot/memory-consolidation.d.ts +2 -1
  14. package/dist/boot/parked-revive-gate.d.ts +2 -0
  15. package/dist/boot/parked-revive-gate.js +15 -10
  16. package/dist/boot/resolve-spec.js +8 -3
  17. package/dist/boot/runner-deps.d.ts +50 -46
  18. package/dist/boot/runner-deps.js +12 -10
  19. package/dist/brain.d.ts +3 -1
  20. package/dist/brain.js +2 -1
  21. package/dist/budget.js +3 -2
  22. package/dist/config-catalog.d.ts +31 -2
  23. package/dist/config-catalog.js +44 -24
  24. package/dist/config-types.d.ts +31 -1
  25. package/dist/config-types.js +1 -0
  26. package/dist/config.js +15 -3
  27. package/dist/deployment-governance.d.ts +6 -3
  28. package/dist/deployment-governance.js +2 -1
  29. package/dist/execution-lane-caps.d.ts +51 -37
  30. package/dist/execution-lane-caps.js +23 -0
  31. package/dist/fleet/fleet-bus.d.ts +22 -1
  32. package/dist/fleet/fleet-bus.js +2 -1
  33. package/dist/http/routes/approvals-assistant.js +3 -12
  34. package/dist/http/routes/diagnostics.js +4 -0
  35. package/dist/http/routes/runs.js +2 -3
  36. package/dist/http/routes/tasks.js +22 -18
  37. package/dist/http/server.js +21 -7
  38. package/dist/index.d.ts +1 -1
  39. package/dist/leader/wire.js +4 -3
  40. package/dist/main.js +6 -4
  41. package/dist/memory-operator-faces.d.ts +1 -1
  42. package/dist/memory-scope.d.ts +24 -0
  43. package/dist/memory-scope.js +12 -3
  44. package/dist/observability/metrics.d.ts +4 -6
  45. package/dist/observability/metrics.js +5 -1
  46. package/dist/observability/permission-rule-events.d.ts +20 -0
  47. package/dist/observability/permission-rule-events.js +28 -0
  48. package/dist/observability/run-terminal-log.d.ts +13 -22
  49. package/dist/observability/run-terminal-log.js +187 -47
  50. package/dist/observability/tool-trace.d.ts +12 -0
  51. package/dist/observability/tool-trace.js +5 -1
  52. package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
  53. package/dist/orchestration/workflow-agent-session-index.js +38 -5
  54. package/dist/parked-decide.d.ts +40 -1
  55. package/dist/parked-decide.js +19 -2
  56. package/dist/plugins/checkpoint-store-sql.js +5 -5
  57. package/dist/plugins/local-checkpoint-store.js +3 -3
  58. package/dist/plugins/remote-env-adb.d.ts +11 -0
  59. package/dist/plugins/remote-env-adb.js +2 -0
  60. package/dist/plugins/remote-env-device.d.ts +9 -0
  61. package/dist/plugins/remote-env-device.js +3 -0
  62. package/dist/plugins/remote-env-e2b.d.ts +11 -0
  63. package/dist/plugins/remote-env-e2b.js +2 -0
  64. package/dist/plugins/remote-env-host.d.ts +11 -0
  65. package/dist/plugins/remote-env-host.js +2 -0
  66. package/dist/plugins/remote-env-k8s.d.ts +11 -0
  67. package/dist/plugins/remote-env-k8s.js +2 -0
  68. package/dist/plugins/remote-env-local-docker.d.ts +11 -0
  69. package/dist/plugins/remote-env-local-docker.js +2 -0
  70. package/dist/plugins/remote-env-ssh.d.ts +11 -0
  71. package/dist/plugins/remote-env-ssh.js +2 -0
  72. package/dist/plugins/store-backend.d.ts +36 -13
  73. package/dist/plugins/store-backend.js +12 -3
  74. package/dist/plugins/store-contracts.d.ts +54 -18
  75. package/dist/plugins/store-contracts.js +17 -30
  76. package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
  77. package/dist/plugins/workflow-journal-store-sql.js +3 -10
  78. package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
  79. package/dist/plugins/workflow-run-store-sql.js +28 -1
  80. package/dist/posture-source.d.ts +23 -3
  81. package/dist/posture-source.js +1 -1
  82. package/dist/read-face-posture.d.ts +8 -1
  83. package/dist/read-face-posture.js +1 -0
  84. package/dist/run-cancel-context.d.ts +50 -1
  85. package/dist/run-cancel-context.js +22 -0
  86. package/dist/run-local.d.ts +14 -6
  87. package/dist/run-local.js +4 -1
  88. package/dist/runs.d.ts +0 -51
  89. package/dist/runs.js +40 -25
  90. package/dist/task-a2a.js +2 -2
  91. package/dist/task-mcp.js +2 -2
  92. package/dist/task-settings.d.ts +21 -8
  93. package/dist/task-settings.js +2 -2
  94. package/dist/tool-approval.d.ts +14 -8
  95. package/dist/tool-approval.js +3 -3
  96. package/dist/trace/core-keyset-guard.d.ts +135 -11
  97. package/dist/trace/engine-notice-wire.d.ts +1 -1
  98. package/dist/trace/engine-notice-wire.js +1 -0
  99. package/dist/trace/ledger-events.d.ts +22 -2
  100. package/dist/trace/ledger-events.js +6 -1
  101. package/dist/trace/ledger-sink.d.ts +10 -8
  102. package/dist/trace/ledger-sink.js +10 -2
  103. package/dist/trace/project.d.ts +24 -6
  104. package/dist/trace/project.js +5 -3
  105. package/dist/trace/redact.d.ts +28 -0
  106. package/dist/trace/redact.js +58 -30
  107. package/package.json +3 -3
@@ -6,6 +6,7 @@ import { join } from "node:path";
6
6
  import { tmpdir } from "node:os";
7
7
  import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, hasDestroy, } from "@sema-agent/core";
8
8
  import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
9
+ import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
9
10
  import { createDurableAskPolicy } from "../approval.js";
10
11
  import { presignS3Url } from "../plugins/s3-presign.js";
11
12
  import { buildUploadScript, uploadStepSuffix, fetchUploadedDiff, withStaging } from "./diffup.js";
@@ -376,7 +377,7 @@ export function createLeaderRunner(cfg) {
376
377
  }
377
378
  if (!cfg.e2bApiKey)
378
379
  throw new Error("leader wire: no envFactory+s3 (factory mode) and no e2bApiKey (static mode)");
379
- const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
380
+ const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
380
381
  if (!(await env.connect()).ok)
381
382
  throw new Error(`connect failed for ${sub.workerId}`);
382
383
  await sh(env)(`${body.seedCmd}; cd ${repo}; git checkout -q -b ${sub.branch}`);
@@ -410,7 +411,7 @@ export function createLeaderRunner(cfg) {
410
411
  }
411
412
  if (!cfg.e2bApiKey)
412
413
  throw new Error("leader wire: no envFactory and no e2bApiKey");
413
- const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
414
+ const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
414
415
  if (!(await env.connect()).ok) {
415
416
  await env.destroy().catch(() => { });
416
417
  throw new Error("integ connect failed");
@@ -449,7 +450,7 @@ export function createLeaderRunner(cfg) {
449
450
  else {
450
451
  if (!cfg.e2bApiKey)
451
452
  throw new Error("leader wire: repair-loop grader needs an envFactory or e2bApiKey");
452
- const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
453
+ const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
453
454
  if (!(await e2b.connect()).ok) {
454
455
  await e2b.destroy().catch(() => { });
455
456
  throw new Error("repair-loop grader connect failed");
package/dist/main.js CHANGED
@@ -131,7 +131,8 @@ async function main() {
131
131
  await configCenter.applyLocalRemoteExec();
132
132
  const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
133
133
  const instanceId = uuidv7();
134
- const sessionCaptureRecordStore = backend?.sessionCaptureRecords();
134
+ const sessionCaptureRecords = backend?.sessionCaptureRecords();
135
+ const sessionCaptureRecordStore = sessionCaptureRecords?.();
135
136
  const memoryBundleFaces = memoryEngine ? createMemoryBundleFaces(memoryEngine, { ...(sessionCaptureRecordStore !== undefined ? { captureRecordStore: sessionCaptureRecordStore } : {}) }) : undefined;
136
137
  const memoryComplianceFaces = memoryEngine
137
138
  ? createMemoryComplianceFaces(memoryEngine, {
@@ -271,8 +272,8 @@ async function main() {
271
272
  sessionShellGateExplicitlyOff: sessionShellGateRegistry.explicitlyOff,
272
273
  config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
273
274
  pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
274
- sessionCaptureRecordStore,
275
- memorySyncRunner, toolResultStore: runnerOffloadStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
275
+ sessionCaptureRecords,
276
+ memorySyncRunner, toolResultStore: runnerOffloadStore, checkpointStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
276
277
  legacyRewindBoundaryProbe: backend?.legacyRewindBoundaryProbe ? backend.legacyRewindBoundaryProbe.bind(backend) : undefined,
277
278
  permissionRuleStore: permissionRuleProvider,
278
279
  executionEnvFactory, lspManager, fleetBus, deploymentHooks, workflowRunStore, workflowJournalStore,
@@ -330,6 +331,7 @@ async function main() {
330
331
  const subRunnerSessions = new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 }));
331
332
  const subRunnerDeps = {
332
333
  ...createSharedRunnerDeps({
334
+ metrics,
333
335
  config,
334
336
  logger,
335
337
  hands: commitHands,
@@ -345,6 +347,7 @@ async function main() {
345
347
  rosterStore,
346
348
  deploymentHooks,
347
349
  toolResultStore: runnerOffloadStore,
350
+ checkpointStore,
348
351
  sessionPolicyStore,
349
352
  usageWindowStore,
350
353
  orgMemoryAdmission,
@@ -356,7 +359,6 @@ async function main() {
356
359
  loadProjectMemory: runnerDeps.loadProjectMemory ? runnerDeps.loadProjectMemory : undefined,
357
360
  probeInstructionSources: runnerDeps.probeInstructionSources ? runnerDeps.probeInstructionSources : undefined,
358
361
  onError: runnerDeps.onError ? runnerDeps.onError : undefined,
359
- checkpointStore: checkpointStore ? checkpointStore : undefined,
360
362
  };
361
363
  const subRunner = new Runner(subRunnerDeps);
362
364
  const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
@@ -227,7 +227,7 @@ export interface SessionMemoryStatusFace {
227
227
  *
228
228
  * `captureRecordStore` = [ref]② 的 SQL 载体(main.ts 单实例,与 Runner/其余引擎同源 —— 全站同批注入律,
229
229
  * 机器钉 capture-optout-lane ②):`captureOptedOut` / `optOutSource` 两键经它读,多副本部署上答的才是
230
- * 跨副本的真记录。缺席(local 车道)⇒ core 文件三腿,单机字节不变。
230
+ * 跨副本的真记录。S-215 起 local 车道在引擎接线时也供(core 文件三腿,同一只控制面,单机字节不变);缺席 ⇒ 记忆面暗。
231
231
  *
232
232
  * 面**不抛**(core 契约:sessionMemoryStatus never throws —— 每个事实源各自 catch 折缺席键);
233
233
  * 不接 onIncident:这条只读路径上今天零 sink 站点,而且缺席键本身就是它的披露形。
@@ -104,6 +104,30 @@ export declare function memoryEngineRootFor(config: Pick<ServiceConfig, "memoryE
104
104
  * 所以它的根必须走这条链而不是 `memoryEngineRootFor` 的分派(codex 修复验证轮 [medium]:分派会把 run-local 的
105
105
  * 记忆搬到 `<localDataRoot>/memory-work`,存量记忆与控制面升级后隐形)。 */
106
106
  export declare function fileMemoryEngineRoot(config: Pick<ServiceConfig, "memoryEngineDir">, fallbackRoot?: string): string;
107
+ /** 本部署 file 记忆引擎的**平面三坐标**(根 / 记忆目录 / 控制面目录)—— {@link fileMemoryPlaneFor} 一次算齐。 */
108
+ export interface FileMemoryPlane {
109
+ readonly root: string;
110
+ readonly memoryDir: string;
111
+ readonly controlDir: string;
112
+ }
113
+ /**
114
+ * S-215(B-085)—— **file 记忆引擎平面的单铸点**,兼这条腿的**接线门**。
115
+ *
116
+ * 为什么是一只函数而不是两处各自推导:平面坐标有**两个**消费者 —— ① {@link memoryEngineBackendFor}
117
+ * 钉给 `FileMemoryEngineBackend` 的 `memoryDir` + `controlDir`;② local 车道的 capture opt-out **记录店**
118
+ * (`StoreBackend.sessionCaptureRecords()` 喂 core 的 `fileSessionCaptureRecordStore(controlDir)`)。
119
+ * 两者必须是**同一只目录**,否则声明写进 A、引擎读 B ⇒ 一条被「受理」的隐私声明在引擎面根本不存在,
120
+ * 那比一句诚实的 409 更坏。两处各写一份推导链 = 哪天根链改了只改一处的必然漂移,所以推导(根 → 记忆目录 →
121
+ * 控制面目录,三步都在这里、只写一遍)与门合并在此(合并复审 R37:此前 `join(root, "memory")` 在本函数与
122
+ * `memoryEngineBackendFor` 各写一份 —— 记忆目录规则有了第二个写者,现已收回一处)。
123
+ *
124
+ * 返回 `undefined` ⇔ 本部署上 file 记忆引擎**不接线**(多租户 / `MEMORY_ENGINE=off` / 平面分裂车道未 allow)。
125
+ * 门与 `memoryEngineBackendFor` 逐字同源(它就读本函数),所以「有引擎 ⇔ 有控制面 ⇔ 供记录店」三者
126
+ * 在构造上不可能分家。
127
+ */
128
+ export declare function fileMemoryPlaneFor(config: ServiceConfig, fallbackRoot?: string): FileMemoryPlane | undefined;
129
+ /** 控制面目录那一坐标(local 车道 capture 记录店的依托)—— 只是 {@link fileMemoryPlaneFor} 的投影,不是第二条推导。 */
130
+ export declare function fileMemoryControlDirFor(config: ServiceConfig, fallbackRoot?: string): string | undefined;
107
131
  export declare function memoryEngineBackendFor(config: ServiceConfig, fallbackRoot?: string): {
108
132
  backend: FileMemoryEngineBackend;
109
133
  root: string;
@@ -68,14 +68,23 @@ export function memoryEngineRootFor(config, fallbackRoot) {
68
68
  export function fileMemoryEngineRoot(config, fallbackRoot) {
69
69
  return resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot);
70
70
  }
71
- export function memoryEngineBackendFor(config, fallbackRoot) {
71
+ export function fileMemoryPlaneFor(config, fallbackRoot) {
72
72
  if (config.requirePrincipal === true || !config.memoryEngineEnabled)
73
73
  return undefined;
74
74
  if (memoryEngineRemoteLanePosture(config)?.posture === "dark")
75
75
  return undefined;
76
76
  const root = fileMemoryEngineRoot(config, fallbackRoot);
77
- const dir = join(root, "memory");
78
- return { backend: new FileMemoryEngineBackend(dir, { controlDir: deriveControlPlaneDir(root, dir) }), root };
77
+ const memoryDir = join(root, "memory");
78
+ return { root, memoryDir, controlDir: deriveControlPlaneDir(root, memoryDir) };
79
+ }
80
+ export function fileMemoryControlDirFor(config, fallbackRoot) {
81
+ return fileMemoryPlaneFor(config, fallbackRoot)?.controlDir;
82
+ }
83
+ export function memoryEngineBackendFor(config, fallbackRoot) {
84
+ const plane = fileMemoryPlaneFor(config, fallbackRoot);
85
+ if (plane === undefined)
86
+ return undefined;
87
+ return { backend: new FileMemoryEngineBackend(plane.memoryDir, { controlDir: plane.controlDir }), root: plane.root };
79
88
  }
80
89
  export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy, memoryCapture) {
81
90
  if (!scope)
@@ -1,9 +1,3 @@
1
- /**
2
- * Tiny in-process metrics registry with Prometheus text exposition — no dependency.
3
- *
4
- * Per-instance (each container exposes its own counters); aggregate across instances at the scraper.
5
- * Supports labeled counters, gauges (static + dynamic), and duration histograms.
6
- */
7
1
  export type Labels = Record<string, string>;
8
2
  export declare class Metrics {
9
3
  private counters;
@@ -51,6 +45,10 @@ export interface MetricsSummary {
51
45
  rateLimited: number;
52
46
  costQuotaRejected: number;
53
47
  budgetExceeded: Record<string, number>;
48
+ /** 答后撞顶(walltime/turns)的计数。⚠️ 与 {@link budgetExceeded} **口径不同**:本枚在 deps 级通告席上铸,
49
+ * 覆盖**每一条**引擎 run(后台子代 / 委派嵌套腿 / leader 车道);`budgetExceeded` 只由 HTTP 提交腿的
50
+ * `finalizeTaskResult` 递增(= 顶层 HTTP 提交的 run)。两枚相加只在后者那个较窄的总体里成立。 */
51
+ limitCeilingAfterAnswer: Record<string, number>;
54
52
  degraded: Record<string, number>;
55
53
  degenerate: Record<string, number>;
56
54
  memoryConsolidationOps: Record<string, number>;
@@ -1,3 +1,4 @@
1
+ import { buildPermissionRuleEventsHelp } from "./permission-rule-events.js";
1
2
  const DEFAULT_BUCKETS = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60];
2
3
  function labelKey(labels) {
3
4
  return Object.keys(labels)
@@ -173,6 +174,7 @@ export class Metrics {
173
174
  rateLimited: ctotal("rate_limited_total"),
174
175
  costQuotaRejected: ctotal("cost_quota_rejected_total"),
175
176
  budgetExceeded: cby("budget_exceeded_total", "code"),
177
+ limitCeilingAfterAnswer: cby("limit_ceiling_after_answer_total", "origin"),
176
178
  degraded: cby("degraded_total", "reason"),
177
179
  degenerate: cby("degenerate_total", "class"),
178
180
  memoryConsolidationOps: cby("memory_consolidation_ops_total", "op"),
@@ -214,6 +216,7 @@ export function createMetrics() {
214
216
  ]);
215
217
  m.counter("tool_calls_total", "Tool invocations by name and outcome");
216
218
  m.counter("budget_exceeded_total", "Tasks failed by a budget/limit ceiling, by errorCode");
219
+ m.counter("limit_ceiling_after_answer_total", "Runs where a walltime/turns ceiling fired AFTER the run had already answered (engine 7.13.0: these end completed with NO limits.* code). Dual of budget_exceeded_total, but NOT over the same population: this one is minted on the deps-level notice sink so it sees EVERY engine run (background children, delegated/nested legs, leader lanes); budget_exceeded_total is incremented only by the HTTP submit legs finalizeTaskResult, i.e. top-level HTTP-submitted runs. Sum them only within that narrower population; see the forwarder comment, by origin");
217
220
  m.counter("cost_quota_rejected_total", "Tasks refused because the principal is over its cumulative cost quota");
218
221
  m.counter("degraded_total", "Tasks degraded to a cheaper model (1.40), by reason (budget/rate_limit/breaker_open)");
219
222
  m.counter("task_model_not_allowlisted_resume_total", "Resumed legs whose model/compactionModel is outside the @-mention allowlist (allowed to keep running by design — the allowlist gates fresh picks only)");
@@ -260,8 +263,9 @@ export function createMetrics() {
260
263
  m.counter("stale_cache_conflict_total", "Warm session-cache entries evicted by a cross-instance write while cached (S25)");
261
264
  m.counter("breaker_writethrough_failed_total", "Circuit-breaker cross-replica write-through failures (LOW), by backend");
262
265
  m.counter("web_search_bad_payload_total", "Web-search provider responses whose results field was not an array (LOW), by provider");
266
+ m.counter("auto_mode_classifier_unavailable_total", "Auto-mode gate denials caused by a classifier OUTAGE, by cause (core AUTO_MODE_UNAVAILABLE_CAUSES: error/timeout; 'other' = this build cannot name the word). Distinct from permission_denied_total{deniedBy=\"classifier\"}, which also counts genuine safety blocks — a spike here means the classifier itself is failing (gateway/credential fault, or too slow), not that the model kept asking for dangerous things");
263
267
  m.counter("permission_denied_total", "Tool-gate denials by the LAYER whose verdict is the deny (core's DeniedBy: policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution/persisted_rule; 'other' = this build cannot name the layer; label is `deniedBy` since server 7.64.0 — it was `source` over core's retired PermissionDeniedSource; goal B4 always-on deny meter)");
264
- m.counter("permission_rule_events_total", "Persisted allow-rule seam events by event (persisted_rule_allowed = a rule authorized a call with nobody asked; rule_store_unreadable = the store failed to read so the call was adjudicated with ZERO rules)");
268
+ m.counter("permission_rule_events_total", buildPermissionRuleEventsHelp());
265
269
  m.counter("compaction_events_total", "Compaction lifecycle events by outcome (started/completed/failed/skipped) and trigger");
266
270
  m.counter("hook_llm_calls_total", "prompt/agent hook entries that completed a model call (hooks 3b), by type");
267
271
  m.counter("auth_bridge_verify_total", "Registry SSO JWT verifications (auth-bridge), by outcome");
@@ -0,0 +1,20 @@
1
+ /** 计量面的 `event` 词表(顺序 = HELP 行里的出现顺序)。 */
2
+ export declare const PERMISSION_RULE_EVENT_TABLE: readonly [{
3
+ readonly event: "persisted_rule_allowed";
4
+ readonly meaning: "a persisted allow rule authorized the call and nobody was asked";
5
+ }, {
6
+ readonly event: "rule_store_unreadable";
7
+ readonly meaning: "the rule store failed to read, so the call was adjudicated with ZERO rules (expect an approval storm whose cause is the store, not the model)";
8
+ }, {
9
+ readonly event: "read_only_allowed";
10
+ readonly meaning: "the engine's read-only shell verdict cleared an ask that was NOT an operator-written rule (core decisionReason \"read_only\"; an operator-declared ask carries matchedAskRule and is never cleared this way)";
11
+ }];
12
+ /** 词表里的一个词(闭集)。 */
13
+ export type PermissionRuleEventWord = (typeof PERMISSION_RULE_EVENT_TABLE)[number]["event"];
14
+ /** 这一帧是否进 `permission_rule_events_total`(判别式与词表同源,不再手抄第二串 `===`)。 */
15
+ export declare function isPermissionRuleEventKind(kind: string): boolean;
16
+ /** 帧名 → `event` 标签值。调用方须先过 {@link isPermissionRuleEventKind}。 */
17
+ export declare function permissionRuleEventWordOf(kind: string): string;
18
+ /** `/metrics` 的 HELP 行文案(从词表派生 —— 加词自动出现,不存在「第二份会漂的散文」)。 */
19
+ export declare function buildPermissionRuleEventsHelp(): string;
20
+ //# sourceMappingURL=permission-rule-events.d.ts.map
@@ -0,0 +1,28 @@
1
+ const PERMISSION_TRACE_PREFIX = "permission.";
2
+ export const PERMISSION_RULE_EVENT_TABLE = [
3
+ {
4
+ event: "persisted_rule_allowed",
5
+ meaning: "a persisted allow rule authorized the call and nobody was asked",
6
+ },
7
+ {
8
+ event: "rule_store_unreadable",
9
+ meaning: "the rule store failed to read, so the call was adjudicated with ZERO rules (expect an approval storm whose cause is the store, not the model)",
10
+ },
11
+ {
12
+ event: "read_only_allowed",
13
+ meaning: "the engine's read-only shell verdict cleared an ask that was NOT an operator-written rule (core decisionReason \"read_only\"; an operator-declared ask carries matchedAskRule and is never cleared this way)",
14
+ },
15
+ ];
16
+ const PERMISSION_RULE_EVENT_KINDS = new Set(PERMISSION_RULE_EVENT_TABLE.map((r) => `${PERMISSION_TRACE_PREFIX}${r.event}`));
17
+ export function isPermissionRuleEventKind(kind) {
18
+ return PERMISSION_RULE_EVENT_KINDS.has(kind);
19
+ }
20
+ export function permissionRuleEventWordOf(kind) {
21
+ return kind.slice(PERMISSION_TRACE_PREFIX.length);
22
+ }
23
+ export function buildPermissionRuleEventsHelp() {
24
+ return ("Permission allow-layer attribution events by event — the ONE answer to \"why was I not asked this time?\" (" +
25
+ PERMISSION_RULE_EVENT_TABLE.map((r) => `${r.event} = ${r.meaning}`).join("; ") +
26
+ ")");
27
+ }
28
+ //# sourceMappingURL=permission-rule-events.js.map
@@ -1,4 +1,4 @@
1
- import type { RunRecord } from "../plugins/store-contracts.js";
1
+ import { type RunRecord } from "../plugins/store-contracts.js";
2
2
  /** 一次终局写口交给观测面的原料(store 侧装配,**未脱敏** —— 脱敏在本模块内做,见 {@link notifyRunTerminal})。 */
3
3
  export interface RunTerminalRecord {
4
4
  /** 这条 run 的账本 id = `GET /v1/runs/:id` 的 id(本仓 run 行主键名为 taskId,同一个值)。 */
@@ -20,8 +20,9 @@ export type RunTerminalObserver = (level: "error" | "info", fields: Record<strin
20
20
  /**
21
21
  * 终局行的级别判据。`undefined` = 不打行。
22
22
  *
23
- * `blocked` 刻意不打:它是**治理判决**(门拒绝了这次执行),不是失败——它有自己的门遥测与
24
- * `GET /v1/runs/:id` 读面,混进 `run_failed` 会把「模型/网络出事了」这条读法冲淡。
23
+ * 一句话:**非成功终局才出声**(`failed` / `blocked`),用户主动取消降 `info`。词的分类不在本模块做 ——
24
+ * 它的唯一属主是 `plugins/store-contracts.ts` 的 `RUN_STATUS_CLASS` 表(S-213③:本模块此前手抄了一张
25
+ * 穷举 switch,并在 `blocked` 那一格给出与 fleet 读面互斥的答案)。表的编译期双向钉在属主处。
25
26
  */
26
27
  export declare function runTerminalLogLevel(status: RunRecord["status"], errorCode: string | undefined): "error" | "info" | undefined;
27
28
  /** 装配点(`main.ts` 唯一)。传 `undefined` 卸载(测试隔离)。 */
@@ -52,26 +53,15 @@ export declare const REDACT_INPUT_CAP = 65536;
52
53
  * 脱敏**之后**按字节封顶(占位已替换完,切在占位中间不是泄漏)并带标记。 */
53
54
  export declare const REDACT_OUTPUT_MAX_BYTES = 65000;
54
55
  export declare function redactErrorMessage(raw: string | undefined): string | undefined;
55
- /**
56
- * 同口:终态 `result` 整块也持久化(`GET /v1/runs/:id` 直出),写入前把里面的**自由文本座**脱敏,其余键
57
- * 逐字不动;`undefined`/`null` 透传。
58
- *
59
- * 🔴 S-136(core 7.6.0 [ref] D-8):自由文本座**搬家了** —— 失败文案与阻塞理由从顶层平面键
60
- * (`errorMessage` / `blockedReason`)搬进因由 `terminal` 的两条臂(`failed.message` / `blocked.reason`)。
61
- * 本函数**两种形都认**,而这不是「兼容读臂」(codex 对抗复审 r1 [high],亲读验真后**改判**;首版删掉了旧键
62
- * 臂,那是错的):本函数是**读盘面**,而 `task_run.result` / `task_event(done).result` 里躺着 7.63.0 及以前
63
- * 写下的**真字节**,它们本来就是平面形。写口只剩因由一条(没有任何东西会再写出旧形),所以这里不存在
64
- * 「两个写者」;而不认旧形的代价是**可复现的凭据回放**:上线前落盘的原文经 `GET /v1/runs/:id/events` 重新
65
- * 明文外发(读面这一层纵深正是为那批行存在的)。⇒ 规则仍只有一条:**读盘认盘上真有的形,写盘只写一种形**。
66
- *
67
- * 读面也走本函数,所以 `terminal` 形上取 `unknown`:旧持久 blob 与非 core 的 Runner 实现给的形不可信,
68
- * 运行期逐臂判形、不抛(与 `remoteEnvFailures` / `editedFiles` 两座同款不信任)。
69
- */
56
+ /** {@link redactTerminalResult} 会碰的全部顶层键 = 现行形的 `"redacted"` 行 ∪ 旧形平面键;
57
+ * `memoRedactTerminalResult` 的记忆键由它派生(两处同源,见 `RESULT_KEY_TREATMENT` 顶注)。 */
58
+ export declare const REDACTABLE_RESULT_LEAVES: readonly string[];
70
59
  export declare function redactTerminalResult<T extends {
71
60
  terminal?: unknown;
72
- /** 旧形(≤7.63.0)的两个顶层自由文本座;形上取 `unknown` —— 读盘面对盘上字节不作假设。 */
61
+ /** 旧形(≤7.63.0)的三个顶层座;形上取 `unknown` —— 读盘面对盘上字节不作假设。 */
73
62
  errorMessage?: unknown;
74
63
  blockedReason?: unknown;
64
+ checkpointGate?: unknown;
75
65
  remoteEnvFailures?: readonly {
76
66
  message?: string | undefined;
77
67
  }[] | undefined;
@@ -94,9 +84,10 @@ export declare function redactWorkerReportError<T extends {
94
84
  error?: string | undefined;
95
85
  checkpointGate?: unknown;
96
86
  }>(report: T): T;
97
- /** codex r5 [high]:`WorkerReport.checkpointGate`(core `CheckpointGate`)整块随 LeaderResult 落库/直出——`reason` 与
98
- * `riskDescriptor.summary`/`shadowedRule` 是自由文本([ref] 实测 summary 带 PEM 明文;[ref] R3 登记),按类型表脱这三叶;
99
- * `kind`/`toolName`/`axes`/`severity`/`touchedPaths` 闭集或路径,逐字。非对象形原样返回。 */
87
+ export declare function redactRiskDescriptor(rd: unknown): unknown;
88
+ /** `CheckpointGate`(core 类型)整块上 wire —— `reason` 与 `riskDescriptor` 的叶族是自由文本
89
+ * ([ref] 实测 summary 带 PEM 明文;[ref] R3 登记;[ref] 把它扩到结果/账本两面)。逐键**按表**处置,
90
+ * 叶表属主见 {@link redactRiskDescriptor}。非对象形原样返回(同一引用);字节没变也回同一引用。 */
100
91
  export declare function redactCheckpointGate(gate: unknown): unknown;
101
92
  /** 工件字节(patch / *.rej 拒绝块):codex r6 [high]——拒绝块能带 `git clone https://u:TOKEN@host` 或 `_authToken=` 行,
102
93
  * 「工件保真」不能让原凭据上元数据读面。做**原位密钥形替换**(与预览同一张规则表,字节只在密钥处变)、**不截断**;
@@ -1,24 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { redactSecrets } from "../trace/redact.js";
3
+ import { isParkLedgerEventType } from "../trace/ledger-events.js";
4
+ import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
3
5
  import { errorText } from "./err-text.js";
4
6
  import { recordFailOpen } from "./fail-open.js";
5
7
  const MESSAGE_CAP = 1024;
6
8
  export function runTerminalLogLevel(status, errorCode) {
7
- switch (status) {
8
- case "failed":
9
- return errorCode === "cancelled" ? "info" : "error";
10
- case "blocked":
11
- case "completed":
12
- case "running":
13
- case "suspended":
14
- case "needs_review":
15
- return undefined;
16
- default: {
17
- const unhandled = status;
18
- void unhandled;
19
- return undefined;
20
- }
21
- }
9
+ if (!isUnsuccessfulRunStatus(status))
10
+ return undefined;
11
+ return errorCode === "cancelled" ? "info" : "error";
22
12
  }
23
13
  let observer;
24
14
  export function setRunTerminalObserver(fn) {
@@ -75,6 +65,37 @@ export function redactErrorMessage(raw) {
75
65
  }
76
66
  }
77
67
  const MAX_EDITED_FILES = 1000;
68
+ const RESULT_KEY_TREATMENT = {
69
+ terminal: "redacted",
70
+ remoteEnvFailures: "redacted",
71
+ editedFiles: "redacted",
72
+ taskId: "verbatim",
73
+ runId: "verbatim",
74
+ sessionId: "verbatim",
75
+ model: "verbatim",
76
+ effectiveReasoning: "verbatim",
77
+ result: "verbatim",
78
+ salvagedOutput: "verbatim",
79
+ apiFailure: "verbatim",
80
+ retryAfterMs: "verbatim",
81
+ degraded: "verbatim",
82
+ structuredOutput: "verbatim",
83
+ rewindNotes: "verbatim",
84
+ haltedOnUserRejection: "verbatim",
85
+ haltedByUser: "verbatim",
86
+ strandedHumanAnswers: "verbatim",
87
+ pendingApproval: "verbatim",
88
+ approvalWaitedMs: "verbatim",
89
+ effectiveReadFace: "verbatim",
90
+ effectiveReadDenyPatterns: "verbatim",
91
+ effectiveMemoryScopes: "verbatim",
92
+ stats: "verbatim",
93
+ };
94
+ const LEGACY_RESULT_LEAVES = ["errorMessage", "blockedReason", "checkpointGate"];
95
+ export const REDACTABLE_RESULT_LEAVES = [
96
+ ...Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t === "redacted").map(([k]) => k),
97
+ ...LEGACY_RESULT_LEAVES,
98
+ ];
78
99
  export function redactTerminalResult(result) {
79
100
  if (result === undefined || result === null)
80
101
  return result;
@@ -89,14 +110,41 @@ export function redactTerminalResult(result) {
89
110
  out.blockedReason = redactErrorMessage(r.blockedReason);
90
111
  changed = true;
91
112
  }
113
+ if (r.checkpointGate !== undefined) {
114
+ const legacyGate = redactCheckpointGate(r.checkpointGate);
115
+ if (legacyGate !== r.checkpointGate) {
116
+ out.checkpointGate = legacyGate;
117
+ changed = true;
118
+ }
119
+ }
92
120
  if (r.terminal !== null && typeof r.terminal === "object") {
93
121
  const t = r.terminal;
94
- if (t.kind === "failed" && typeof t.message === "string") {
95
- out.terminal = { ...t, message: redactErrorMessage(t.message) };
96
- changed = true;
122
+ let nt;
123
+ if (t.kind === "failed" && typeof t.message === "string")
124
+ nt = { ...t, message: redactErrorMessage(t.message) };
125
+ else if (t.kind === "blocked" && typeof t.reason === "string")
126
+ nt = { ...t, reason: redactErrorMessage(t.reason) };
127
+ const gateOf = (cause) => {
128
+ if (cause === null || typeof cause !== "object" || !("gate" in cause))
129
+ return undefined;
130
+ const g = cause.gate;
131
+ const ng = redactCheckpointGate(g);
132
+ return { gate: ng, changed: ng !== g };
133
+ };
134
+ const base = nt ?? t;
135
+ if (t.kind === "paused") {
136
+ const g = gateOf(base);
137
+ if (g?.changed === true)
138
+ nt = { ...base, gate: g.gate };
97
139
  }
98
- else if (t.kind === "blocked" && typeof t.reason === "string") {
99
- out.terminal = { ...t, reason: redactErrorMessage(t.reason) };
140
+ else if (t.kind === "failed") {
141
+ const np = base.nestedPause;
142
+ const g = gateOf(np);
143
+ if (g?.changed === true)
144
+ nt = { ...base, nestedPause: { ...np, gate: g.gate } };
145
+ }
146
+ if (nt !== undefined) {
147
+ out.terminal = nt;
100
148
  changed = true;
101
149
  }
102
150
  }
@@ -136,6 +184,11 @@ export function redactLedgerEventData(type, data) {
136
184
  return data;
137
185
  return { ...data, errorMessage: redactErrorMessage(msg) };
138
186
  }
187
+ if (isParkLedgerEventType(type)) {
188
+ const gate = data.gate;
189
+ const next = redactCheckpointGate(gate);
190
+ return next === gate ? data : { ...data, gate: next };
191
+ }
139
192
  return data;
140
193
  }
141
194
  export function redactWorkerReportError(report) {
@@ -145,38 +198,123 @@ export function redactWorkerReportError(report) {
145
198
  return report;
146
199
  return { ...report, ...(error !== report.error ? { error } : {}), ...(gate !== report.checkpointGate ? { checkpointGate: gate } : {}) };
147
200
  }
201
+ const RISK_DESCRIPTOR_TREATMENT = {
202
+ severity: "verbatim",
203
+ axes: "verbatim",
204
+ toolName: "verbatim",
205
+ shellGateDoctrine: "verbatim",
206
+ summary: "text",
207
+ touchedPaths: "text[]",
208
+ shadowedRule: "text",
209
+ probeReason: "text",
210
+ probeCause: "probe-cause",
211
+ };
212
+ const PROBE_CAUSE_TREATMENT = {
213
+ code: "verbatim",
214
+ roots: "operands",
215
+ further: "operands",
216
+ };
217
+ const PROBE_CAUSE_OPERANDS_TREATMENT = {
218
+ shown: "text[]",
219
+ total: "verbatim",
220
+ };
221
+ export function redactRiskDescriptor(rd) {
222
+ if (rd === null || typeof rd !== "object" || Array.isArray(rd))
223
+ return rd;
224
+ const r = rd;
225
+ let changed = false;
226
+ const str = (v) => { const o = redactErrorMessage(v) ?? ""; if (o !== v)
227
+ changed = true; return o; };
228
+ const strs = (v) => (Array.isArray(v) ? v.map((x) => (typeof x === "string" ? str(x) : x)) : v);
229
+ const out = { ...r };
230
+ for (const [key, treatment] of Object.entries(RISK_DESCRIPTOR_TREATMENT)) {
231
+ switch (treatment) {
232
+ case "verbatim":
233
+ break;
234
+ case "text":
235
+ if (typeof r[key] === "string")
236
+ out[key] = str(r[key]);
237
+ break;
238
+ case "text[]":
239
+ if (Array.isArray(r[key]))
240
+ out[key] = strs(r[key]);
241
+ break;
242
+ case "probe-cause": {
243
+ const pc = r[key];
244
+ if (pc === null || typeof pc !== "object" || Array.isArray(pc))
245
+ break;
246
+ const p = pc;
247
+ const np = { ...p };
248
+ for (const [ck, ct] of Object.entries(PROBE_CAUSE_TREATMENT)) {
249
+ if (ct === "verbatim")
250
+ continue;
251
+ const ops = p[ck];
252
+ if (ops === null || typeof ops !== "object" || Array.isArray(ops))
253
+ continue;
254
+ const o = ops;
255
+ const no = { ...o };
256
+ for (const [ok, ot] of Object.entries(PROBE_CAUSE_OPERANDS_TREATMENT)) {
257
+ if (ot === "text[]" && Array.isArray(o[ok]))
258
+ no[ok] = strs(o[ok]);
259
+ }
260
+ np[ck] = no;
261
+ }
262
+ out[key] = np;
263
+ break;
264
+ }
265
+ default: {
266
+ const unhandled = treatment;
267
+ throw new Error(`unhandled leaf treatment: ${String(unhandled)}`);
268
+ }
269
+ }
270
+ }
271
+ return changed ? out : rd;
272
+ }
273
+ const CHECKPOINT_GATE_TREATMENT = {
274
+ kind: "verbatim",
275
+ reason: "text",
276
+ toolName: "verbatim",
277
+ riskDescriptor: "risk-descriptor",
278
+ safetyAxis: "verbatim",
279
+ realApproval: "verbatim",
280
+ resumeAfterMs: "verbatim",
281
+ };
148
282
  export function redactCheckpointGate(gate) {
149
283
  if (gate === null || typeof gate !== "object" || Array.isArray(gate))
150
284
  return gate;
151
285
  const g = gate;
152
- let changed = false;
153
286
  const out = { ...g };
154
- const str = (v) => { const o = redactErrorMessage(v) ?? ""; if (o !== v)
155
- changed = true; return o; };
156
- const strs = (v) => (Array.isArray(v) ? v.map((x) => (typeof x === "string" ? str(x) : x)) : v);
157
- if (typeof g.reason === "string")
158
- out.reason = str(g.reason);
159
- const rd = g.riskDescriptor;
160
- if (rd && typeof rd === "object" && !Array.isArray(rd)) {
161
- const r = rd;
162
- const nr = { ...r };
163
- for (const k of ["summary", "shadowedRule", "probeReason"])
164
- if (typeof r[k] === "string")
165
- nr[k] = str(r[k]);
166
- if (Array.isArray(r.touchedPaths))
167
- nr.touchedPaths = strs(r.touchedPaths);
168
- const pc = r.probeCause;
169
- if (pc && typeof pc === "object" && !Array.isArray(pc)) {
170
- const p = pc;
171
- const np = { ...p };
172
- for (const k of ["roots", "further"]) {
173
- const ops = p[k];
174
- if (ops && typeof ops === "object" && !Array.isArray(ops) && Array.isArray(ops.shown))
175
- np[k] = { ...ops, shown: strs(ops.shown) };
287
+ let changed = false;
288
+ for (const [key, treatment] of Object.entries(CHECKPOINT_GATE_TREATMENT)) {
289
+ switch (treatment) {
290
+ case "verbatim":
291
+ break;
292
+ case "text": {
293
+ const v = g[key];
294
+ if (typeof v !== "string")
295
+ break;
296
+ const next = redactErrorMessage(v) ?? "";
297
+ if (next !== v) {
298
+ out[key] = next;
299
+ changed = true;
300
+ }
301
+ break;
302
+ }
303
+ case "risk-descriptor": {
304
+ if (!(key in g))
305
+ break;
306
+ const next = redactRiskDescriptor(g[key]);
307
+ if (next !== g[key]) {
308
+ out[key] = next;
309
+ changed = true;
310
+ }
311
+ break;
312
+ }
313
+ default: {
314
+ const unhandled = treatment;
315
+ throw new Error(`unhandled gate field treatment: ${String(unhandled)}`);
176
316
  }
177
- nr.probeCause = np;
178
317
  }
179
- out.riskDescriptor = nr;
180
318
  }
181
319
  return changed ? out : gate;
182
320
  }
@@ -366,9 +504,11 @@ export function memoRedactTerminalResult(seat, result) {
366
504
  if (result === undefined || result === null)
367
505
  return result;
368
506
  const r = result;
369
- if (r.terminal === undefined && r.errorMessage === undefined && r.blockedReason === undefined && r.remoteEnvFailures === undefined)
507
+ if (REDACTABLE_RESULT_LEAVES.every((k) => r[k] === undefined))
370
508
  return result;
371
- const leaves = { terminal: r.terminal, errorMessage: r.errorMessage, blockedReason: r.blockedReason, remoteEnvFailures: r.remoteEnvFailures };
509
+ const leaves = {};
510
+ for (const k of REDACTABLE_RESULT_LEAVES)
511
+ leaves[k] = r[k];
372
512
  const redacted = memoRedactedRead(seat, leaves, (l) => redactTerminalResult(l));
373
513
  if (redacted === leaves)
374
514
  return result;
@@ -14,6 +14,18 @@ import type { Metrics } from "./metrics.js";
14
14
  * 🔴 BREAKING(server 7.64.0,core 7.6.0 提货):标签名 `source` → `deniedBy`,词表从七词
15
15
  * (`policy/hook/safety/shellGate/planMode/classifier/org`)换成八词
16
16
  * (`policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution`)。仪表盘/告警规则要改。
17
+ *
18
+ * 🔴 **第二个计数器 `auto_mode_classifier_unavailable_total{cause}`(server 7.70.0,core 7.12.0 [ref])**
19
+ * —— 分类器**停摆**的常开信号。7.12.0 之前这件事有两条可观测路:熔断跳闸时的
20
+ * `auto_mode_breaker_open_total`,以及「`unavailable` 的 ask 会走到人面前」(审批 inbox 里看得见)。
21
+ * 该版把熔断族整族退役、又把 `unavailable` 改成**当场 deny** ⇒ 两条路同时没了,而
22
+ * `permission_denied_total{deniedBy}` 对它是**瞎的**:分类器停摆与分类器正常拦下一次危险调用,
23
+ * 两者都记 `deniedBy:"classifier"`。一次持续停摆于是在默认监控上与「今天安全拦截多了点」不可区分
24
+ * (`TOOL_TRACE` 默认关,拿不到那半边)。core 同批把事实加在了 deny 上
25
+ * (`PermissionDeniedPayload.classifierUnavailable`),本仓就在**这同一只常开观察者**上消费它。
26
+ * 两个计数刻意**不合并成一个标签**:它们答两个问题(「谁拒的」vs「这次拒是不是因为分类器停摆、哪一类」),
27
+ * 而运维对停摆要立刻动手(改配置 / 等重试 / 看后端),对安全拦截不要。
28
+ * ⛔ 纯计量:不改任何裁决、不新开观察面、事实缺席时**不铸**(缺席 ≠ 「分类器好着呢」,只是这次拒不是它)。
17
29
  */
18
30
  export declare function createPermissionDeniedMeter(metrics: Pick<Metrics, "inc">): Hooks;
19
31
  /**