@sema-agent/server 7.111.0 → 7.113.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/MIGRATION.md +68 -0
- package/USAGE.md +3 -3
- package/dist/approval-ask-audit-store.d.ts +3 -4
- package/dist/approval-ask-audit-store.js +3 -3
- package/dist/approval-card.d.ts +4 -4
- package/dist/boot/coordinators.js +1 -1
- package/dist/boot/stores.js +4 -6
- package/dist/boot/workflow-orchestration.js +4 -4
- package/dist/config-types.d.ts +1 -1
- package/dist/config.js +2 -1
- package/dist/hooks/hook-runner.d.ts +7 -2
- package/dist/hooks/hook-runner.js +48 -4
- package/dist/host-decision.d.ts +5 -3
- package/dist/http/admission.js +12 -3
- package/dist/http/request-guards.js +2 -2
- package/dist/http/resume-legs.d.ts +14 -24
- package/dist/http/resume-legs.js +91 -29
- package/dist/http/route-ctx.d.ts +64 -17
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/background.js +2 -2
- package/dist/http/routes/mcp-reconnect.js +2 -2
- package/dist/http/routes/notify-wake.js +3 -3
- package/dist/http/routes/runs.js +22 -19
- package/dist/http/routes/session-sync.js +6 -6
- package/dist/http/routes/sessions.js +20 -20
- package/dist/http/routes/tasks.d.ts +3 -2
- package/dist/http/routes/tasks.js +38 -45
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +1 -1
- package/dist/http/server.js +1 -2
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/local-data-root.d.ts +14 -0
- package/dist/local-data-root.js +5 -0
- package/dist/observability/corrupt-read-seat.d.ts +46 -66
- package/dist/observability/corrupt-read-seat.js +74 -59
- package/dist/observability/fail-open.d.ts +29 -1
- package/dist/observability/fail-open.js +26 -1
- package/dist/observability/run-terminal.js +39 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +1 -5
- package/dist/orchestration/workflow-agent-session-index.js +2 -2
- package/dist/orchestration/workflow-notify-journal.d.ts +1 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -2
- package/dist/plugins/approval-ask-store-file.d.ts +1 -4
- package/dist/plugins/approval-ask-store-file.js +2 -2
- package/dist/plugins/file-resume-anchor-store.d.ts +1 -6
- package/dist/plugins/file-resume-anchor-store.js +2 -4
- package/dist/plugins/file-run-store.d.ts +0 -4
- package/dist/plugins/file-run-store.js +1 -3
- package/dist/plugins/send-file-ledger.d.ts +1 -5
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/store-backend.js +8 -9
- package/dist/plugins/store-contracts.d.ts +40 -7
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/user-input-ledger-file.d.ts +1 -5
- package/dist/plugins/user-input-ledger-file.js +2 -2
- package/dist/run-cancel-context.d.ts +59 -7
- package/dist/run-cancel-context.js +26 -0
- package/dist/run-local.d.ts +1 -1
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +7 -5
- package/dist/runs.js +18 -13
- package/dist/spec-fields.js +0 -1
- package/dist/tool-approval.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.js +2 -3
- package/dist/trace/redact.d.ts +18 -0
- package/dist/trace/redact.js +4 -0
- package/dist/trace/self-minted-wire.d.ts +22 -0
- package/dist/trace/self-minted-wire.js +8 -0
- package/dist/unattended-permission-mode.d.ts +9 -2
- package/dist/unattended-permission-mode.js +5 -1
- package/dist/user-input-ledger.d.ts +24 -5
- package/dist/user-input-ledger.js +46 -11
- package/package.json +4 -3
- package/scripts/retired-mode-seed-drain-local.mjs +317 -0
|
@@ -54,7 +54,7 @@ export class FileApprovalAskStore {
|
|
|
54
54
|
replayRejectedCount = 0;
|
|
55
55
|
corruptLineCount = 0;
|
|
56
56
|
corruptQuarantinePath;
|
|
57
|
-
constructor(root
|
|
57
|
+
constructor(root) {
|
|
58
58
|
const dir = join(root, APPROVAL_ASK_DIR);
|
|
59
59
|
ensureDir(dir);
|
|
60
60
|
this.tmpDir = join(dir, ".tmp");
|
|
@@ -62,7 +62,7 @@ export class FileApprovalAskStore {
|
|
|
62
62
|
this.path = join(dir, APPROVAL_ASK_JOURNAL_FILE);
|
|
63
63
|
this.core = new InMemoryApprovalAskStore({ now: () => this.clockRead() });
|
|
64
64
|
const tornTail = existsSync(this.path) && !endsAtRecordBoundary(this.path);
|
|
65
|
-
const { records, skipped } = replayJsonlSkipping(this.path, "approval-ask-journal", ASK_JOURNAL_SHAPE
|
|
65
|
+
const { records, skipped } = replayJsonlSkipping(this.path, "approval-ask-journal", ASK_JOURNAL_SHAPE);
|
|
66
66
|
this.corruptLineCount = skipped;
|
|
67
67
|
for (const rec of records)
|
|
68
68
|
this.replayRecord(rec);
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import type { Logger } from "../observability/logger.js";
|
|
2
1
|
export declare class FileResumeAnchorStore {
|
|
3
2
|
private readonly dir;
|
|
4
3
|
/** sessionId → (eventId → {entryId, owner}) — the same in-memory shape MemoryResumeAnchorStore holds, hydrated on ctor. */
|
|
5
4
|
private readonly bySession;
|
|
6
5
|
/** sessionId → the open append log (one O_APPEND fd per session, lazily opened on first put, held for lifetime). */
|
|
7
6
|
private readonly logs;
|
|
8
|
-
|
|
9
|
-
/** @param opts.warn corrupt-read disclosure seat (S-582; absent ⇒ the replay owner falls back to `console.warn`). */
|
|
10
|
-
constructor(root: string, opts?: {
|
|
11
|
-
warn?: Logger["warn"];
|
|
12
|
-
});
|
|
7
|
+
constructor(root: string);
|
|
13
8
|
/** `<dir>/<sanitizePathComponent(sessionId)>.jsonl` — the per-session log path (sessionId is uuidv7, already safe). */
|
|
14
9
|
private pathFor;
|
|
15
10
|
/** Replay every session log into the in-memory index on boot (torn-tail-safe via the replay owner; last-wins upsert
|
|
@@ -16,10 +16,8 @@ export class FileResumeAnchorStore {
|
|
|
16
16
|
dir;
|
|
17
17
|
bySession = new Map();
|
|
18
18
|
logs = new Map();
|
|
19
|
-
|
|
20
|
-
constructor(root, opts) {
|
|
19
|
+
constructor(root) {
|
|
21
20
|
this.dir = join(root, "resume-anchors");
|
|
22
|
-
this.warn = opts?.warn;
|
|
23
21
|
ensureDir(this.dir);
|
|
24
22
|
this.hydrate();
|
|
25
23
|
}
|
|
@@ -38,7 +36,7 @@ export class FileResumeAnchorStore {
|
|
|
38
36
|
if (!name.endsWith(".jsonl"))
|
|
39
37
|
continue;
|
|
40
38
|
const key = name.slice(0, -".jsonl".length);
|
|
41
|
-
const { records } = replayJsonlSkipping(join(this.dir, name), "resume-anchor", ANCHOR_RECORD_SHAPE
|
|
39
|
+
const { records } = replayJsonlSkipping(join(this.dir, name), "resume-anchor", ANCHOR_RECORD_SHAPE);
|
|
42
40
|
if (!records.length)
|
|
43
41
|
continue;
|
|
44
42
|
const m = this.bySession.get(key) ?? new Map();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
2
|
import type { TaskStatus } from "@sema-agent/core";
|
|
3
|
-
import type { Logger } from "../observability/logger.js";
|
|
4
3
|
import { StoreBootRefusalError, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
5
4
|
import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
|
|
6
5
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
@@ -46,12 +45,9 @@ export declare class FileRunStore {
|
|
|
46
45
|
/** [ref] 件B(census 第 29 行终局):`RUN_STORE_STRICT_HYDRATE` 的**部署级**表态。
|
|
47
46
|
* `true` ⇒ hydrate 的 readdir 失败拒启;缺席/`false` ⇒ 件6 的记债腿,行为字节不变。 */
|
|
48
47
|
private readonly strictHydrate;
|
|
49
|
-
/** S-582:三个盘面坏行的腐读披露座(`LocalBackend` 接 `discloseWarn`;缺席 ⇒ 重放属主退回 `console.warn`)。 */
|
|
50
|
-
private readonly corruptReadWarn;
|
|
51
48
|
constructor(root: string, opts?: {
|
|
52
49
|
error?(msg: string, fields?: Record<string, unknown>): void;
|
|
53
50
|
strictHydrate?: boolean;
|
|
54
|
-
warn?: Logger["warn"];
|
|
55
51
|
});
|
|
56
52
|
private runDir;
|
|
57
53
|
private runJsonPath;
|
|
@@ -62,11 +62,9 @@ export class FileRunStore {
|
|
|
62
62
|
claimLocks = new Map();
|
|
63
63
|
logger;
|
|
64
64
|
strictHydrate;
|
|
65
|
-
corruptReadWarn;
|
|
66
65
|
constructor(root, opts) {
|
|
67
66
|
this.logger = opts;
|
|
68
67
|
this.strictHydrate = opts?.strictHydrate === true;
|
|
69
|
-
this.corruptReadWarn = opts?.warn;
|
|
70
68
|
this.runsDir = join(root, "runs");
|
|
71
69
|
this.activeDir = join(root, "runs", "active");
|
|
72
70
|
this.tmpDir = join(root, "tmp");
|
|
@@ -137,7 +135,7 @@ export class FileRunStore {
|
|
|
137
135
|
}
|
|
138
136
|
}
|
|
139
137
|
readRecords(path, shape) {
|
|
140
|
-
return replayJsonlSkipping(path, "run-store", shape
|
|
138
|
+
return replayJsonlSkipping(path, "run-store", shape);
|
|
141
139
|
}
|
|
142
140
|
refuseIfUnreadable(jsonPath, dir) {
|
|
143
141
|
try {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Pool as MysqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
3
|
import type { IssuedFileLink } from "./send-user-file.js";
|
|
4
|
-
import type { Logger } from "../observability/logger.js";
|
|
5
4
|
/** Ledger scope sentinel for single-user issuance (no principal) — mirrors scopeSegment's "_" key segment. */
|
|
6
5
|
export declare const SENDFILE_SINGLE_USER_SCOPE = "_";
|
|
7
6
|
export interface SendFileLedgerEntry {
|
|
@@ -48,10 +47,7 @@ export declare class FileSendFileLedger implements SendFileLedger {
|
|
|
48
47
|
private readonly path;
|
|
49
48
|
private readonly mem;
|
|
50
49
|
private log;
|
|
51
|
-
|
|
52
|
-
constructor(root: string, opts?: {
|
|
53
|
-
warn?: Logger["warn"];
|
|
54
|
-
});
|
|
50
|
+
constructor(root: string);
|
|
55
51
|
record(entry: SendFileLedgerEntry): Promise<void>;
|
|
56
52
|
listByScope(scope: string, limit: number, beforeId?: string): Promise<SendFileLedgerEntry[]>;
|
|
57
53
|
/** Release the append-log fd (LocalBackend.close → graceful-restart re-open). */
|
|
@@ -80,11 +80,11 @@ export class FileSendFileLedger {
|
|
|
80
80
|
path;
|
|
81
81
|
mem = new MemorySendFileLedger();
|
|
82
82
|
log;
|
|
83
|
-
constructor(root
|
|
83
|
+
constructor(root) {
|
|
84
84
|
const dir = join(root, "sendfile-ledger");
|
|
85
85
|
ensureDir(dir);
|
|
86
86
|
this.path = join(dir, "links.jsonl");
|
|
87
|
-
for (const r of replayJsonlSkipping(this.path, "send-file-ledger", LEDGER_ROW_SHAPE
|
|
87
|
+
for (const r of replayJsonlSkipping(this.path, "send-file-ledger", LEDGER_ROW_SHAPE).records)
|
|
88
88
|
void this.mem.record(r);
|
|
89
89
|
}
|
|
90
90
|
async record(entry) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import {
|
|
2
|
+
import { createCorruptReadFailOpenSeat, createDurableAbsenceSeat } from "../observability/corrupt-read-seat.js";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { AdoptionError, FileStorageBackend, FileSessionRepo } from "@sema-agent/core";
|
|
5
5
|
import { FileWorkflowJournalStore } from "@sema-agent/core";
|
|
@@ -230,7 +230,7 @@ class LocalBackend {
|
|
|
230
230
|
this.fileBackend = acquireLocalBackendWithWait(() => new FileStorageBackend({
|
|
231
231
|
root,
|
|
232
232
|
...(config.fileHistory !== undefined ? { fileHistory: config.fileHistory } : {}),
|
|
233
|
-
onCorruptRead:
|
|
233
|
+
onCorruptRead: createDurableAbsenceSeat((msg, fields) => this.discloseWarn(msg, fields), root, "file-history = a rewind boundary or scope read as missing"),
|
|
234
234
|
}), config.storeLockWaitMs, (fields) => this.onWarn?.("store_dir_lock_wait", { ...fields }));
|
|
235
235
|
}
|
|
236
236
|
catch (e) {
|
|
@@ -241,22 +241,21 @@ class LocalBackend {
|
|
|
241
241
|
(tail === undefined ? "" : ` ${tail}`));
|
|
242
242
|
}
|
|
243
243
|
this.sessionStore = new LocalSessionStore(new FileSessionRepo(this.fileBackend.root, {
|
|
244
|
-
onCorruptRead:
|
|
244
|
+
onCorruptRead: createCorruptReadFailOpenSeat("server.session-repo.corrupt-record-skipped", noteReplayInSessionOpenScope),
|
|
245
245
|
}), this.fileBackend.root);
|
|
246
246
|
const corruptReadWarn = (msg, fields) => this.discloseWarn(msg, fields);
|
|
247
247
|
this.runStore = new FileRunStore(this.fileBackend.root, {
|
|
248
248
|
error: (msg, fields) => void (this.onError ?? this.onWarn)?.(msg, fields),
|
|
249
249
|
strictHydrate: config.runStoreStrictHydrate,
|
|
250
|
-
warn: corruptReadWarn,
|
|
251
250
|
});
|
|
252
|
-
this.resumeAnchorStore = new FileResumeAnchorStore(this.fileBackend.root
|
|
251
|
+
this.resumeAnchorStore = new FileResumeAnchorStore(this.fileBackend.root);
|
|
253
252
|
this.approvalExemptionStore = new FileApprovalExemptionStore(this.fileBackend.root, { warn: corruptReadWarn });
|
|
254
253
|
this.approvalNonceStore = new FileApprovalNonceStore(this.fileBackend.root, { warn: corruptReadWarn });
|
|
255
|
-
this.approvalAskStore = new FileApprovalAskStore(this.fileBackend.root
|
|
256
|
-
this.sendFileLedgerStore = new FileSendFileLedger(this.fileBackend.root
|
|
257
|
-
this.userInputLedgerStore = new FileUserInputLedgerStore(this.fileBackend.root
|
|
254
|
+
this.approvalAskStore = new FileApprovalAskStore(this.fileBackend.root);
|
|
255
|
+
this.sendFileLedgerStore = new FileSendFileLedger(this.fileBackend.root);
|
|
256
|
+
this.userInputLedgerStore = new FileUserInputLedgerStore(this.fileBackend.root);
|
|
258
257
|
this.workflowJournalStore = new FileWorkflowJournalStore(this.fileBackend.root, undefined, {
|
|
259
|
-
onCorruptRead:
|
|
258
|
+
onCorruptRead: createCorruptReadFailOpenSeat("server.workflow-journal.corrupt-record-skipped"),
|
|
260
259
|
});
|
|
261
260
|
this.outcomeSinkInst = new FileOutcomeSink(this.fileBackend.root);
|
|
262
261
|
const ownControlDir = fileMemoryControlDirFor(config);
|
|
@@ -94,16 +94,49 @@ export interface RunCancelContext {
|
|
|
94
94
|
elapsedMs?: number;
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
|
-
*
|
|
97
|
+
* 🔴 S-947(cli [ref] 第 3 件 / L-212)—— **server 为什么中止了这条 run**:取消终局的机读成因,闭集。
|
|
98
98
|
*
|
|
99
|
-
*
|
|
100
|
-
* (`
|
|
101
|
-
*
|
|
102
|
-
*
|
|
99
|
+
* 病:非 detach 流断连、`POST /v1/runs/:id/cancel`、断连时有 ask 但 park 宽限用尽 —— 三件事在终局上**逐字同形**
|
|
100
|
+
* (`failed` + `terminal.code:"cancelled"`),壳只能按 `errorMessage` 文案猜。
|
|
101
|
+
*
|
|
102
|
+
* 每个词都必须有一个**真站点**(没有站点的词不许进表;普查与站点坐标见契约 §5.0d):
|
|
103
|
+
* · `client_disconnected` —— 非 detach 的 `POST /v1/tasks/stream` 客户端断连,断连那一刻本 run 没有流内未决 ask
|
|
104
|
+
* (`http/routes/tasks.ts` 的断连臂,立即中止);
|
|
105
|
+
* · `ask_park_grace_exhausted` —— 同一断连臂,但断连那一刻有流内未决 ask、已按窗到期同路转 park,宽限计时器用尽时 park 仍没落成;
|
|
106
|
+
* · `cancel_verb` —— `POST /v1/runs/:id/cancel`:同副本快路、跨副本耐久取消旗的轮询(四条腿的心跳 / 登记追平),
|
|
107
|
+
* 以及 park 中的 run 被它直接终局化(那一形不经 abort,成因照样是这条动词)。
|
|
108
|
+
* **刻意不在表里**(普查过、没有真站点):抢占(`preempt` 是优雅让出,终局是 `paused`,不是取消)、有序关停
|
|
109
|
+
* (S-929 只把未决卡转 park,从不中止活 run;进程退出后的行由收割器落「持有者死亡」判决,那不是本仓铸的取消终局)、
|
|
110
|
+
* `interrupt`(引擎的 turn 动词,终局 `completed` + `haltedByUser`)、detach 断连(不中止)。
|
|
111
|
+
*
|
|
112
|
+
* 词表属主 = 本文件(与它所在的 {@link PersistedTaskResult} 同住);**单写者** = `run-cancel-context.ts` 的 `createRunAbort`
|
|
113
|
+
* (中止那一刻登记)与 `mintCancelledResult`(终局铸点读它),自铸 wire 值登记 = `trace/self-minted-wire.ts` 的 `abortCause`
|
|
114
|
+
* (`satisfies Record<AbortCause, …>`:加 / 删一个词而登记没跟 ⇒ tsc 红)。
|
|
103
115
|
*/
|
|
104
|
-
export
|
|
116
|
+
export declare const ABORT_CAUSES: readonly ["client_disconnected", "ask_park_grace_exhausted", "cancel_verb"];
|
|
117
|
+
export type AbortCause = (typeof ABORT_CAUSES)[number];
|
|
118
|
+
/**
|
|
119
|
+
* server 自铸、挂在终局结果上的**侧记键**全集(都描述「server 这一侧发生了什么」,core 那一侧无从知道)。
|
|
120
|
+
* 本型是这一族键的**唯一登记处**(`trace/core-keyset-guard.ts` 的辖域线指向这里;那边另有一道编译期钉:
|
|
121
|
+
* core 的 `TaskResult` 哪天长出同名键 ⇒ 当场红 —— 同一个键两个铸者是本型存在要防的那件事)。
|
|
122
|
+
* · `cancelContext`([ref])—— 取消现场快照(分诊材料:多久没动静、起跑多久);
|
|
123
|
+
* · `abortCause`(S-947)—— 这次取消是谁 / 什么发起的({@link AbortCause})。
|
|
124
|
+
* 两键**同一只铸点**(`mintCancelledResult`)、**同一条在场闸**(「只补空座」:终局就是被那次中止打断的那一条,
|
|
125
|
+
* 才挂;与中止赛跑跑完的终局、引擎自带码的失败一律原样立住、两键都不挂)。差别只在缺席纪律:快照一键都证不出 ⇒ 整只缺席;
|
|
126
|
+
* 成因在铸点那一刻恒已知 ⇒ 取消终局上恒在场。
|
|
127
|
+
*/
|
|
128
|
+
export interface PersistedResultSideKeys {
|
|
105
129
|
cancelContext?: RunCancelContext;
|
|
106
|
-
|
|
130
|
+
abortCause?: AbortCause;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 落库/上 wire 的终局结果 = core 的 `TaskResult` **加上** server 自铸的可缺席侧记键({@link PersistedResultSideKeys})。
|
|
134
|
+
*
|
|
135
|
+
* additive 且可缺席,所以旧行、旧消费端逐字不受影响(`TaskResult` 仍可原样赋给本型)。⚠️ 这不是给「往 core 结果里塞私货」
|
|
136
|
+
* 开的口子:每加一个键都要能回答「为什么它属于 run 账本而不属于 core 的任务结果」—— 两键的答案都是它们描述的是
|
|
137
|
+
* **server 侧的取消现场**,core 那一侧根本不知道有人按了取消、谁按的。落点 = `result` JSON 列(三孪生整只存),**零新列**。
|
|
138
|
+
*/
|
|
139
|
+
export type PersistedTaskResult = TaskResult & PersistedResultSideKeys;
|
|
107
140
|
/**
|
|
108
141
|
* 🔴 run 店「持有它的实例丢了」那一判的**行上落款**(S-928 codex r1-F1 提为常量):三只 run 店的 `reapStale` 把心跳过期的 running 行翻成
|
|
109
142
|
* `failed` 并写下这一句。它是 run 店死亡判决留在行上的证据之一(另一只 = {@link RUN_ORPHANED_ERROR})—— 用户输入受理账本的收敛判词
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export class StoreBootRefusalError extends Error {
|
|
2
2
|
}
|
|
3
|
+
export const ABORT_CAUSES = ["client_disconnected", "ask_park_grace_exhausted", "cancel_verb"];
|
|
3
4
|
export const RUN_STALLED_ERROR = "run stalled (instance lost?)";
|
|
4
5
|
export const RUN_ORPHANED_ERROR = "run orphaned by engine restart";
|
|
5
6
|
export function buildRunOrphanedError(heldBy) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Logger } from "../observability/logger.js";
|
|
2
1
|
import { UserInputLedgerFold, type NewUserInputAcceptance, type OpenScanCursor, type OpenScanPage, type SettleHow, type UserInputAcceptance, type UserInputLedgerRecord, type UserInputLedgerStore } from "../user-input-ledger.js";
|
|
3
2
|
/** 账本文件名(取证按文件字节对账 —— 崩溃语义的真源是文件)。 */
|
|
4
3
|
export declare const USER_INPUT_LEDGER_FILE = "ledger.jsonl";
|
|
@@ -24,10 +23,7 @@ export declare class FileUserInputLedgerStore extends MemoryUserInputLedgerStore
|
|
|
24
23
|
/** 账本绝对路径。 */
|
|
25
24
|
readonly path: string;
|
|
26
25
|
private log;
|
|
27
|
-
|
|
28
|
-
constructor(root: string, opts?: {
|
|
29
|
-
warn?: Logger["warn"];
|
|
30
|
-
});
|
|
26
|
+
constructor(root: string);
|
|
31
27
|
protected persist(rec: UserInputLedgerRecord): void;
|
|
32
28
|
/** 收追加口(LocalBackend.close → 优雅重启重开)。core `AppendLog.close()` 自己吞 `closeSync` 的错,不抛。 */
|
|
33
29
|
dispose(): void;
|
|
@@ -88,12 +88,12 @@ const RECORD_SHAPE = {
|
|
|
88
88
|
export class FileUserInputLedgerStore extends MemoryUserInputLedgerStore {
|
|
89
89
|
path;
|
|
90
90
|
log;
|
|
91
|
-
constructor(root
|
|
91
|
+
constructor(root) {
|
|
92
92
|
super();
|
|
93
93
|
const dir = join(root, USER_INPUT_LEDGER_DIR);
|
|
94
94
|
ensureDir(dir);
|
|
95
95
|
this.path = join(dir, USER_INPUT_LEDGER_FILE);
|
|
96
|
-
for (const rec of replayJsonlSkipping(this.path, "user-input-ledger", RECORD_SHAPE
|
|
96
|
+
for (const rec of replayJsonlSkipping(this.path, "user-input-ledger", RECORD_SHAPE).records)
|
|
97
97
|
this.fold.apply(rec);
|
|
98
98
|
}
|
|
99
99
|
persist(rec) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PersistedTaskResult, RunCancelContext } from "./plugins/store-contracts.js";
|
|
1
|
+
import type { AbortCause, PersistedTaskResult, RunCancelContext } from "./plugins/store-contracts.js";
|
|
2
2
|
/**
|
|
3
3
|
* @param taskId 这条 run 的账本 id(turn 活性登记的键)。
|
|
4
4
|
* @param startedAtMs 这条 run 起跑的墙钟时刻(epoch ms):取消腿手上有行就用行的 `createdAt`,
|
|
@@ -43,11 +43,23 @@ export declare const CANCELLED_CODE = "cancelled";
|
|
|
43
43
|
* · `cancelContext`:已经有快照就不重铸(重铸会把「上一条引擎事件多久以前」重新计时)。
|
|
44
44
|
*
|
|
45
45
|
* ## 为什么不判「这条 failed 是不是 abort 形」
|
|
46
|
-
*
|
|
47
|
-
* `
|
|
46
|
+
* 判据在**调用点**:调用点问的是「本腿这次是不是被 server 中止了」—— 本腿 {@link RunAbort} 句柄上登记的成因
|
|
47
|
+
* (`cause()` 在场),那是 server 自己的事实。让本函数去嗅 `terminal.message` 的字面就是再造事实
|
|
48
48
|
* —— [ref] 二那次事故的同一个形。
|
|
49
|
+
*
|
|
50
|
+
* ## S-947:成因(`abortCause`)与码、快照**同进同退**
|
|
51
|
+
* `cause` 是**必填**:本函数是 server 铸取消终局的唯一出口,而每一次取消都有一个发起者 ⇒ 「本仓铸的取消终局上恒有
|
|
52
|
+
* `abortCause`」由签名保证,不靠调用方记得。它走与补码、补快照**同一条**空座闸 —— 引擎给的终局不是无码的 failed
|
|
53
|
+
* (与中止赛跑跑完了 / 引擎自带码)⇒ 整只原样返回,成因也不挂(那条终局不是这次中止的结果,挂上就是「完成了却带着取消便签」)。
|
|
49
54
|
*/
|
|
50
|
-
export declare function mintCancelledResult(args: {
|
|
55
|
+
export declare function mintCancelledResult<R extends PersistedTaskResult>(args: CancelMintArgs & {
|
|
56
|
+
engineResult: R;
|
|
57
|
+
}): R;
|
|
58
|
+
export declare function mintCancelledResult(args: CancelMintArgs & {
|
|
59
|
+
engineResult?: undefined;
|
|
60
|
+
}): PersistedTaskResult;
|
|
61
|
+
/** {@link mintCancelledResult} 的公共实参(`engineResult` 另列:在场与否决定返回型)。 */
|
|
62
|
+
interface CancelMintArgs {
|
|
51
63
|
/** 这条 run 的账本 id(活性登记的键;账本不在场时给空串,快照按「证不出」处理)。 */
|
|
52
64
|
taskId: string;
|
|
53
65
|
/** 会话 id(合成形要写进结果;引擎给了结果时不用)。 */
|
|
@@ -56,7 +68,47 @@ export declare function mintCancelledResult(args: {
|
|
|
56
68
|
startedAtMs: number;
|
|
57
69
|
/** 合成形的失败文案(引擎给了终局时不用)。 */
|
|
58
70
|
message: string;
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
}
|
|
71
|
+
/** S-947:这次取消的发起者(本腿 {@link RunAbort} 句柄上登记的那一个;park 中直接终局化的取消动词给 `"cancel_verb"`)。 */
|
|
72
|
+
cause: AbortCause;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* S-947 —— 没有 `done` 可落的取消形在账本上的那一行(`failed`):**从铸好的取消终局投影**,不手抄。
|
|
76
|
+
*
|
|
77
|
+
* 账本 `failed` 行是平面形(`errorMessage` / `errorCode` 顶层),所以成因也在顶层、与 `errorCode` 并列;有 `done` 可落的
|
|
78
|
+
* 那几形,成因随 `done.result` 走(结果本身带它)。两形同一个来源 = 同一只取消终局,不是两处各写一份。
|
|
79
|
+
* 返回纯数据 ⇒ `build*`。
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildCancelledLedgerRow(c: PersistedTaskResult): {
|
|
82
|
+
errorMessage?: string;
|
|
83
|
+
errorCode?: string;
|
|
84
|
+
abortCause?: AbortCause;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* S-947 —— 一条 run 腿的**中止句柄**:server 中止这条腿的唯一入口,也是「为什么中止」的唯一登记处。
|
|
88
|
+
*
|
|
89
|
+
* ## 为什么是句柄、不是一张按 taskId 键控的表
|
|
90
|
+
* 修前这件事由 `cancelledViaVerb: Set<taskId>`(「这次是取消动词」一个词)+ 各腿手写的生命周期(登记 / finally 里按身份守卫删 /
|
|
91
|
+
* 同步腿「删之前先取」)承担:一个键控在 **taskId** 上的旁表,要防的病都来自键与腿不同寿 —— 快速续跑在旧腿 finally 之前
|
|
92
|
+
* 用同一 taskId 登记新腿、旧腿收尾删掉新腿的条目;同步腿必须在删之前取值。成因挂在**腿自己的中止控制器**上之后,
|
|
93
|
+
* 这些规则一条都不需要:句柄与腿同寿,换腿就是换句柄,没有可删、可串、可漏的旁表。
|
|
94
|
+
*
|
|
95
|
+
* ## 先到先得(first-writer-wins)
|
|
96
|
+
* 第一次 `abort(cause)` 生效并登记成因;之后的调用全是 no-op(信号早已中止,成因不改写)—— 断连之后又到了取消动词、
|
|
97
|
+
* 取消之后又断连,终局说的都是**真正让这条腿停下来**的那一个。`AbortController.abort()` 自身就是幂等的,本句柄只是在
|
|
98
|
+
* 「第一次」那一刻把词记下。
|
|
99
|
+
*
|
|
100
|
+
* ## 引擎看到的东西不变
|
|
101
|
+
* 中止走 `AbortController.abort()` 的默认原因(引擎、provider 的 fetch、子进程看到的仍是标准 `AbortError`)—— 成因**不**塞进
|
|
102
|
+
* `signal.reason`:那会改掉引擎与 provider 层收到的拒绝值,是在下游改上游接缝的输入(立宪禁区)。
|
|
103
|
+
*/
|
|
104
|
+
export interface RunAbort {
|
|
105
|
+
/** 交给引擎的那只信号(`spec.signal` / 审批 / 提问 ALS 的 `abortSignal` / 子代 stoppedBy 归因都用它)。 */
|
|
106
|
+
readonly signal: AbortSignal;
|
|
107
|
+
/** 以 `cause` 中止本腿。已中止 ⇒ no-op(成因不改写)。 */
|
|
108
|
+
abort(cause: AbortCause): void;
|
|
109
|
+
/** 让本腿停下来的那次中止的成因;未中止 ⇒ `undefined`。 */
|
|
110
|
+
cause(): AbortCause | undefined;
|
|
111
|
+
}
|
|
112
|
+
export declare function createRunAbort(): RunAbort;
|
|
113
|
+
export {};
|
|
62
114
|
//# sourceMappingURL=run-cancel-context.d.ts.map
|
|
@@ -23,6 +23,7 @@ export function mintCancelledResult(args) {
|
|
|
23
23
|
...engine,
|
|
24
24
|
terminal: { ...engine.terminal, code: CANCELLED_CODE },
|
|
25
25
|
...(engine.cancelContext === undefined && ctx !== undefined ? { cancelContext: ctx } : {}),
|
|
26
|
+
abortCause: args.cause,
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
return {
|
|
@@ -32,6 +33,31 @@ export function mintCancelledResult(args) {
|
|
|
32
33
|
result: "",
|
|
33
34
|
stats: { turns: 0, tokens: 0, usageMissing: true },
|
|
34
35
|
...(ctx ? { cancelContext: ctx } : {}),
|
|
36
|
+
abortCause: args.cause,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function buildCancelledLedgerRow(c) {
|
|
40
|
+
const t = c.terminal;
|
|
41
|
+
return {
|
|
42
|
+
...(t.kind === "failed" && t.message !== undefined ? { errorMessage: t.message } : {}),
|
|
43
|
+
...(t.kind === "failed" && t.code !== undefined ? { errorCode: t.code } : {}),
|
|
44
|
+
...(c.abortCause !== undefined ? { abortCause: c.abortCause } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function createRunAbort() {
|
|
48
|
+
const ctrl = new AbortController();
|
|
49
|
+
let landed;
|
|
50
|
+
return {
|
|
51
|
+
signal: ctrl.signal,
|
|
52
|
+
abort(cause) {
|
|
53
|
+
if (ctrl.signal.aborted)
|
|
54
|
+
return;
|
|
55
|
+
landed = cause;
|
|
56
|
+
ctrl.abort();
|
|
57
|
+
},
|
|
58
|
+
cause() {
|
|
59
|
+
return landed;
|
|
60
|
+
},
|
|
35
61
|
};
|
|
36
62
|
}
|
|
37
63
|
//# sourceMappingURL=run-cancel-context.js.map
|
package/dist/run-local.d.ts
CHANGED
|
@@ -142,7 +142,7 @@ export interface LocalApproverIo {
|
|
|
142
142
|
* · **人答 N**(TTY 臂)⇒ 裸 `false` —— core 契约里这就是「一个人拒了」(`human_refused` / `who.party:"person"`,模型读「用户不想继续,
|
|
143
143
|
* 停下来等他」,run 以 `haltedOnUserRejection` 停批);
|
|
144
144
|
* · **没有人被问**(无 TTY 臂 / 参数展示不出臂)⇒ 部署政策拒对象 {@link UnattendedPolicyRefusal}(经与协调器 deny 政策臂**同一只**构造
|
|
145
|
-
* `buildUnattendedPolicyRefusal`;core
|
|
145
|
+
* `buildUnattendedPolicyRefusal`;core `readApproverOutcomeObject` ⇒ `policy_refused` / `who.party:"policy"`(core 7.39.0 #1265 起;此前 `"none"`),模型读 core 的
|
|
146
146
|
* 策略句 + 本席的拒因,不停批)。修前两臂交裸 `false` ⇒ 机器的拒被记成人拒、模型被告知去等一个不存在的人。
|
|
147
147
|
* 两臂的拒都经 `io.noteRefusedWithoutPerson` 报一次 —— 「活没干完」(退 1)此前搭的是 core 人拒标记的便车,S-956 之后由本席的事实给。
|
|
148
148
|
*/
|
package/dist/run-local.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { DEFAULT_MODEL_REF, resolveTaskModel } from "./model-select.js";
|
|
3
3
|
import { defaultModelRefusal, modelUnavailableMessage } from "./model-entry-refusal.js";
|
|
4
|
-
import {
|
|
4
|
+
import { createDurableAbsenceSeat } from "./observability/corrupt-read-seat.js";
|
|
5
5
|
import { outwardPlane } from "./terminal.js";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { realpathSync } from "node:fs";
|
|
@@ -351,7 +351,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
351
351
|
fileBackend = acquireLocalBackendWithWait(() => new FileStorageBackend({
|
|
352
352
|
root,
|
|
353
353
|
...(config.fileHistory !== undefined ? { fileHistory: config.fileHistory } : {}),
|
|
354
|
-
onCorruptRead:
|
|
354
|
+
onCorruptRead: createDurableAbsenceSeat(logger.warn.bind(logger), root, "file-snapshot = a snapshot or scope read as missing"),
|
|
355
355
|
}), config.storeLockWaitMs, (fields) => logger.warn("store_dir_lock_wait", { ...fields }));
|
|
356
356
|
}
|
|
357
357
|
catch (e) {
|
package/dist/runs.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defaultTaskRegistry, type CascadeConfig, type CheckpointToken, type Run
|
|
|
2
2
|
import type { SendUserFileEmitter } from "./capabilities/send-user-file-tool.js";
|
|
3
3
|
import type { RunStore } from "./plugins/store-backend.js";
|
|
4
4
|
import type { Metrics } from "./observability/metrics.js";
|
|
5
|
+
import { type RunAbort } from "./run-cancel-context.js";
|
|
5
6
|
import type { ModelUsageTracker, PromptManifestTracker } from "./budget.js";
|
|
6
7
|
import type { ElicitationCoordinator } from "./elicitation.js";
|
|
7
8
|
import type { QuestionCoordinator } from "./question.js";
|
|
@@ -259,10 +260,11 @@ export declare function evictIfConflict(runner: Runner, sessionId: string | unde
|
|
|
259
260
|
* text); tool/lifecycle events are recorded individually. Per-token live streaming stays on the
|
|
260
261
|
* synchronous `/v1/tasks/stream` endpoint. Never throws — failures are recorded on the run.
|
|
261
262
|
*
|
|
262
|
-
* Cancellation (POST /v1/runs/:id/cancel): the run registers
|
|
263
|
-
* fast path — the cancel handler aborts it directly) and its heartbeat tick polls the durable
|
|
264
|
-
* `cancel_requested` flag (cross-replica, ≤HEARTBEAT_MS lag)
|
|
265
|
-
* status "failed" + errorCode "cancelled" (metric label "cancelled"); the entry is removed in
|
|
263
|
+
* Cancellation (POST /v1/runs/:id/cancel): the run registers its abort handle (S-947 `RunAbort`) in `inflight`
|
|
264
|
+
* (same-replica fast path — the cancel handler aborts it directly) and its heartbeat tick polls the durable
|
|
265
|
+
* `cancel_requested` flag (cross-replica, ≤HEARTBEAT_MS lag); both abort with `cancel_verb`. Either way the run settles
|
|
266
|
+
* to status "failed" + errorCode "cancelled" + `result.abortCause` (metric label "cancelled"); the entry is removed in
|
|
267
|
+
* `finally`.
|
|
266
268
|
*
|
|
267
269
|
* Preemption (POST /v1/assistant/tasks/:id/preempt, [ref] seam #2): a SECOND, INDEPENDENT controller
|
|
268
270
|
* (`preemptCtrl`, registered in `preemptable`) carries the scheduler's graceful "yield this task" — when raised,
|
|
@@ -281,7 +283,7 @@ export declare function evictIfConflict(runner: Runner, sessionId: string | unde
|
|
|
281
283
|
export declare function runInBackground(runner: Runner, spec: TaskSpec, runStore: RunStore, taskId: string, metrics?: Metrics, principal?: string, verify?: VerifyRoundsSpec, // 快审 F1:number 参升整对象——cost 顶随传
|
|
282
284
|
cascade?: CascadeConfig, instrumentDegenerate?: (result: TaskResult) => void, planCacheProbe?: {
|
|
283
285
|
record: (scope: string | undefined, objective: string, completed: boolean) => void;
|
|
284
|
-
}, persistThinking?: boolean, inflight?: Map<string,
|
|
286
|
+
}, persistThinking?: boolean, inflight?: Map<string, RunAbort>, preemptable?: Map<string, AbortController>, steerable?: Map<string, TaskStream>, modelUsage?: ModelUsageTracker, elicitation?: ElicitationCoordinator,
|
|
285
287
|
/** E23: the VERIFIED principal (gatedPrincipal) that may answer this run's elicitations — matches the respond
|
|
286
288
|
* owner-gate's identity source (NOT the cost-attribution `principal`, which on a direct door is the self-asserted
|
|
287
289
|
* header → can be null while a JWT principal exists). Falls back to the run owner when not supplied. */
|
package/dist/runs.js
CHANGED
|
@@ -12,7 +12,7 @@ import { taskNotificationEventData, appendModelUsageDelta, appendPromptManifest,
|
|
|
12
12
|
import { createLedgerSink } from "./trace/ledger-sink.js";
|
|
13
13
|
import { registerEngineNoticeLeg } from "./trace/engine-notice-wire.js";
|
|
14
14
|
import { recordTurnActivity } from "./turn-activity.js";
|
|
15
|
-
import { CANCELLED_CODE, mintCancelledResult } from "./run-cancel-context.js";
|
|
15
|
+
import { buildCancelledLedgerRow, CANCELLED_CODE, createRunAbort, mintCancelledResult } from "./run-cancel-context.js";
|
|
16
16
|
import { createApprovalCardEmitter, resolveApprovalLeg } from "./tool-approval.js";
|
|
17
17
|
import { fleetRunResiduals, isFleetAgentTerminalNotification } from "./fleet/fleet-bus.js";
|
|
18
18
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
@@ -178,14 +178,16 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
178
178
|
const startedAt = Date.now();
|
|
179
179
|
metrics?.addGauge("runs_active", 1);
|
|
180
180
|
fleetPublisher?.onStart();
|
|
181
|
-
const cancelCtrl =
|
|
181
|
+
const cancelCtrl = createRunAbort();
|
|
182
182
|
inflight?.set(taskId, cancelCtrl);
|
|
183
183
|
markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, principal);
|
|
184
184
|
const preemptEligible = verify === undefined && cascade === undefined;
|
|
185
185
|
const preemptCtrl = new AbortController();
|
|
186
186
|
if (preemptEligible)
|
|
187
187
|
preemptable?.set(taskId, preemptCtrl);
|
|
188
|
-
const cancelledResult = (engineResult) =>
|
|
188
|
+
const cancelledResult = (cause, engineResult) => engineResult !== undefined
|
|
189
|
+
? mintCancelledResult({ taskId, sessionId: spec.sessionId ?? "", startedAtMs: startedAt, message: "cancelled by user", cause, engineResult })
|
|
190
|
+
: mintCancelledResult({ taskId, sessionId: spec.sessionId ?? "", startedAtMs: startedAt, message: "cancelled by user", cause });
|
|
189
191
|
const recordDone = (result, status) => {
|
|
190
192
|
metrics?.addGauge("runs_active", -1);
|
|
191
193
|
metrics?.inc("tasks_total", { status });
|
|
@@ -222,7 +224,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
222
224
|
void runStore.heartbeat(taskId, owner).catch(() => { metrics?.inc("run_signal_poll_errors_total", { kind: "heartbeat" }); });
|
|
223
225
|
if (!cancelCtrl.signal.aborted) {
|
|
224
226
|
void runStore.isCancelRequested(taskId, owner).then((c) => { if (c)
|
|
225
|
-
cancelCtrl.abort(); }).catch(() => { metrics?.inc("run_signal_poll_errors_total", { kind: "cancel" }); });
|
|
227
|
+
cancelCtrl.abort("cancel_verb"); }).catch(() => { metrics?.inc("run_signal_poll_errors_total", { kind: "cancel" }); });
|
|
226
228
|
}
|
|
227
229
|
if (preemptEligible && !preemptCtrl.signal.aborted) {
|
|
228
230
|
void runStore.isPreemptRequested(taskId, owner).then((p) => { if (p)
|
|
@@ -233,7 +235,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
233
235
|
const anchor = new TurnAnchorCapture(captureTurnAnchor, () => metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
|
|
234
236
|
let reached;
|
|
235
237
|
let observedDone;
|
|
236
|
-
let
|
|
238
|
+
let causeAtDone;
|
|
237
239
|
let settledAsCancel = false;
|
|
238
240
|
let steerableStreamRef;
|
|
239
241
|
let legLive = true;
|
|
@@ -364,9 +366,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
364
366
|
for await (const ev of stream) {
|
|
365
367
|
if (ev.type === "done") {
|
|
366
368
|
observedDone = ev.result;
|
|
367
|
-
|
|
369
|
+
causeAtDone = cancelCtrl.cause();
|
|
368
370
|
}
|
|
369
371
|
fleetPublisher?.onEvent(ev);
|
|
372
|
+
await userInputLedger?.observeLegEvent(stream, ev);
|
|
370
373
|
await flushPromptManifest();
|
|
371
374
|
switch (ev.type) {
|
|
372
375
|
case "text_delta":
|
|
@@ -402,7 +405,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
402
405
|
}
|
|
403
406
|
evictIfConflict(runner, spec.sessionId, ev.result);
|
|
404
407
|
{
|
|
405
|
-
const settled =
|
|
408
|
+
const settled = causeAtDone !== undefined ? cancelledResult(causeAtDone, ev.result) : ev.result;
|
|
406
409
|
settledAsCancel = failureCodeOf(settled.terminal) === CANCELLED_CODE;
|
|
407
410
|
const safe = await withModelUsage(sealResultForExit(settled));
|
|
408
411
|
await sink.appendDone(safe);
|
|
@@ -448,17 +451,18 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
448
451
|
? sendUserFile.runWithContext({ taskId, emit: (frame) => { const { type, ...rest } = frame; return append(type, rest); } }, withApproval)
|
|
449
452
|
: withApproval();
|
|
450
453
|
})));
|
|
451
|
-
|
|
454
|
+
const cleanCancelCause = cancelCtrl.cause();
|
|
455
|
+
if (!reached && cleanCancelCause !== undefined) {
|
|
452
456
|
try {
|
|
453
457
|
await flush();
|
|
454
458
|
}
|
|
455
459
|
catch (e) {
|
|
456
460
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=flush-clean-cancel ${errText(e)}`);
|
|
457
461
|
}
|
|
458
|
-
const c = cancelledResult();
|
|
462
|
+
const c = cancelledResult(cleanCancelCause);
|
|
459
463
|
settledAsCancel = failureCodeOf(c.terminal) === CANCELLED_CODE;
|
|
460
464
|
try {
|
|
461
|
-
await append("failed",
|
|
465
|
+
await append("failed", buildCancelledLedgerRow(c));
|
|
462
466
|
}
|
|
463
467
|
catch (e) {
|
|
464
468
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-clean-cancel ${errText(e)}`);
|
|
@@ -469,6 +473,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
469
473
|
}
|
|
470
474
|
}
|
|
471
475
|
catch (err) {
|
|
476
|
+
const thrownCancelCause = cancelCtrl.cause();
|
|
472
477
|
const message = errText(err);
|
|
473
478
|
try {
|
|
474
479
|
try {
|
|
@@ -488,11 +493,11 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
488
493
|
else if (reached?.kind === "needs_review") {
|
|
489
494
|
await runStore.setNeedsReview(taskId, reached.result);
|
|
490
495
|
}
|
|
491
|
-
else if (
|
|
492
|
-
const c = cancelledResult();
|
|
496
|
+
else if (thrownCancelCause !== undefined && observedDone === undefined) {
|
|
497
|
+
const c = cancelledResult(thrownCancelCause);
|
|
493
498
|
settledAsCancel = failureCodeOf(c.terminal) === CANCELLED_CODE;
|
|
494
499
|
try {
|
|
495
|
-
await append("failed",
|
|
500
|
+
await append("failed", buildCancelledLedgerRow(c));
|
|
496
501
|
}
|
|
497
502
|
catch (e) {
|
|
498
503
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-thrown-cancel ${errText(e)}`);
|
package/dist/spec-fields.js
CHANGED
|
@@ -37,7 +37,6 @@ export function normalizeAttachments(v) {
|
|
|
37
37
|
const out = {
|
|
38
38
|
...(o.todoReminder === true ? { todoReminder: true } : {}),
|
|
39
39
|
...(changedFiles !== undefined ? { changedFiles } : {}),
|
|
40
|
-
...(o.planModeReminder === true ? { planModeReminder: true } : {}),
|
|
41
40
|
...(typeof o.backgroundTasks === "boolean" ? { backgroundTasks: o.backgroundTasks } : {}),
|
|
42
41
|
...(o.toolsDelta === true ? { toolsDelta: true } : {}),
|
|
43
42
|
...(o.todoReminderMode === "baseline" || o.todoReminderMode === "off" ? { todoReminderMode: o.todoReminderMode } : {}),
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -821,7 +821,7 @@ export type RespondSettledBy = Extract<HostSettledBy, HostDecidedBy>;
|
|
|
821
821
|
export type UnattendedApprovalPolicy = "park" | "deny";
|
|
822
822
|
/**
|
|
823
823
|
* S-689 —— `deny` 政策下「无人可答」交给 core 的形:core `AskOutcome` 对象臂的**部署政策拒**
|
|
824
|
-
* (`settledBy:"policy"` ⇒ core 结算 `policy_refused`、`who.party:"none"
|
|
824
|
+
* (`settledBy:"policy"` ⇒ core 结算 `policy_refused`、`who.party:"policy"`(core 7.39.0 #1265 起;此前 `"none"`),模型读的是策略句)。
|
|
825
825
|
*
|
|
826
826
|
* 修前这里交**裸 `false`** —— 而在 core 的 onAsk 契约里裸 `false` 就是「一个人拒了」:结算记
|
|
827
827
|
* `human_refused` / `who.party:"person"`,模型被告知「用户不想继续,停下来等他」,run 以
|
|
@@ -146,7 +146,7 @@ type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueue
|
|
|
146
146
|
type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
|
|
147
147
|
type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
|
|
148
148
|
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" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory";
|
|
149
|
-
type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence";
|
|
149
|
+
type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence" | "outputStyle";
|
|
150
150
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
151
151
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
152
152
|
type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles" | "responseModel" | "compaction";
|