@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.
- package/README.md +4 -1
- package/README.zh-CN.md +4 -1
- package/USAGE.md +109 -10
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +3 -2
- package/dist/approval-content-kind.d.ts +13 -7
- package/dist/approval-content-kind.js +3 -1
- package/dist/approval-reconciler.d.ts +0 -54
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.js +3 -2
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/execution-env.js +12 -2
- package/dist/boot/memory-consolidation.d.ts +2 -1
- package/dist/boot/parked-revive-gate.d.ts +2 -0
- package/dist/boot/parked-revive-gate.js +15 -10
- package/dist/boot/resolve-spec.js +8 -3
- package/dist/boot/runner-deps.d.ts +50 -46
- package/dist/boot/runner-deps.js +12 -10
- package/dist/brain.d.ts +3 -1
- package/dist/brain.js +2 -1
- package/dist/budget.js +3 -2
- package/dist/config-catalog.d.ts +31 -2
- package/dist/config-catalog.js +44 -24
- package/dist/config-types.d.ts +31 -1
- package/dist/config-types.js +1 -0
- package/dist/config.js +15 -3
- package/dist/deployment-governance.d.ts +6 -3
- package/dist/deployment-governance.js +2 -1
- package/dist/execution-lane-caps.d.ts +51 -37
- package/dist/execution-lane-caps.js +23 -0
- package/dist/fleet/fleet-bus.d.ts +22 -1
- package/dist/fleet/fleet-bus.js +2 -1
- package/dist/http/routes/approvals-assistant.js +3 -12
- 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/server.js +21 -7
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +4 -3
- package/dist/main.js +6 -4
- package/dist/memory-operator-faces.d.ts +1 -1
- package/dist/memory-scope.d.ts +24 -0
- package/dist/memory-scope.js +12 -3
- package/dist/observability/metrics.d.ts +4 -6
- package/dist/observability/metrics.js +5 -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 +13 -22
- package/dist/observability/run-terminal-log.js +187 -47
- package/dist/observability/tool-trace.d.ts +12 -0
- package/dist/observability/tool-trace.js +5 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
- package/dist/orchestration/workflow-agent-session-index.js +38 -5
- package/dist/parked-decide.d.ts +40 -1
- package/dist/parked-decide.js +19 -2
- package/dist/plugins/checkpoint-store-sql.js +5 -5
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/remote-env-adb.d.ts +11 -0
- package/dist/plugins/remote-env-adb.js +2 -0
- package/dist/plugins/remote-env-device.d.ts +9 -0
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +11 -0
- package/dist/plugins/remote-env-e2b.js +2 -0
- package/dist/plugins/remote-env-host.d.ts +11 -0
- package/dist/plugins/remote-env-host.js +2 -0
- package/dist/plugins/remote-env-k8s.d.ts +11 -0
- package/dist/plugins/remote-env-k8s.js +2 -0
- package/dist/plugins/remote-env-local-docker.d.ts +11 -0
- package/dist/plugins/remote-env-local-docker.js +2 -0
- package/dist/plugins/remote-env-ssh.d.ts +11 -0
- package/dist/plugins/remote-env-ssh.js +2 -0
- package/dist/plugins/store-backend.d.ts +36 -13
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +54 -18
- package/dist/plugins/store-contracts.js +17 -30
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +3 -10
- package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
- package/dist/plugins/workflow-run-store-sql.js +28 -1
- 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/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 +4 -1
- package/dist/runs.d.ts +0 -51
- package/dist/runs.js +40 -25
- package/dist/task-a2a.js +2 -2
- package/dist/task-mcp.js +2 -2
- package/dist/task-settings.d.ts +21 -8
- package/dist/task-settings.js +2 -2
- package/dist/tool-approval.d.ts +14 -8
- package/dist/tool-approval.js +3 -3
- package/dist/trace/core-keyset-guard.d.ts +135 -11
- 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 +22 -2
- package/dist/trace/ledger-events.js +6 -1
- package/dist/trace/ledger-sink.d.ts +10 -8
- package/dist/trace/ledger-sink.js +10 -2
- package/dist/trace/project.d.ts +24 -6
- package/dist/trace/project.js +5 -3
- package/dist/trace/redact.d.ts +28 -0
- package/dist/trace/redact.js +58 -30
- package/package.json +3 -3
package/dist/run-local.d.ts
CHANGED
|
@@ -96,17 +96,25 @@ export interface LocalApproverIo {
|
|
|
96
96
|
/**
|
|
97
97
|
* [ref] 件三:**onAsk 座**(§0 潜雷 / §5 锁 6)。
|
|
98
98
|
*
|
|
99
|
-
* 🔴
|
|
100
|
-
* `AUTONOMY=ask` ⇒ `shellGate:"always"` ⇒ Bash 进
|
|
101
|
-
* shell 调用都 durable park 成一张永远没人 resume
|
|
102
|
-
*
|
|
103
|
-
* 早退(prepare-task dist 亲读),ask
|
|
99
|
+
* 🔴 为什么这个座位是接治理链的**同窗必做项**(成因,留档):彼时 run-local 的主 runner 带着
|
|
100
|
+
* checkpointStore(file backend),于是 `AUTONOMY=ask` ⇒ `shellGate:"always"` ⇒ Bash 进
|
|
101
|
+
* `irreversibleTools` ⇒ park lane effective ⇒ **每一次 shell 调用都 durable park 成一张永远没人 resume
|
|
102
|
+
* 的 checkpoint**,任务挂起、CLI 退 1。一次性 CLI 里没有任何赎回腿,那不是「等人来批」,是卡死。
|
|
103
|
+
* core 的 `isLiveApproverSeat(onAsk)` 为真时 `suspendAsk` 早退(prepare-task dist 亲读),ask 于是落回
|
|
104
|
+
* 本回调 —— 把 park 换成「问人 / 当场拒」的正当接法。
|
|
105
|
+
*
|
|
106
|
+
* 🔴 **本席不是那颗雷的唯一防线,也从来不该是**(B-060 批 codex 交叉复审 [medium] 的教训):它只挡
|
|
107
|
+
* **审批**那条车道;core 的**平台/资源停驻**(`suspendForPlatformLimit`)由「店在不在」一位武装,
|
|
108
|
+
* 压根不经过 onAsk。所以自 B-060 起本腿的结构性防线是**整条腿不接 checkpointStore**(装配点在
|
|
109
|
+
* `createSharedRunnerDeps` 调用处,理由全文在那里),本席退回它本来的职责:**给 TTY 前的人一次真的
|
|
110
|
+
* 问答**。两道并存不是冗余 —— 一道管「谁来答」,一道管「答不了时会不会留下无人兑付的行」。
|
|
104
111
|
*
|
|
105
112
|
* ⛔ **不在 toolPolicy 层做 ask→deny 映射**:core 自己就有 headless auto-deny 的语义与文案,在策略层
|
|
106
113
|
* 复刻一份 = 同源谎(两处判据日后各改各的)。裁决归 core,座位归这里。
|
|
107
114
|
*
|
|
108
115
|
* 🔴 非 TTY 臂答 `false`(真 deny),**绝不答 `"unavailable"`**:后者被 core 读成「席位够不到」并**改道
|
|
109
|
-
* `suspendAsk`**(dist `core/hooks.js:296` 的 `approverUnavailable` 重投)
|
|
116
|
+
* `suspendAsk`**(dist `core/hooks.js:296` 的 `approverUnavailable` 重投)。今天本腿无店 ⇒ 那条改道无处
|
|
117
|
+
* 可去(fail-closed 拒),但这一条仍是本席的契约:答案是「不批」,不是「找不到人」。
|
|
110
118
|
*/
|
|
111
119
|
export declare function createLocalApprover(io: LocalApproverIo): OnAsk;
|
|
112
120
|
/** 默认的 TTY 问人器:一问一答,回答写 **stderr**(stdout 是任务结果的专用管道,别污染它)。
|
package/dist/run-local.js
CHANGED
|
@@ -19,6 +19,7 @@ import { loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./c
|
|
|
19
19
|
import { createConfigProvider } from "./config-provider.js";
|
|
20
20
|
import { applyEffective, applyCenterReadFace, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./config-center/facade.js";
|
|
21
21
|
import { hostExecutionEnvFactory } from "./plugins/remote-env-host.js";
|
|
22
|
+
import { executionLaneHomeDir } from "./execution-lane-caps.js";
|
|
22
23
|
import { makeLoadProjectMemory, makeProbeInstructionSources } from "./project-memory.js";
|
|
23
24
|
import { loadSkills } from "./capabilities/skills.js";
|
|
24
25
|
import { createRepoClient } from "./capabilities/repo-tools.js";
|
|
@@ -273,6 +274,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
273
274
|
const executionEnvFactory = hostRx
|
|
274
275
|
? hostExecutionEnvFactory({
|
|
275
276
|
workspaceDir,
|
|
277
|
+
...((h) => (h === undefined ? {} : { homeDir: h }))(executionLaneHomeDir(config)),
|
|
276
278
|
backgroundShell: config.requirePrincipal !== true,
|
|
277
279
|
...(hostRx.commandTimeoutMs != null ? { commandTimeoutMs: hostRx.commandTimeoutMs } : {}),
|
|
278
280
|
})
|
|
@@ -369,6 +371,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
369
371
|
],
|
|
370
372
|
});
|
|
371
373
|
const sharedRunnerDeps = createSharedRunnerDeps({
|
|
374
|
+
metrics,
|
|
372
375
|
config,
|
|
373
376
|
governanceSeams,
|
|
374
377
|
logger,
|
|
@@ -387,6 +390,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
387
390
|
...(config.hooksTimeoutMs !== undefined ? { timeoutMs: config.hooksTimeoutMs } : {}),
|
|
388
391
|
},
|
|
389
392
|
toolResultStore: fileBackend.toolResultStore,
|
|
393
|
+
checkpointStore: undefined,
|
|
390
394
|
sessionPolicyStore: fileBackend.sessionPolicyStore,
|
|
391
395
|
usageWindowStore: config.usageWindows ? fileBackend.usageWindowStore : undefined,
|
|
392
396
|
orgMemoryAdmission,
|
|
@@ -397,7 +401,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
397
401
|
...sharedRunnerDeps,
|
|
398
402
|
sessionStore,
|
|
399
403
|
...(memoryEngine ? { memoryBackend: memoryEngine.backend, memoryEngineDir: memoryEngine.root } : {}),
|
|
400
|
-
checkpointStore: fileBackend.checkpointStore,
|
|
401
404
|
onError: engineOnError,
|
|
402
405
|
onAsk,
|
|
403
406
|
...(config.projectMemoryEnabled
|
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,30 +66,35 @@ 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")
|
|
88
95
|
return 400;
|
|
89
96
|
if (code === "config.invalid_checkpoint_store")
|
|
90
97
|
return 400;
|
|
91
|
-
if (code === "config.tool_mount_denied")
|
|
92
|
-
return 400;
|
|
93
98
|
if (code === "config.memory_capture_unsupported")
|
|
94
99
|
return 409;
|
|
95
100
|
if (!code.startsWith("resume_at."))
|
|
@@ -166,10 +171,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
166
171
|
const preemptCtrl = new AbortController();
|
|
167
172
|
if (preemptEligible)
|
|
168
173
|
preemptable?.set(taskId, preemptCtrl);
|
|
169
|
-
const cancelledResult = () => {
|
|
170
|
-
const ctx = buildCancelContext(taskId, startedAt);
|
|
171
|
-
return { taskId, sessionId: spec.sessionId, terminal: { kind: "failed", code: "cancelled", message: "cancelled by user" }, stats: { turns: 0, tokens: 0 }, ...(ctx ? { cancelContext: ctx } : {}) };
|
|
172
|
-
};
|
|
174
|
+
const cancelledResult = (engineResult) => mintCancelledResult({ taskId, sessionId: spec.sessionId ?? "", startedAtMs: startedAt, message: "cancelled by user", ...(engineResult !== undefined ? { engineResult } : {}) });
|
|
173
175
|
const recordDone = (result, status) => {
|
|
174
176
|
metrics?.addGauge("runs_active", -1);
|
|
175
177
|
metrics?.inc("tasks_total", { status });
|
|
@@ -216,6 +218,9 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
216
218
|
heartbeat.unref?.();
|
|
217
219
|
const anchor = new TurnAnchorCapture(captureTurnAnchor, () => metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
|
|
218
220
|
let reached;
|
|
221
|
+
let observedDone;
|
|
222
|
+
let cancelledAtDone = false;
|
|
223
|
+
let settledAsCancel = false;
|
|
219
224
|
let steerableStreamRef;
|
|
220
225
|
let legLive = true;
|
|
221
226
|
const notifiedKeys = new NotifiedKeys();
|
|
@@ -343,6 +348,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
343
348
|
steerableStreamRef = stream;
|
|
344
349
|
steerable?.set(taskId, stream);
|
|
345
350
|
for await (const ev of stream) {
|
|
351
|
+
if (ev.type === "done") {
|
|
352
|
+
observedDone = ev.result;
|
|
353
|
+
cancelledAtDone = cancelCtrl.signal.aborted;
|
|
354
|
+
}
|
|
346
355
|
fleetPublisher?.onEvent(ev);
|
|
347
356
|
await flushPromptManifest();
|
|
348
357
|
switch (ev.type) {
|
|
@@ -377,12 +386,14 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
377
386
|
}
|
|
378
387
|
evictIfConflict(runner, spec.sessionId, ev.result);
|
|
379
388
|
{
|
|
380
|
-
const
|
|
389
|
+
const settled = cancelledAtDone ? cancelledResult(ev.result) : ev.result;
|
|
390
|
+
settledAsCancel = failureCodeOf(settled.terminal) === CANCELLED_CODE;
|
|
391
|
+
const safe = await withModelUsage(stripResumeToken(settled));
|
|
381
392
|
await sink.appendDone(safe);
|
|
382
393
|
reached = { kind: "done", result: safe };
|
|
383
394
|
const plane = terminalProjection(safe.terminal);
|
|
384
395
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
385
|
-
recordDone(safe, plane.status);
|
|
396
|
+
recordDone(safe, failureCodeOf(safe.terminal) === CANCELLED_CODE ? CANCELLED_CODE : plane.status);
|
|
386
397
|
}
|
|
387
398
|
continue;
|
|
388
399
|
}
|
|
@@ -429,15 +440,16 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
429
440
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=flush-clean-cancel ${errText(e)}`);
|
|
430
441
|
}
|
|
431
442
|
const c = cancelledResult();
|
|
443
|
+
settledAsCancel = failureCodeOf(c.terminal) === CANCELLED_CODE;
|
|
432
444
|
try {
|
|
433
|
-
await append("failed", { errorMessage: "cancelled by user", errorCode:
|
|
445
|
+
await append("failed", { errorMessage: "cancelled by user", errorCode: CANCELLED_CODE });
|
|
434
446
|
}
|
|
435
447
|
catch (e) {
|
|
436
448
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-clean-cancel ${errText(e)}`);
|
|
437
449
|
}
|
|
438
450
|
reached = { kind: "done", result: c };
|
|
439
451
|
await runStore.setTerminal(taskId, "failed", c, terminalProjection(c.terminal).errorMessage ?? null);
|
|
440
|
-
recordDone(c,
|
|
452
|
+
recordDone(c, CANCELLED_CODE);
|
|
441
453
|
}
|
|
442
454
|
}
|
|
443
455
|
catch (err) {
|
|
@@ -451,6 +463,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
451
463
|
}
|
|
452
464
|
if (reached?.kind === "done") {
|
|
453
465
|
const plane = terminalProjection(reached.result.terminal);
|
|
466
|
+
settledAsCancel = failureCodeOf(reached.result.terminal) === CANCELLED_CODE;
|
|
454
467
|
await runStore.setTerminal(taskId, plane.status, reached.result, plane.errorMessage ?? null);
|
|
455
468
|
}
|
|
456
469
|
else if (reached?.kind === "suspended") {
|
|
@@ -459,10 +472,11 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
459
472
|
else if (reached?.kind === "needs_review") {
|
|
460
473
|
await runStore.setNeedsReview(taskId);
|
|
461
474
|
}
|
|
462
|
-
else if (cancelCtrl.signal.aborted) {
|
|
475
|
+
else if (cancelCtrl.signal.aborted && observedDone === undefined) {
|
|
463
476
|
const c = cancelledResult();
|
|
477
|
+
settledAsCancel = failureCodeOf(c.terminal) === CANCELLED_CODE;
|
|
464
478
|
try {
|
|
465
|
-
await append("failed", { errorMessage: "cancelled by user", errorCode:
|
|
479
|
+
await append("failed", { errorMessage: "cancelled by user", errorCode: CANCELLED_CODE });
|
|
466
480
|
}
|
|
467
481
|
catch (e) {
|
|
468
482
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-thrown-cancel ${errText(e)}`);
|
|
@@ -470,6 +484,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
470
484
|
await runStore.setTerminal(taskId, "failed", c, terminalProjection(c.terminal).errorMessage ?? null);
|
|
471
485
|
}
|
|
472
486
|
else {
|
|
487
|
+
settledAsCancel = false;
|
|
473
488
|
const safeMsg = redactErrorMessage(message) ?? message;
|
|
474
489
|
try {
|
|
475
490
|
await append("failed", { errorMessage: safeMsg });
|
|
@@ -483,13 +498,13 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
483
498
|
catch {
|
|
484
499
|
}
|
|
485
500
|
if (reached?.kind === "done")
|
|
486
|
-
recordDone(reached.result, failureCodeOf(reached.result.terminal) ===
|
|
501
|
+
recordDone(reached.result, failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : terminalProjection(reached.result.terminal).status);
|
|
487
502
|
else if (reached?.kind === "suspended") {
|
|
488
503
|
metrics?.addGauge("runs_active", -1);
|
|
489
504
|
metrics?.inc("tasks_total", { status: "suspended" });
|
|
490
505
|
}
|
|
491
|
-
else if (
|
|
492
|
-
recordDone(null,
|
|
506
|
+
else if (settledAsCancel) {
|
|
507
|
+
recordDone(null, CANCELLED_CODE);
|
|
493
508
|
}
|
|
494
509
|
else {
|
|
495
510
|
recordDone(null, "failed");
|
|
@@ -500,14 +515,14 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
500
515
|
unregisterEngineNotice();
|
|
501
516
|
if (fleetPublisher) {
|
|
502
517
|
if (reached?.kind === "done")
|
|
503
|
-
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));
|
|
504
519
|
else if (reached?.kind === "suspended")
|
|
505
520
|
fleetPublisher.onTerminal("suspended");
|
|
506
521
|
else if (reached?.kind === "needs_review")
|
|
507
522
|
fleetPublisher.onTerminal("needs_review");
|
|
508
523
|
else
|
|
509
|
-
fleetPublisher.onTerminal(
|
|
510
|
-
...(
|
|
524
|
+
fleetPublisher.onTerminal(settledAsCancel ? CANCELLED_CODE : "failed", {
|
|
525
|
+
...(settledAsCancel ? { stoppedBy: "user" } : {}),
|
|
511
526
|
...(spec.sessionId ? { transcriptId: spec.sessionId } : {}),
|
|
512
527
|
});
|
|
513
528
|
}
|
package/dist/task-a2a.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComplianceDenies } from "@sema-agent/core";
|
|
1
|
+
import { resolveComplianceDenies, TOOL_WIRE_NAME_MAX_CHARS } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "./security.js";
|
|
3
3
|
export const MAX_REQUEST_A2A_PEERS = 32;
|
|
4
4
|
export function a2aInjectionHonored(config) {
|
|
@@ -19,7 +19,7 @@ function normalizeA2aPeer(e) {
|
|
|
19
19
|
if (!e || typeof e !== "object")
|
|
20
20
|
return null;
|
|
21
21
|
const o = e;
|
|
22
|
-
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length >
|
|
22
|
+
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length > TOOL_WIRE_NAME_MAX_CHARS)
|
|
23
23
|
return null;
|
|
24
24
|
if (typeof o.url !== "string" || !HTTP_URL_RE.test(o.url))
|
|
25
25
|
return null;
|
package/dist/task-mcp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mcpInjectionDroppedNotice, resolveComplianceDenies } from "@sema-agent/core";
|
|
1
|
+
import { mcpInjectionDroppedNotice, resolveComplianceDenies, TOOL_WIRE_NAME_MAX_CHARS } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "./security.js";
|
|
3
3
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "./mcp-content-origin.js";
|
|
4
4
|
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
@@ -26,7 +26,7 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
26
26
|
if (!e || typeof e !== "object")
|
|
27
27
|
return bad("entry");
|
|
28
28
|
const o = e;
|
|
29
|
-
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length >
|
|
29
|
+
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length > TOOL_WIRE_NAME_MAX_CHARS)
|
|
30
30
|
return bad("name");
|
|
31
31
|
const t = o.transport;
|
|
32
32
|
if (!t || typeof t !== "object")
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -312,15 +312,28 @@ export interface FsWriteGateWiring {
|
|
|
312
312
|
/**
|
|
313
313
|
* 🔴 S-177 / codex r2 [high](验真后修)—— **执行环境的 home**(core `PathRuleBases.home`,`~/…` 形的基)。
|
|
314
314
|
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
315
|
+
* 🔴 **S-213④ 起本字段在远端车道上也可以有值**:四只远端 adapter 此前一个都不声明 `ExecutionEnv.homeDir`,
|
|
316
|
+
* 而本字段那时只在 `remoteExec` 整个缺席时才铸 —— 于是**每一条远端部署的 `~/` 规则都恒 422**(接入审计
|
|
317
|
+
* M13)。修法不是放松下面那道门,而是让部署**真的知道** home:`execution-lane-caps.ts` 的
|
|
318
|
+
* `executionLaneHomeDir` 是唯一属主,`boot/execution-env.ts` 把它递给 adapter 的 `homeDir` 座、
|
|
319
|
+
* `boot/resolve-spec.ts` / `boot/parked-revive-gate.ts` 把**同一个值**递成本字段。两处同源是承重的:
|
|
320
|
+
* 各算一份就会出现「422 门放行而引擎判 unreadable」或反过来。
|
|
319
321
|
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
322
|
+
* 🔴 **S-213⑦ 改真话**(亲读 core 7.12.0 `dist/core/permission-rule-model.js:365-372` /
|
|
323
|
+
* `:340-350`,推翻本注此前的两句):
|
|
324
|
+
* · core 对缺席的 `home` **不**回落 `os.homedir()` —— `resolvePathPattern` 走
|
|
325
|
+
* `lexicalNormalAbsolutePathOf(baseValueOf("home", bases) ?? "")`,base 缺席 ⇒ `undefined` ⇒ 直接回
|
|
326
|
+
* `{missingBase:"home"}`,该调用的射程判 `unreadable`(fail-closed 的 ask,人来清),从不猜一个目录。
|
|
327
|
+
* 换言之引擎侧本来就没有「拿 server 的 home 去匹配远端目标」这条静默腿(那句旧注是本仓自己的推断)。
|
|
328
|
+
* · core **有**「这条规则需要哪些 base」的查询口 —— 根导出的 `ruleBasesNeeded(rule)`
|
|
329
|
+
* (`//abs` ⇒ 空、`~/` ⇒ `["home"]`、`/x` ⇒ `["root"]`、其余 ⇒ `["cwd"]`),`resolvePathPattern`
|
|
330
|
+
* 自己也读它。本仓的判据腿今天读的是 core 解析出的规范 command 前缀,与该查询口**同一分类**、
|
|
331
|
+
* 不是第二套文法;两者哪天分叉由这条注负责被人看见。
|
|
332
|
+
*
|
|
333
|
+
* ⇒ 本字段在**这条部署的执行车道 home 已知**时在场(`executionLaneHomeDir`,见 `execution-lane-caps.ts`)。
|
|
334
|
+
* 缺席时 `~/` 形规则由 {@link rejectUnmatchableSettingsNames} **响亮拒**(422),而不是让它编译成一条
|
|
335
|
+
* core 只会判 `unreadable` 的规则 —— 两道门同向(都不猜目录),422 只是把同一个拒挪到更早、更响的地方:
|
|
336
|
+
* 运维在**下单那一刻**就知道这条规则这条车道读不了,而不是等到跑起来每次都弹一张 ask。
|
|
324
337
|
*/
|
|
325
338
|
taskHome?: string;
|
|
326
339
|
/** The session scratchpad dir (envFacts.scratchpadDir, [ref]③) — writes there are always auto-allowed. */
|
package/dist/task-settings.js
CHANGED
|
@@ -279,8 +279,8 @@ function rejectUnmatchableSettingsNames(perms, pathBases) {
|
|
|
279
279
|
const parsed = parseRuleText(name, behavior);
|
|
280
280
|
if ("rule" in parsed && ruleToolGrammarOf(parsed.rule.tool) === "path" && parsed.rule.command.startsWith("~/")) {
|
|
281
281
|
throw new Error(`${field} has a home-relative path rule (${name}) but this deployment cannot resolve the EXECUTION environment's home directory — ` +
|
|
282
|
-
`
|
|
283
|
-
`Spell the path absolutely (${"Edit(//abs/path/**)"} form), or run the task on a lane whose home this worker knows.`);
|
|
282
|
+
`the engine never guesses one: without a home base the rule resolves to "unreadable" and every matching call stops to ask, so it guards nothing. ` +
|
|
283
|
+
`Spell the path absolutely (${"Edit(//abs/path/**)"} form), declare the lane's home with REMOTE_EXEC_HOME_DIR, or run the task on a lane whose home this worker knows.`);
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
}
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -145,9 +145,10 @@ export interface ToolApprovalFrame {
|
|
|
145
145
|
origin?: string;
|
|
146
146
|
/**
|
|
147
147
|
* **S-185 / [ref]**(core 7.10.0;**ADDITIVE**,`"tool_approval"` only)—— 这一问**为什么**到了人手上:
|
|
148
|
-
*
|
|
149
|
-
* (`AUTO_MODE_UNAVAILABLE_CAUSES
|
|
150
|
-
* 逐字透传。
|
|
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` 同名键的顶注)。
|
|
151
152
|
*
|
|
152
153
|
* 🔴 **缺席 ≠「分类器好着呢」**:缺席同时覆盖三形 —— 分类器答上了 / 这只 ask 没资格走分类器
|
|
153
154
|
* (`classifierMayAnswer` 的闭表)/ 本部署压根没接分类器。消费端**禁**把缺席读成对分类器健康的断言。
|
|
@@ -176,13 +177,18 @@ export interface ToolApprovalFrame {
|
|
|
176
177
|
* 执行的那串字节(卡上显示的与真跑的不是同一个东西),**比不披露更坏**;所以 server 只报「有」,显形
|
|
177
178
|
* 归壳的渲染面(转义/高亮/加标记,壳自己定)。
|
|
178
179
|
*
|
|
179
|
-
* **判据素材** = `boundArgs`
|
|
180
|
+
* **判据素材** = `boundArgs` 里对**原字节**(`redactDeep` 之前)的那一次序列化,且在**字节帽判定之前**算:
|
|
180
181
|
* · 超帽时 args 以 `argsOmitted:true` 缺席上帧(卡上根本没有 args)—— 那正是人最看不见输入的一格,
|
|
181
182
|
* 更需要知道「你看不到的那份里有隐形字符」,所以帽不影响本键;
|
|
182
|
-
* · 序列化失败(循环引用 / 抛错的 `toJSON` / BigInt)⇒ 两件事一起交白卷:args
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
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 定案明确不做),不是在这条腿上加特判。
|
|
186
192
|
*
|
|
187
193
|
* 判据**不看 `message`**(引擎/策略写的说明文本,不是待执行输入):本键被壳读作「**待执行的输入**可能
|
|
188
194
|
* 在骗你的眼睛」,把两个来源折进一个布尔会让壳无法判断该给哪一段加显形标记。
|
package/dist/tool-approval.js
CHANGED
|
@@ -2,7 +2,7 @@ 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";
|
|
@@ -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
|
}
|