@sema-agent/server 7.68.0 → 7.70.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.
- package/README.md +3 -0
- package/README.zh-CN.md +3 -0
- package/USAGE.md +122 -9
- package/dist/adoption/plan.js +11 -0
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +27 -0
- package/dist/approval-card.js +11 -0
- package/dist/approval-reconciler.d.ts +0 -54
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.js +5 -4
- package/dist/boot/config-center.d.ts +6 -7
- package/dist/boot/config-center.js +31 -8
- package/dist/boot/leader.js +1 -1
- package/dist/boot/parked-revive-gate.js +10 -9
- package/dist/boot/runner-deps.d.ts +35 -4
- package/dist/boot/runner-deps.js +2 -6
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +10 -2
- package/dist/budget.js +3 -2
- package/dist/config-catalog.d.ts +31 -2
- package/dist/config-catalog.js +61 -24
- package/dist/config-center/apply-effective.d.ts +8 -1
- package/dist/config-center/apply-effective.js +23 -9
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/read-face.d.ts +30 -1
- package/dist/config-center/read-face.js +33 -15
- package/dist/config-center/restart-signal.js +2 -1
- package/dist/config-center/types.d.ts +20 -5
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +17 -2
- package/dist/config.d.ts +0 -21
- package/dist/config.js +19 -6
- package/dist/http/route-ctx.d.ts +39 -1
- package/dist/http/routes/approvals-assistant.js +4 -12
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/diagnostics.js +4 -0
- package/dist/http/routes/runs.js +2 -3
- package/dist/http/routes/tasks.js +22 -18
- package/dist/http/routes/workflows.js +15 -7
- package/dist/http/server.d.ts +10 -2
- package/dist/http/server.js +74 -26
- package/dist/leader/wire.d.ts +9 -2
- package/dist/leader/wire.js +6 -6
- package/dist/main.js +8 -7
- package/dist/model-compat.d.ts +81 -0
- package/dist/model-compat.js +52 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/metrics.d.ts +0 -6
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/permission-rule-events.d.ts +20 -0
- package/dist/observability/permission-rule-events.js +28 -0
- package/dist/observability/run-terminal-log.d.ts +9 -19
- package/dist/observability/run-terminal-log.js +183 -32
- package/dist/observability/tool-trace.d.ts +12 -0
- package/dist/observability/tool-trace.js +5 -1
- package/dist/operator-ask.d.ts +46 -0
- package/dist/operator-ask.js +4 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +96 -0
- package/dist/orchestration/workflow-agent-session-index.js +146 -0
- package/dist/orchestration/workflow-journal-entry.d.ts +78 -0
- package/dist/orchestration/workflow-journal-entry.js +20 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +43 -1
- package/dist/orchestration/workflow-notify-journal.js +43 -3
- package/dist/parked-decide.d.ts +115 -5
- package/dist/parked-decide.js +94 -26
- package/dist/plugins/pg-pool.js +8 -0
- package/dist/plugins/store-backend.d.ts +7 -1
- package/dist/plugins/store-backend.js +3 -1
- package/dist/plugins/tidb-pool.js +8 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -0
- package/dist/plugins/workflow-run-store-sql.js +40 -0
- package/dist/posture-source.d.ts +23 -3
- package/dist/posture-source.js +1 -1
- package/dist/read-face-posture.d.ts +8 -1
- package/dist/read-face-posture.js +1 -0
- package/dist/router/route-orchestration.js +2 -1
- package/dist/run-cancel-context.d.ts +50 -1
- package/dist/run-cancel-context.js +22 -0
- package/dist/run-local.d.ts +14 -6
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +0 -51
- package/dist/runs.js +40 -23
- package/dist/runtime-governance.js +5 -4
- package/dist/tool-approval.d.ts +31 -5
- package/dist/tool-approval.js +5 -4
- package/dist/trace/core-keyset-guard.d.ts +9 -5
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/ledger-events.d.ts +17 -1
- package/dist/trace/ledger-events.js +6 -1
- package/dist/trace/ledger-sink.d.ts +10 -8
- package/dist/trace/ledger-sink.js +2 -1
- package/dist/trace/project.d.ts +3 -2
- package/dist/trace/redact.d.ts +28 -0
- package/dist/trace/redact.js +50 -27
- package/package.json +4 -4
package/dist/runs.d.ts
CHANGED
|
@@ -97,57 +97,6 @@ export declare function markChildrenStoppedByUserOnAbort(signal: AbortSignal, ta
|
|
|
97
97
|
export declare function isSessionConflictResult(result: {
|
|
98
98
|
terminal: TerminalCause;
|
|
99
99
|
}): boolean;
|
|
100
|
-
/**
|
|
101
|
-
* E18 resume-at CALLER error → an HTTP 4xx. A bad/stale resume-at target (the resolved entryId is gone or is not a
|
|
102
|
-
* settled message boundary) surfaces as a FAILED TaskResult carrying a `resume_at.*` errorCode (core swallows the
|
|
103
|
-
* prepare-throw into the result, exactly like a session conflict — runtask catch → status:"failed"+errorCode). The
|
|
104
|
-
* sync path otherwise 200s a failed result; map these to 4xx so a caller mistake reads as one. `not_found` (the
|
|
105
|
-
* entry is genuinely absent) → 404; everything else (not_a_message / conflicts_resume / no_session) → 422.
|
|
106
|
-
*
|
|
107
|
-
* [ref] Phase3: `requireExistingSession` on a genuinely-missing/purged session ALSO surfaces as a failed result
|
|
108
|
-
* (core prepare-task maps the store's `not_found` → `resume.session_not_found`); map it to 404 (the session is gone),
|
|
109
|
-
* the same "caller mistake reads as a 4xx, not a silent 200 fresh run" discipline.
|
|
110
|
-
*
|
|
111
|
-
* [ref] rewind exclusive mode (core 1.292 resumeAtMode:"before"): its two edge rejections ride the same `resume_at.*`
|
|
112
|
-
* prefix (before_target_not_user / before_root_unsupported → 422 via the existing branch, errorCode passed through
|
|
113
|
-
* UNCHANGED for the shell). `rewind_snapshot.unresolvable` (rewindFiles + "before": no snapshot at/above the branch
|
|
114
|
-
* point) is core's third rejection with a DIFFERENT prefix — same caller-mistake shape (prepare-throw, nothing
|
|
115
|
-
* billed), map it to 422 explicitly so it doesn't fall through to a 200-with-failed body.
|
|
116
|
-
*
|
|
117
|
-
* [ref] 件A([ref] 件4): core 5.13.0's org-memory admission rides the SAME prepare-throw pipe
|
|
118
|
-
* (GOVERNANCE_CODES). `memory.admission_denied` is TERMINAL — the principal has no grant on that tenant
|
|
119
|
-
* plane → 403 (a retry cannot change the verdict). `memory.admission_required` is TRANSIENT fail-closed —
|
|
120
|
-
* the directory is unreachable / no resolver is wired → 503 (retry later; the sync leg forwards the
|
|
121
|
-
* result's `retryAfterMs` as body `retryAfterSec`, same family shape as usage.window_exhausted). Exact
|
|
122
|
-
* codes only, no memory.* family grab — an unknown sibling stays a 200-with-failed-body until cataloged.
|
|
123
|
-
*
|
|
124
|
-
* [ref] (core 7.0.0 片2/3, [ref] S-3): the session capture opt-out's four prepare-throw refusals ride the
|
|
125
|
-
* same pipe. `memory.capture_optout_denied` is TERMINAL (the principal's `allowMemoryOptOut` verdict is an
|
|
126
|
-
* explicit `false`, or the posture is `capture-required` / `governed`-with-absent-verdict — a retry cannot
|
|
127
|
-
* change it) → 403, the `memory.admission_denied` twin. `memory.capture_optout_unpersisted` is the
|
|
128
|
-
* declaration arm's "control-plane record could not be written" refusal — a store outage, retry may succeed
|
|
129
|
-
* → 503 (transient fail-closed, the `memory.admission_required` twin; no retryAfterMs on this one — core
|
|
130
|
-
* mints none, so the sync leg forwards no hint rather than inventing one). `config.memory_capture_spelling`
|
|
131
|
-
* is a TaskSpec authoring mistake (the only legal spelling is the string "off") → 400, the
|
|
132
|
-
* `config.memory_erasure_request` family shape. `config.memory_capture_unsupported` is the DEPLOYMENT SHAPE
|
|
133
|
-
* refusing the declaration (remote/per-run execution with no durable capture-record carrier: the record
|
|
134
|
-
* would not survive to a resume replica) → 409 — the request is well-formed, the deployment's state
|
|
135
|
-
* conflicts with it, the `config.refresh_unavailable` family shape ("change the deployment, not the request").
|
|
136
|
-
* Status choices are pinned by the `config.`/`memory.` family status sets in error-code-catalog-live.test.ts.
|
|
137
|
-
*
|
|
138
|
-
* S-125 件⑥(core 7.4.0 [ref]):`rewind.child_scope_unsupported` —— 这条腿是一个**委派子代**,它的文件编辑
|
|
139
|
-
* 记在**根 session** 的文件历史里,所以「把文件还原到某个 entry」是根 session 的动作、不是子代的;core 在
|
|
140
|
-
* prepare 期响亮拒(零计费,文件一个字节没动)。映射 **422**:请求形式合法、语义上找错了执行者 —— 与
|
|
141
|
-
* `resume_at.*` 的「调用方指错了锚」同一族同一档,而不是 404(目标在,只是不归你)也不是 409(部署状态没冲突)。
|
|
142
|
-
* ⚠️ **前缀不吞**:本码前缀是 `rewind.`,与下面那条 `resume_at.` **开集前缀臂**不同族 —— 它不会被那一臂顺手
|
|
143
|
-
* 兜走(兜走会把一条「找错执行者」的拒答成「锚解析不了」),也不是 `rewind_snapshot.`(那是「快照找不到」)。
|
|
144
|
-
* 三个前缀各自成族,逐码对表见 `test/resume-anchor.test.ts` 的 S-125 `rewind.` 族格。
|
|
145
|
-
* 🔴 **可达性如实**:本仓的 HTTP 面**今天铸不出**这条腿 —— 子代由 core 内部的 Agent 工具派生,
|
|
146
|
-
* `fileHistoryLineage` 是 core prepare 的 internals(不在 `TaskSpec` 上),server 从不直接 prepare 一条带
|
|
147
|
-
* lineage 的子代腿。本臂是**分诊面的完备性**(core 说 X ⇒ wire 形是 Y),不是一条今天在跑的路;将来的
|
|
148
|
-
* 委派入口接线那天零改动即生效。同族其余五码今天**仍落 200-with-failed-body**,那是本批**之前**就存在的
|
|
149
|
-
* 缺口(不是本批回归),已在上述对表格里逐码显式登记为债务,并由那道门看住不许陈腐。
|
|
150
|
-
*/
|
|
151
100
|
export declare function resumeAtHttpStatus(result: {
|
|
152
101
|
terminal: TerminalCause;
|
|
153
102
|
}): number | undefined;
|
package/dist/runs.js
CHANGED
|
@@ -7,7 +7,7 @@ import { taskNotificationEventData, appendModelUsageDelta, appendPromptManifest,
|
|
|
7
7
|
import { createLedgerSink } from "./trace/ledger-sink.js";
|
|
8
8
|
import { registerEngineNoticeLeg } from "./trace/engine-notice-wire.js";
|
|
9
9
|
import { recordTurnActivity } from "./turn-activity.js";
|
|
10
|
-
import {
|
|
10
|
+
import { CANCELLED_CODE, mintCancelledResult } from "./run-cancel-context.js";
|
|
11
11
|
import { createApprovalCardEmitter, resolveApprovalLeg } from "./tool-approval.js";
|
|
12
12
|
import { fleetRunResiduals, isFleetAgentTerminalNotification } from "./fleet/fleet-bus.js";
|
|
13
13
|
import { defaultSubagentTailBus, projectTailFrame } from "./fleet/subagent-tail-bus.js";
|
|
@@ -66,22 +66,29 @@ export function isSessionConflictResult(result) {
|
|
|
66
66
|
return result.terminal.code === "conflict";
|
|
67
67
|
return /stale write|concurrent write|conflict/i.test(result.terminal.message ?? "");
|
|
68
68
|
}
|
|
69
|
+
const GOVERNANCE_HTTP_STATUS = {
|
|
70
|
+
"memory.admission_denied": 403,
|
|
71
|
+
"memory.admission_required": 503,
|
|
72
|
+
"memory.capture_optout_denied": 403,
|
|
73
|
+
"config.tool_mount_denied": 400,
|
|
74
|
+
"config.locked_key": 400,
|
|
75
|
+
"usage.window_exhausted": null,
|
|
76
|
+
"config.compliance_required": null,
|
|
77
|
+
"config.compliance_denied": null,
|
|
78
|
+
};
|
|
69
79
|
export function resumeAtHttpStatus(result) {
|
|
70
80
|
const code = failureCodeOf(result.terminal);
|
|
71
81
|
if (code === undefined)
|
|
72
82
|
return undefined;
|
|
83
|
+
if (Object.hasOwn(GOVERNANCE_HTTP_STATUS, code)) {
|
|
84
|
+
return GOVERNANCE_HTTP_STATUS[code] ?? undefined;
|
|
85
|
+
}
|
|
73
86
|
if (code === "resume.session_not_found")
|
|
74
87
|
return 404;
|
|
75
88
|
if (code === "rewind_snapshot.unresolvable")
|
|
76
89
|
return 422;
|
|
77
90
|
if (code === "rewind.child_scope_unsupported")
|
|
78
91
|
return 422;
|
|
79
|
-
if (code === "memory.admission_denied")
|
|
80
|
-
return 403;
|
|
81
|
-
if (code === "memory.admission_required")
|
|
82
|
-
return 503;
|
|
83
|
-
if (code === "memory.capture_optout_denied")
|
|
84
|
-
return 403;
|
|
85
92
|
if (code === "memory.capture_optout_unpersisted")
|
|
86
93
|
return 503;
|
|
87
94
|
if (code === "config.memory_capture_spelling")
|
|
@@ -164,10 +171,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
164
171
|
const preemptCtrl = new AbortController();
|
|
165
172
|
if (preemptEligible)
|
|
166
173
|
preemptable?.set(taskId, preemptCtrl);
|
|
167
|
-
const cancelledResult = () => {
|
|
168
|
-
const ctx = buildCancelContext(taskId, startedAt);
|
|
169
|
-
return { taskId, sessionId: spec.sessionId, terminal: { kind: "failed", code: "cancelled", message: "cancelled by user" }, stats: { turns: 0, tokens: 0 }, ...(ctx ? { cancelContext: ctx } : {}) };
|
|
170
|
-
};
|
|
174
|
+
const cancelledResult = (engineResult) => mintCancelledResult({ taskId, sessionId: spec.sessionId ?? "", startedAtMs: startedAt, message: "cancelled by user", ...(engineResult !== undefined ? { engineResult } : {}) });
|
|
171
175
|
const recordDone = (result, status) => {
|
|
172
176
|
metrics?.addGauge("runs_active", -1);
|
|
173
177
|
metrics?.inc("tasks_total", { status });
|
|
@@ -214,6 +218,9 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
214
218
|
heartbeat.unref?.();
|
|
215
219
|
const anchor = new TurnAnchorCapture(captureTurnAnchor, () => metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
|
|
216
220
|
let reached;
|
|
221
|
+
let observedDone;
|
|
222
|
+
let cancelledAtDone = false;
|
|
223
|
+
let settledAsCancel = false;
|
|
217
224
|
let steerableStreamRef;
|
|
218
225
|
let legLive = true;
|
|
219
226
|
const notifiedKeys = new NotifiedKeys();
|
|
@@ -341,6 +348,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
341
348
|
steerableStreamRef = stream;
|
|
342
349
|
steerable?.set(taskId, stream);
|
|
343
350
|
for await (const ev of stream) {
|
|
351
|
+
if (ev.type === "done") {
|
|
352
|
+
observedDone = ev.result;
|
|
353
|
+
cancelledAtDone = cancelCtrl.signal.aborted;
|
|
354
|
+
}
|
|
344
355
|
fleetPublisher?.onEvent(ev);
|
|
345
356
|
await flushPromptManifest();
|
|
346
357
|
switch (ev.type) {
|
|
@@ -375,12 +386,14 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
375
386
|
}
|
|
376
387
|
evictIfConflict(runner, spec.sessionId, ev.result);
|
|
377
388
|
{
|
|
378
|
-
const
|
|
389
|
+
const settled = cancelledAtDone ? cancelledResult(ev.result) : ev.result;
|
|
390
|
+
settledAsCancel = failureCodeOf(settled.terminal) === CANCELLED_CODE;
|
|
391
|
+
const safe = await withModelUsage(stripResumeToken(settled));
|
|
379
392
|
await sink.appendDone(safe);
|
|
380
393
|
reached = { kind: "done", result: safe };
|
|
381
394
|
const plane = terminalProjection(safe.terminal);
|
|
382
395
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
383
|
-
recordDone(safe, plane.status);
|
|
396
|
+
recordDone(safe, failureCodeOf(safe.terminal) === CANCELLED_CODE ? CANCELLED_CODE : plane.status);
|
|
384
397
|
}
|
|
385
398
|
continue;
|
|
386
399
|
}
|
|
@@ -427,15 +440,16 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
427
440
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=flush-clean-cancel ${errText(e)}`);
|
|
428
441
|
}
|
|
429
442
|
const c = cancelledResult();
|
|
443
|
+
settledAsCancel = failureCodeOf(c.terminal) === CANCELLED_CODE;
|
|
430
444
|
try {
|
|
431
|
-
await append("failed", { errorMessage: "cancelled by user", errorCode:
|
|
445
|
+
await append("failed", { errorMessage: "cancelled by user", errorCode: CANCELLED_CODE });
|
|
432
446
|
}
|
|
433
447
|
catch (e) {
|
|
434
448
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-clean-cancel ${errText(e)}`);
|
|
435
449
|
}
|
|
436
450
|
reached = { kind: "done", result: c };
|
|
437
451
|
await runStore.setTerminal(taskId, "failed", c, terminalProjection(c.terminal).errorMessage ?? null);
|
|
438
|
-
recordDone(c,
|
|
452
|
+
recordDone(c, CANCELLED_CODE);
|
|
439
453
|
}
|
|
440
454
|
}
|
|
441
455
|
catch (err) {
|
|
@@ -449,6 +463,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
449
463
|
}
|
|
450
464
|
if (reached?.kind === "done") {
|
|
451
465
|
const plane = terminalProjection(reached.result.terminal);
|
|
466
|
+
settledAsCancel = failureCodeOf(reached.result.terminal) === CANCELLED_CODE;
|
|
452
467
|
await runStore.setTerminal(taskId, plane.status, reached.result, plane.errorMessage ?? null);
|
|
453
468
|
}
|
|
454
469
|
else if (reached?.kind === "suspended") {
|
|
@@ -457,10 +472,11 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
457
472
|
else if (reached?.kind === "needs_review") {
|
|
458
473
|
await runStore.setNeedsReview(taskId);
|
|
459
474
|
}
|
|
460
|
-
else if (cancelCtrl.signal.aborted) {
|
|
475
|
+
else if (cancelCtrl.signal.aborted && observedDone === undefined) {
|
|
461
476
|
const c = cancelledResult();
|
|
477
|
+
settledAsCancel = failureCodeOf(c.terminal) === CANCELLED_CODE;
|
|
462
478
|
try {
|
|
463
|
-
await append("failed", { errorMessage: "cancelled by user", errorCode:
|
|
479
|
+
await append("failed", { errorMessage: "cancelled by user", errorCode: CANCELLED_CODE });
|
|
464
480
|
}
|
|
465
481
|
catch (e) {
|
|
466
482
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-thrown-cancel ${errText(e)}`);
|
|
@@ -468,6 +484,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
468
484
|
await runStore.setTerminal(taskId, "failed", c, terminalProjection(c.terminal).errorMessage ?? null);
|
|
469
485
|
}
|
|
470
486
|
else {
|
|
487
|
+
settledAsCancel = false;
|
|
471
488
|
const safeMsg = redactErrorMessage(message) ?? message;
|
|
472
489
|
try {
|
|
473
490
|
await append("failed", { errorMessage: safeMsg });
|
|
@@ -481,13 +498,13 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
481
498
|
catch {
|
|
482
499
|
}
|
|
483
500
|
if (reached?.kind === "done")
|
|
484
|
-
recordDone(reached.result, failureCodeOf(reached.result.terminal) ===
|
|
501
|
+
recordDone(reached.result, failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : terminalProjection(reached.result.terminal).status);
|
|
485
502
|
else if (reached?.kind === "suspended") {
|
|
486
503
|
metrics?.addGauge("runs_active", -1);
|
|
487
504
|
metrics?.inc("tasks_total", { status: "suspended" });
|
|
488
505
|
}
|
|
489
|
-
else if (
|
|
490
|
-
recordDone(null,
|
|
506
|
+
else if (settledAsCancel) {
|
|
507
|
+
recordDone(null, CANCELLED_CODE);
|
|
491
508
|
}
|
|
492
509
|
else {
|
|
493
510
|
recordDone(null, "failed");
|
|
@@ -498,14 +515,14 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
498
515
|
unregisterEngineNotice();
|
|
499
516
|
if (fleetPublisher) {
|
|
500
517
|
if (reached?.kind === "done")
|
|
501
|
-
fleetPublisher.onTerminal(failureCodeOf(reached.result.terminal) ===
|
|
518
|
+
fleetPublisher.onTerminal(failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : terminalProjection(reached.result.terminal).status, fleetRunResiduals(reached.result));
|
|
502
519
|
else if (reached?.kind === "suspended")
|
|
503
520
|
fleetPublisher.onTerminal("suspended");
|
|
504
521
|
else if (reached?.kind === "needs_review")
|
|
505
522
|
fleetPublisher.onTerminal("needs_review");
|
|
506
523
|
else
|
|
507
|
-
fleetPublisher.onTerminal(
|
|
508
|
-
...(
|
|
524
|
+
fleetPublisher.onTerminal(settledAsCancel ? CANCELLED_CODE : "failed", {
|
|
525
|
+
...(settledAsCancel ? { stoppedBy: "user" } : {}),
|
|
509
526
|
...(spec.sessionId ? { transcriptId: spec.sessionId } : {}),
|
|
510
527
|
});
|
|
511
528
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, DEFAULT_SUBAGENT_TOOL_NAME, parseLeadingCommandName, tightenTaskSpec, } from "@sema-agent/core";
|
|
2
2
|
import { SAFETY_MERGE_SPEC } from "@sema-agent/settings-schema";
|
|
3
3
|
import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
|
|
4
|
+
import { operatorAsk } from "./operator-ask.js";
|
|
4
5
|
export function isDelegationToolName(name) {
|
|
5
6
|
return name === DEFAULT_SUBAGENT_TOOL_NAME;
|
|
6
7
|
}
|
|
@@ -13,18 +14,18 @@ export const COARSE_SHELL_TOOLS_MIRROR = COARSE_SHELL_TOOLS;
|
|
|
13
14
|
function createCommandAskListPolicy(askCommands, tools = COARSE_SHELL_TOOLS) {
|
|
14
15
|
const askSet = new Set(askCommands);
|
|
15
16
|
const shellTools = new Set(tools);
|
|
16
|
-
const ask = (message) => (
|
|
17
|
+
const ask = (message, rule) => operatorAsk(rule, message);
|
|
17
18
|
return {
|
|
18
19
|
check(req) {
|
|
19
20
|
if (!shellTools.has(req.toolName))
|
|
20
21
|
return { action: "allow" };
|
|
21
22
|
const command = req.args?.command;
|
|
22
23
|
if (typeof command !== "string")
|
|
23
|
-
return ask(`tool "${req.toolName}" call has no parseable command string
|
|
24
|
+
return ask(`tool "${req.toolName}" call has no parseable command string`, "commandPolicy(unparseable command)");
|
|
24
25
|
const parsed = parseLeadingCommandName(command);
|
|
25
26
|
if ("reject" in parsed)
|
|
26
|
-
return ask(`command is not a single simple command (${parsed.reject})
|
|
27
|
-
return askSet.has(parsed.name) ? ask(`command "${parsed.name}" requires approval by policy`) : { action: "allow" };
|
|
27
|
+
return ask(`command is not a single simple command (${parsed.reject})`, "commandPolicy(argv[0] unparseable)");
|
|
28
|
+
return askSet.has(parsed.name) ? ask(`command "${parsed.name}" requires approval by policy`, `commandPolicy(${parsed.name}=ask)`) : { action: "allow" };
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
}
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -143,6 +143,27 @@ export interface ToolApprovalFrame {
|
|
|
143
143
|
* ask」,治理策略产的与普通策略产的在这一个词上同形 ⇒ 顶替不了(逐字论证见 `governance-ask-marks.ts` 顶注)。
|
|
144
144
|
*/
|
|
145
145
|
origin?: string;
|
|
146
|
+
/**
|
|
147
|
+
* **S-185 / [ref]**(core 7.10.0;**ADDITIVE**,`"tool_approval"` only)—— 这一问**为什么**到了人手上:
|
|
148
|
+
* 分类器被咨询过、而它**跑不了**。`cause` = core 的闭集词
|
|
149
|
+
* (`AUTO_MODE_UNAVAILABLE_CAUSES`;core 7.12.0 起 = `error` / `timeout`),`AskRequest.classifierUnavailable`
|
|
150
|
+
* 逐字透传。core 7.12.0 / [ref] 起 ask 上这一位只由**策略层自声明**铸(引擎自己的那份改骑 deny —— 全文
|
|
151
|
+
* 在 `approval-card.ts` 同名键的顶注)。
|
|
152
|
+
*
|
|
153
|
+
* 🔴 **缺席 ≠「分类器好着呢」**:缺席同时覆盖三形 —— 分类器答上了 / 这只 ask 没资格走分类器
|
|
154
|
+
* (`classifierMayAnswer` 的闭表)/ 本部署压根没接分类器。消费端**禁**把缺席读成对分类器健康的断言。
|
|
155
|
+
* 另注:`parse_error`(分类器跑了但答得出格)**不盖这一位** —— 那是另一句话,core 顶注逐字。
|
|
156
|
+
*
|
|
157
|
+
* 🔴 **不枚举词表**(值形是 `{cause: string}` 而不是本仓自铸的联合):词表单一属主在 core,抄一份会把
|
|
158
|
+
* core 加的新词吞成缺席。窄读只判「对象 ∧ `cause` 非空串 ∧ 无多余成员」(`readClassifierUnavailable`,
|
|
159
|
+
* 帧与卡的**唯一**铸造点 ⇒ 两面结构性同值)。
|
|
160
|
+
*
|
|
161
|
+
* 🔴 **echo-only,零消费**:熔断/超时的处置全在 core 的分类器站,server 不许据本键自铸第二套回落判据
|
|
162
|
+
* —— 同 `origin` / `denialLimitFallback` 两条顶注的同一条禁令(同一语义面两个写者)。
|
|
163
|
+
*/
|
|
164
|
+
classifierUnavailable?: {
|
|
165
|
+
cause: string;
|
|
166
|
+
};
|
|
146
167
|
/**
|
|
147
168
|
* **E-14**([ref]② Trojan Source 族;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 的**工具输入**
|
|
148
169
|
* (`AskRequest.args`)里含 bidi 控制符(LRM/RLM、嵌入/覆写、隔离符;字符类属主 `text-bidi.ts`)。
|
|
@@ -156,13 +177,18 @@ export interface ToolApprovalFrame {
|
|
|
156
177
|
* 执行的那串字节(卡上显示的与真跑的不是同一个东西),**比不披露更坏**;所以 server 只报「有」,显形
|
|
157
178
|
* 归壳的渲染面(转义/高亮/加标记,壳自己定)。
|
|
158
179
|
*
|
|
159
|
-
* **判据素材** = `boundArgs`
|
|
180
|
+
* **判据素材** = `boundArgs` 里对**原字节**(`redactDeep` 之前)的那一次序列化,且在**字节帽判定之前**算:
|
|
160
181
|
* · 超帽时 args 以 `argsOmitted:true` 缺席上帧(卡上根本没有 args)—— 那正是人最看不见输入的一格,
|
|
161
182
|
* 更需要知道「你看不到的那份里有隐形字符」,所以帽不影响本键;
|
|
162
|
-
* · 序列化失败(循环引用 / 抛错的 `toJSON` / BigInt)⇒ 两件事一起交白卷:args
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
183
|
+
* · 序列化失败(循环引用 / 抛错的 `toJSON` / BigInt)⇒ 两件事一起交白卷:args 不上帧,本键**不铸**
|
|
184
|
+
* (两次 `JSON.stringify` 共用同一个 try/catch —— 顶注反对的是第二个**静默降级点**,不是第二次序列化);
|
|
185
|
+
* · 🔴 **7.70.0(S-103)改素材**:此前用的是脱敏后那份(理由=与卡面同源),而本版 `redactSecrets` 入口
|
|
186
|
+
* 先剥全部 `\p{Cf}` —— bidi 控制符**整族都是** `\p{Cf}` ⇒ 在脱敏后的文本上扫,本键恒缺席、E-14 整条
|
|
187
|
+
* 失效(六格红先复现)。所以判据素材改回原字节;同源那条理由让位于「信号必须真」。
|
|
188
|
+
* · ⚠️ **上面「字节零改」那一条自 7.70.0 起对 `\p{Cf}` 族不再成立**:帧/卡上的 args 是**剥后**字节
|
|
189
|
+
* (与 core 显示面 `renderUntrustedCommandText` 同律,显示的是真正执行的逻辑顺序),壳因此**拿不到
|
|
190
|
+
* 「在哪一位」**、只能按整只 args 打标。这是有意的取舍(夹层密钥的根治要求单一剥点),不是遗漏;
|
|
191
|
+
* 要恢复定位面需要给脱敏器一条 span/偏移表回填路(S-103 定案明确不做),不是在这条腿上加特判。
|
|
166
192
|
*
|
|
167
193
|
* 判据**不看 `message`**(引擎/策略写的说明文本,不是待执行输入):本键被壳读作「**待执行的输入**可能
|
|
168
194
|
* 在骗你的眼睛」,把两个来源折进一个布尔会让壳无法判断该给哪一段加显形标记。
|
package/dist/tool-approval.js
CHANGED
|
@@ -2,13 +2,13 @@ import { contentKindOf } from "./approval-content-kind.js";
|
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
import { uuidv7, MAX_RULE_TEXT_CHARS, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
4
4
|
import { precheckCardRuleText } from "./rules-consent.js";
|
|
5
|
-
import { redactDeep, redactSecrets } from "./trace/redact.js";
|
|
5
|
+
import { normalizeForRedaction, redactDeep, redactSecrets } from "./trace/redact.js";
|
|
6
6
|
import { hasBidiControls } from "./text-bidi.js";
|
|
7
7
|
import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerprint.js";
|
|
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, readDenialLimitFallback, readProbeCause, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
11
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readClassifierUnavailable, 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();
|
|
@@ -34,7 +34,7 @@ const PARKING_RETRY_AFTER_MS = 250;
|
|
|
34
34
|
function durableRuleMaterial(material) {
|
|
35
35
|
if (material === undefined)
|
|
36
36
|
return { ruleCommand: null, ruleScopeRoot: null };
|
|
37
|
-
if (redactSecrets(material.command, { observe: false }) !== material.command) {
|
|
37
|
+
if (redactSecrets(material.command, { observe: false }) !== normalizeForRedaction(material.command)) {
|
|
38
38
|
defaultLogger.warn("approval rule material withheld: the adjudicated command carries credential-shaped bytes", {
|
|
39
39
|
commandChars: material.command.length,
|
|
40
40
|
});
|
|
@@ -141,8 +141,8 @@ function boundArgs(args) {
|
|
|
141
141
|
const redacted = redactDeep(args);
|
|
142
142
|
let hasBidi = false;
|
|
143
143
|
try {
|
|
144
|
+
hasBidi = hasBidiControls(JSON.stringify(args));
|
|
144
145
|
const text = JSON.stringify(redacted);
|
|
145
|
-
hasBidi = hasBidiControls(text);
|
|
146
146
|
if (Buffer.byteLength(text, "utf8") > MAX_APPROVAL_ARGS_BYTES)
|
|
147
147
|
return { omitted: true, hasBidi };
|
|
148
148
|
}
|
|
@@ -844,6 +844,7 @@ export class ToolApprovalCoordinator {
|
|
|
844
844
|
...(ruleLaneMaterial !== undefined ? { ruleOffers: ruleLaneMaterial.offers } : {}),
|
|
845
845
|
...((a) => (a !== undefined ? { ruleOffersAbsence: a } : {}))(readRuleOffersAbsence(req)),
|
|
846
846
|
...((f) => (f !== undefined ? { denialLimitFallback: f } : {}))(readDenialLimitFallback(req)),
|
|
847
|
+
...((c) => (c !== undefined ? { classifierUnavailable: c } : {}))(readClassifierUnavailable(req)),
|
|
847
848
|
...((c) => (c !== undefined ? { probeCause: c } : {}))(readProbeCause(req)),
|
|
848
849
|
...((c) => (c !== undefined ? { ruleEvidence: c } : {}))(readRuleEvidence(req)),
|
|
849
850
|
...(bounded.omitted ? { argsOmitted: true } : { args: bounded.args }),
|
|
@@ -18,8 +18,12 @@
|
|
|
18
18
|
* TaskResult 按「转发/摘除」记 Forwarded/Stripped —— 谁改 resolve-spec.ts / runs.ts 的发射点谁同步本文件。
|
|
19
19
|
*/
|
|
20
20
|
import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxAppendMessage, TaskSpec, TaskResult, GateOutcome, Settlement, PausedCause } from "@sema-agent/core";
|
|
21
|
-
/** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。
|
|
22
|
-
|
|
21
|
+
/** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。
|
|
22
|
+
* 🔴 **导出**([ref] U1/U2):本仓「词表闭集,未处置成员=编译红」的钉法只许有**一个**形 ——
|
|
23
|
+
* `observability/run-terminal-log.ts` 的三张叶表 + 结果顶层键表用同一只断言,而不是各自再抄一份
|
|
24
|
+
* `type X<T extends never> = T`(抄一份就是第二个写者,两处哪天分岔谁都发现不了)。
|
|
25
|
+
* 本文件只有类型、不产 JS,所以被 `import type` 引用不新增任何运行期边。 */
|
|
26
|
+
export type AssertAllKeysHandled<T extends never> = T;
|
|
23
27
|
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
28
|
type NotificationExcluded = "peer" | "crossSessionMessage" | "crossSessionNotice";
|
|
25
29
|
type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected | NotificationExcluded>>;
|
|
@@ -28,7 +32,7 @@ type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "descripti
|
|
|
28
32
|
type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
|
|
29
33
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
30
34
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
31
|
-
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin";
|
|
35
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "classifierUnavailable";
|
|
32
36
|
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
|
|
33
37
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
34
38
|
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" | "tool_roster_delta";
|
|
@@ -38,14 +42,14 @@ type _GuardMailbox = AssertAllKeysHandled<Exclude<keyof MailboxMessage, MailboxP
|
|
|
38
42
|
type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
|
|
39
43
|
type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
|
|
40
44
|
type PermissionTraceKind = Extract<TraceEvent["kind"], `permission.${string}`>;
|
|
41
|
-
type PermissionTraceProjected = "permission.persisted_rule_allowed" | "permission.rule_store_unreadable";
|
|
45
|
+
export type PermissionTraceProjected = "permission.persisted_rule_allowed" | "permission.rule_store_unreadable" | "permission.read_only_allowed";
|
|
42
46
|
type PermissionTraceDropped = "permission.sandbox_admitted" | "permission.rule_sync_resurrected" | "permission.rule_sync_dropped" | "permission.org_snapshot_unavailable";
|
|
43
47
|
type _GuardPermissionTrace = AssertAllKeysHandled<Exclude<PermissionTraceKind, PermissionTraceProjected | PermissionTraceDropped>>;
|
|
44
48
|
type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTraceProjected | PermissionTraceDropped, PermissionTraceKind>>;
|
|
45
49
|
/** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
|
|
46
50
|
type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
|
|
47
51
|
type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
|
|
48
|
-
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin";
|
|
52
|
+
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "classifierUnavailable";
|
|
49
53
|
type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
|
|
50
54
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
51
55
|
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";
|
|
@@ -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", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped"];
|
|
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", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable"];
|
|
20
20
|
export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
|
|
21
21
|
/** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
|
|
22
22
|
export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
|
|
@@ -21,6 +21,7 @@ export const ENGINE_NOTICE_WIRE_CODES = [
|
|
|
21
21
|
"peer.idle_subscription",
|
|
22
22
|
"classifier.denial_limit",
|
|
23
23
|
"mcp.injection_dropped",
|
|
24
|
+
"delegation.ask_unresolvable",
|
|
24
25
|
];
|
|
25
26
|
const WIRE_CODES = new Set(ENGINE_NOTICE_WIRE_CODES);
|
|
26
27
|
export function isEngineNoticeWireCode(code) {
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
* · **腿自己的带外行**:park/终局/失败/建议/用量/装配自证(不来自流事件)。
|
|
33
33
|
*/
|
|
34
34
|
export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" | "status" | "context_usage" | "compacted" | "diagnostics" | "steering_injected" | "compaction_outcome" | "workspace_changed" | "wiring_manifest" | "human_input" | "task_progress" | "task_notification" | "turn_end"
|
|
35
|
-
/** park 行名**两个**(`appendParked(kind)` 逐字取自 `result.status`)
|
|
35
|
+
/** park 行名**两个**(`appendParked(kind)` 逐字取自 `result.status`)—— 运行期闭集见
|
|
36
|
+
* {@link PARK_LEDGER_EVENT_TYPES}(本联合是它的类型面,两者由下方的双向断言锁步)。 */
|
|
36
37
|
| "suspended" | "needs_review" | "done" | "failed" | "suggestions" | "model_usage" | "prompt_assembled" | "config_assembled" | "file_link"
|
|
37
38
|
/** 人在环三族的 open/close 帧对(投递面 = 本腿的 durable events tail)。 */
|
|
38
39
|
| "elicitation" | "elicitation_complete" | "question" | "question_complete" | "tool_approval" | "tool_approval_complete"
|
|
@@ -59,4 +60,19 @@ export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" |
|
|
|
59
60
|
* 由 `test/engine-notice-wire.test.ts` 的腿覆盖门看守,那才是它的对位门。
|
|
60
61
|
*/
|
|
61
62
|
| "engine_notice";
|
|
63
|
+
/**
|
|
64
|
+
* 🔴 **park 行名的运行时闭集**(合并重扫 [4])。
|
|
65
|
+
*
|
|
66
|
+
* 此前这两个词在仓里有**三处**并列的字面量:`ledger-sink.ts` 的 `appendParked` 形参与 `onDone` 的分诊、
|
|
67
|
+
* 以及 `observability/run-terminal-log.ts` 的脱敏臂。`LedgerEventType` 只是类型联合、运行期取不到,
|
|
68
|
+
* 所以脱敏臂只能自己再抄一份 —— 而抄的那一份**加词不红**:仓里/引擎加第三个 park 词并登记进
|
|
69
|
+
* `LedgerEventType` 之后,`appendParked` 与 resume 腿的动态首参照写照落,回放时那一行的
|
|
70
|
+
* `gate.riskDescriptor.summary` 明文,脱敏臂一声不吭。
|
|
71
|
+
*
|
|
72
|
+
* ⇒ 词表落成**运行时常量**由消费点派生;下方两条断言把它与类型联合双向锁死(多一词 / 少一词都 tsc 红)。
|
|
73
|
+
*/
|
|
74
|
+
export declare const PARK_LEDGER_EVENT_TYPES: readonly ["suspended", "needs_review"];
|
|
75
|
+
export type ParkLedgerEventType = (typeof PARK_LEDGER_EVENT_TYPES)[number];
|
|
76
|
+
/** park 行名的**收窄谓词**(消费点要的是「这个词是不是 park 词」+ 类型收窄,不是一次裸 includes)。 */
|
|
77
|
+
export declare function isParkLedgerEventType(v: string): v is ParkLedgerEventType;
|
|
62
78
|
//# sourceMappingURL=ledger-events.d.ts.map
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const PARK_LEDGER_EVENT_TYPES = ["suspended", "needs_review"];
|
|
2
|
+
const _parkWordsAreLedgerTypes = true;
|
|
3
|
+
void _parkWordsAreLedgerTypes;
|
|
4
|
+
export function isParkLedgerEventType(v) {
|
|
5
|
+
return PARK_LEDGER_EVENT_TYPES.includes(v);
|
|
6
|
+
}
|
|
2
7
|
//# sourceMappingURL=ledger-events.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type TerminalCause } from "@sema-agent/core";
|
|
2
2
|
import { brainStatusEventData } from "./project.js";
|
|
3
3
|
import { NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type LedgerEventType, type ParkLedgerEventType } from "./ledger-events.js";
|
|
5
5
|
/**
|
|
6
6
|
* LedgerSink — the SINGLE "stream event → durable runs-ledger" projection, shared by the background leg
|
|
7
7
|
* (runs.ts `runInBackground`) and the sync `/v1/tasks/stream` detach lane (server.ts, `x-detach-on-disconnect`).
|
|
@@ -61,13 +61,15 @@ export interface LedgerSink {
|
|
|
61
61
|
}): void;
|
|
62
62
|
/** Park event (durable F4 suspend / [ref] D-B review pause): ONLY the `checkpointGate` is persisted —
|
|
63
63
|
* never the capability `checkpointToken` (search [18] Q1b).
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* `
|
|
70
|
-
|
|
64
|
+
* 🔴 **The gate is redacted, not "non-secret"** ([ref], 2026-09-10 — the arm [ref] R3 left open): it carries no
|
|
65
|
+
* capability token, but its `reason` and the `riskDescriptor` leaf family are model-/human-controlled display
|
|
66
|
+
* text that [ref] measured carrying a PEM private-key body verbatim, and this durable row replays on EVERY
|
|
67
|
+
* `GET /v1/runs/:id/events` reconnect (core [ref]'s mint-point fix does not backfill rows already written).
|
|
68
|
+
* The redaction owner is NOT this writer: it is `redactLedgerEventData` — the one confluence every ledger row
|
|
69
|
+
* crosses at the three store twins' `appendEvent` AND again on the read face (idempotent). Redacting here too
|
|
70
|
+
* would put two writers on one semantic surface; the other park writers (`http/server.ts`'s two resume legs)
|
|
71
|
+
* would still need it, and a per-writer arm is exactly the shape that drifts. */
|
|
72
|
+
appendParked(kind: ParkLedgerEventType, result: unknown): Promise<void>;
|
|
71
73
|
/** Terminal `done` event. `result` must already be checkpoint-token-stripped (and, on the bg leg,
|
|
72
74
|
* model-usage-attached) by the caller. */
|
|
73
75
|
appendDone(result: unknown): Promise<void>;
|
|
@@ -2,6 +2,7 @@ import { terminalProjection } from "@sema-agent/core";
|
|
|
2
2
|
import { redactSecrets } from "./redact.js";
|
|
3
3
|
import { toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
4
4
|
import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
|
|
5
|
+
import { isParkLedgerEventType } from "./ledger-events.js";
|
|
5
6
|
export function createSerialLedgerAppend(appendEvent, startSeq) {
|
|
6
7
|
let seq = startSeq;
|
|
7
8
|
let chain = Promise.resolve();
|
|
@@ -156,7 +157,7 @@ export function createLedgerSink(opts) {
|
|
|
156
157
|
await flush();
|
|
157
158
|
if (result.terminal.kind === "paused") {
|
|
158
159
|
const parkKind = terminalProjection(result.terminal).status;
|
|
159
|
-
if (parkKind
|
|
160
|
+
if (isParkLedgerEventType(parkKind)) {
|
|
160
161
|
await appendParked(parkKind, result);
|
|
161
162
|
return;
|
|
162
163
|
}
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -357,11 +357,12 @@ export declare function workspaceChangedEventData(ev: {
|
|
|
357
357
|
* ([ref],7.57.0 已发)答的是「**若此刻提交**会不会武装」,本段答的是「**这条真跑起来的腿**武装了吗」;
|
|
358
358
|
* 同一个主体(调用方自己的 run)、同一条轴,只差事前/事后。剥掉它,壳就只能拿事前预测冒充事后事实;
|
|
359
359
|
* ② **两个值都是引擎派生的闭形**:`armed` 布尔,`reason` 是 core 的闭集词(`AUTO_MODE_ARM_REASONS`:
|
|
360
|
-
* armed / no_intent / no_face / denied / resolver_fault
|
|
360
|
+
* armed / no_intent / no_face / denied / resolver_fault;core 7.12.0 起 `latch_open` 随熔断/单向闩
|
|
361
|
+
* 整族退役)—— 非用户内容,不脱敏、逐字;
|
|
361
362
|
* ③ **不是 governance 侧信道**:它描述的是**本腿**的武装结论,与 lockedConfig/compliance/memoryAdmission/
|
|
362
363
|
* retention 四位无函数关系;而 `denied` 这一词所披露的「组织把这一位关了」在 [ref] 读面上**本就已经**
|
|
363
364
|
* 对同一租户可见(`org_denied`),本段没有扩大任何受众。
|
|
364
|
-
* 🔴 **逐字透传,不映射**:core
|
|
365
|
+
* 🔴 **逐字透传,不映射**:core 的五词与本仓 [ref] 读面的六词(`AUTO_MODE_UNARMED_REASONS`)**刻意不归一**
|
|
365
366
|
* —— 两张表回答的是两个问题、粒度也不同(core 的 `denied` 一词对应本仓的 `org_denied` / `local_denied`
|
|
366
367
|
* 两词:core 只看合成后的 caps deny 位,本仓分得出这个 false 是 center 下发的还是本地 env 写的)。
|
|
367
368
|
* ⚠️ **`settings_denied` 不折进 `denied`,它折进 `no_intent`**(codex 车BO r1 [medium] 验真后纠):
|
package/dist/trace/redact.d.ts
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace thinking-block redaction (service-side). Per core [R57]: the agent's reasoning/CoT is emitted on the
|
|
3
|
+
* live TaskEvent stream but DELIBERATELY kept out of the metadata Tracer because CoT may contain reasoned-about
|
|
4
|
+
* secrets (and is large / ToS-sensitive for some providers). Persisting it for the trace UI is the deployment's
|
|
5
|
+
* call — so when we DO persist it, redact unambiguous secret formats first. Conservative on purpose: only clear
|
|
6
|
+
* key/token shapes are redacted. ⚠️ It is NOT exhaustive: unrecognised secret formats (a free-text password,
|
|
7
|
+
* a JWT) pass through, and the broad `Bearer <20+ chars>` rule can over-match legitimate CoT (false positives
|
|
8
|
+
* tolerated — over-redacting trace text is safe). For sensitive deployments, disable thinking capture entirely
|
|
9
|
+
* via TRACE_THINKING=false. This is a seam — deployments can extend the patterns.
|
|
10
|
+
*
|
|
11
|
+
* 两组臂,顺序即优先序:{@link FULL_SECRET_PATTERNS}(完整形)在前,{@link FRAGMENT_SECRET_PATTERNS}
|
|
12
|
+
* ([ref] 前缀残片,上游先截后脱的产物 ⇒ 带 `:partial` 后缀的 token)在后。
|
|
13
|
+
*/
|
|
14
|
+
/** 本模块替换 token 的**闭形**正则源(`«redacted»` / `«redacted:aws-key»` / `«redacted:gh-token:partial»` …):
|
|
15
|
+
* URL-userinfo 臂用它把「用户名位上已经是本模块 token」整个当一个用户名单元吃掉。模块加载时对
|
|
16
|
+
* {@link REDACTION_TOKENS} 逐个校验闭形(表加了新 token 形而这里没跟 ⇒ 加载即抛,fail-closed)。 */
|
|
17
|
+
import { stripFormatCharacters } from "@sema-agent/core";
|
|
18
|
+
/**
|
|
19
|
+
* `redactSecrets` 的**归一化前置**(S-103 ①):把全部 `\p{Cf}`(零宽空格 / 词连接符 / BOM / 软连字符 / ZWJ / ZWNJ …)
|
|
20
|
+
* 剥掉。实现单一属主是 core 的 `stripFormatCharacters`(`@sema-agent/core`,7.3.1 起导出,正是为「先扫描后渲染」这一序
|
|
21
|
+
* 开的口),本模块只**转出**它,不抄第二份 —— 两份实现漂了就等于两个「什么是格式字符」的定义。
|
|
22
|
+
*
|
|
23
|
+
* 🔴 **需要判断「这次脱敏到底有没有认出凭据形」的调用方,比较基准是它、不是原文**:
|
|
24
|
+
* `redactSecrets(x) !== x` 在本版里对**任何**含 Cf 的文本都为真(输出是剥后文本),拿它当谓词会把一条带 emoji ZWJ
|
|
25
|
+
* 的普通 `git commit` 判成「含凭据形」;正确的谓词是 `redactSecrets(x) !== normalizeForRedaction(x)`。
|
|
26
|
+
* 现役消费方:`src/tool-approval.ts` 的 `durableRuleMaterial`(规则车道素材闸)与语料的 FP=0 格。
|
|
27
|
+
*/
|
|
28
|
+
export declare const normalizeForRedaction: typeof stripFormatCharacters;
|
|
1
29
|
/** 测试可见:扫描器 indexOf 实际扫过的字节数(摊还线性 ⇒ ≤ 常数 × 输入长)。 */
|
|
2
30
|
export declare function ppkScanBytesForTest(): number;
|
|
3
31
|
export declare function resetPpkScanBytesForTest(): void;
|