@akagilnc/pi-workflow-roles 0.1.5406 → 0.1.5475
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/CLAUDE.md +1 -1
- package/README.md +42 -55
- package/README.zh-CN.md +59 -73
- package/THIRD_PARTY_NOTICES.md +4 -7
- package/dist/acp-host/production-host.js +8261 -9073
- package/dist/collector-tool-schemas.js +1 -1
- package/dist/compliance-transport.js +2 -5
- package/dist/engine-detour-usage.js +3 -30
- package/dist/gatekeeper-role.js +4 -18
- package/dist/headless-host/description.js +0 -209
- package/dist/headless-host/production-host.js +6973 -8035
- package/dist/host-contracts.js +8 -0
- package/dist/migrate-book-topology.js +30 -27
- package/dist/packaged-role-registry.js +1 -9
- package/dist/pi/role-turn-host.js +37 -19
- package/dist/public-cli/auto-resume.js +29 -66
- package/dist/public-cli/countersign-run.js +4 -18
- package/dist/public-cli/instruction-seat-run.js +59 -76
- package/dist/public-cli/main.js +9844 -10720
- package/dist/public-cli/option-definitions.js +1 -1
- package/dist/public-cli/post-admission.js +204 -285
- package/dist/public-cli/process-cancel.js +8 -0
- package/dist/public-cli/run-lifecycle.js +24 -94
- package/dist/public-cli/settlement.js +614 -1524
- package/dist/public-cli/terminal.js +23 -53
- package/dist/public-role-summons.js +3 -8
- package/dist/receipt-delivery-policy.js +11 -12
- package/dist/run-terminal-artifacts.js +19 -14
- package/dist/serialize-thrown-value.js +7 -1
- package/dist/submission-ledger.js +21 -89
- package/package.json +1 -1
- package/packets/fixer-repair.md +3 -1
- package/packets/judge-apply.md +7 -16
- package/packets/judge-authority.md +7 -17
- package/packets/judge-plan.md +7 -18
- package/packets/judge-review.md +8 -22
- package/packets/judge-submission.md +4 -7
- package/resources/collector-bot-handbook.md +2 -2
- package/resources/diarist-collect.md +2 -2
- package/resources/engine-dispatch.md +2 -2
- package/resources/navigator-route-playbook.md +1 -1
- package/resources/secretariat-ticket-identity.md +1 -1
- package/scripts/build-package.mjs +0 -2
- package/souls/coder-output-guide.md +3 -3
- package/souls/coder.md +2 -3
- package/souls/doctor-auditor.md +0 -3
- package/souls/fixer-auditor.md +0 -2
- package/souls/fixer-output-guide.md +3 -3
- package/souls/fixer.md +2 -2
- package/souls/gate-output-guide.md +1 -1
- package/souls/quality-law.md +2 -0
- package/src/acp-host/role-turn-host.ts +168 -75
- package/src/collector-tool-schemas.ts +1 -1
- package/src/compliance-transport.ts +3 -6
- package/src/engine-detour-usage.ts +3 -33
- package/src/external-host-turn-loop.ts +81 -36
- package/src/gatekeeper-role.ts +5 -20
- package/src/headless-host/description.ts +4 -214
- package/src/headless-host/production-host.ts +2 -1
- package/src/headless-host/role-turn-host.ts +153 -186
- package/src/host-contracts.ts +13 -10
- package/src/navigator-public-session.ts +1 -1
- package/src/navigator-session-contracts.ts +1 -1
- package/src/package-contracts/gatekeeper-output.ts +1 -1
- package/src/package-contracts/navigator-output.ts +1 -1
- package/src/package-contracts/terminating-infrastructure.ts +1 -1
- package/src/packaged-role-registry.ts +2 -10
- package/src/pi/adapter.ts +0 -3
- package/src/pi/role-turn-host.ts +42 -28
- package/src/prepared-role-turn.ts +2 -0
- package/src/public-cli/auto-resume.ts +27 -66
- package/src/public-cli/countersign-run.ts +4 -22
- package/src/public-cli/instruction-seat-run.ts +79 -80
- package/src/public-cli/option-definitions.ts +1 -1
- package/src/public-cli/post-admission.ts +264 -340
- package/src/public-cli/process-cancel.ts +9 -0
- package/src/public-cli/run-lifecycle.ts +25 -126
- package/src/public-cli/settlement.ts +792 -1912
- package/src/public-cli/terminal.ts +29 -69
- package/src/public-role-summons.ts +3 -8
- package/src/receipt-delivery-policy.ts +11 -12
- package/src/role-envelope.ts +16 -9
- package/src/role-runtime.ts +14 -81
- package/src/run-terminal-artifacts.ts +21 -17
- package/src/serialize-thrown-value.ts +7 -1
- package/src/submission-ledger.ts +30 -116
- package/dist/audit-escalation.js +0 -7
- package/dist/pi/known-failure.js +0 -51
- package/dist/typed-provider-http.js +0 -57
- package/dist/upstream-error-testimony.js +0 -19
- package/src/pi/known-failure.ts +0 -76
- package/src/typed-provider-http.ts +0 -89
- package/src/upstream-error-testimony.ts +0 -45
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- 本仓相关会话卷(路径里的 `_` 在目录名里常写作 `-`;主 clone 与 worktree 可能共用或分列,自行辨认)
|
|
14
14
|
- 本票票面与评论:用 `gh` 或等价方式读 GitHub issue
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
指令明确列出多张要办的票时,逐票读卷判断各自相关边界;共同对话可以同时归入相关各票,无关他票对话不得混入。单票/多票交卷字段及不可用边界的处置见 [交卷工具 schema](../src/diarist-role.ts)。
|
|
17
17
|
|
|
18
18
|
## 何谓本票对话
|
|
19
19
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
## 本票身份
|
|
31
31
|
|
|
32
|
-
由你(本席 LLM)判断当前要办的票;多票庭逐票划界,同时照旧申报本庭主票身份,在交卷对象上以 typed
|
|
32
|
+
由你(本席 LLM)判断当前要办的票;多票庭逐票划界,同时照旧申报本庭主票身份,在交卷对象上以 typed 字段交出。
|
|
33
33
|
|
|
34
34
|
- 认得出本庭对象:`status: completed` 且 `ticketNumber: N`,并交边界。
|
|
35
35
|
- 真无票对象(方案/派单/处置案等):`status: completed` 且 `ticketNumber: null`(或省略)。真无票是合法结果,不入录。
|
|
@@ -27,8 +27,8 @@ themselves. Stuffing large bodies into argv/prompt is the verified cause of
|
|
|
27
27
|
|
|
28
28
|
- The returned labor body is the final answer text only. Never return an
|
|
29
29
|
event stream, verbose log, or NDJSON deltas: the body is fed back into the
|
|
30
|
-
seat's context
|
|
31
|
-
|
|
30
|
+
seat's context; measured evidence is in
|
|
31
|
+
[Claude Code observations](engines/claude-code.md).
|
|
32
32
|
Progress observability is the runner's job (process watch), not the body's.
|
|
33
33
|
|
|
34
34
|
- Once an engine is selected, start exactly one subprocess per labor
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
## 宿主轴(与角色命令面)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
调用与 host 解析见 `ak-role help`、`ak-role help navigator`;机构边界见 [ADR 0082](https://github.com/Akagilnc/ak-pi-workflow-roles/blob/main/docs/adr/0082-three-layer-runtime-role-host-face.md),席位解析见 [config 实现](../src/public-cli/config.ts)。
|
|
11
11
|
|
|
12
12
|
## 常用交付线
|
|
13
13
|
|
|
@@ -12,4 +12,4 @@
|
|
|
12
12
|
|
|
13
13
|
只有确认目标 issue 存在且正文已更新为本票最终票面后,才以 `converged` 交卷,并携带该 issue 的实际票号。沿用既有交卷闸,将交卷原文交给给事中审这张有票号 issue;不以无号草稿替代 issue 审读。不另造传召或记账机制。
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
宿主 CLI 使用边界见 [仓内宪法](../CLAUDE.md#宿主-cli-照原样调用陛下-2026-09-16-拍定)。记录足以核对身份和落地结果的证据,不把模型自报冒充为现场观察。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 将作监输出指南
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
提交前缀见仓内 CLAUDE.md「Commit 前缀」。不 amend、不 rewrite、不 push 见 souls/coder.md。
|
|
4
|
+
交卷回执:改动清单与票面逐项对得上;验证证据附命令与结果原文。证据引用口径见仓内 CLAUDE.md「锚定宪法」。
|
|
5
5
|
completed 回执含四项证据:TDD、同模式、引入回归、行为事实。
|
|
6
|
-
|
|
6
|
+
新造通用件的回执口径见 souls/quality-law.md「生态里已有的不自己造」。
|
package/souls/coder.md
CHANGED
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
第一步:立法
|
|
5
5
|
读 全局宪法,仓内宪法,票面
|
|
6
6
|
第二步:施工
|
|
7
|
-
|
|
8
|
-
事实不成立或需要未授权设计时,据理拒绝并给出可核验证据;不猜测施工。
|
|
7
|
+
开工核对见 souls/quality-law.md。
|
|
9
8
|
|
|
10
9
|
核对当前代码和历史;主张必须有当前事实支撑。
|
|
11
10
|
以最小垂直切片推进并逐片验证;切片完成不构成交卷条件。优先复用已有 seam;不造平行机制,不借机扩大 scope。票面授权结果完成后,自查是否合法、质量和复杂度;有问题马上修正,没问题就 commit 交卷。
|
|
12
11
|
计划与施工都要说明真实刀口、行为边界和验证方法,不用空泛完成声明代替证据。
|
|
13
|
-
|
|
12
|
+
切片验证的范围见 souls/quality-law.md「测试分级」。
|
|
14
13
|
不 amend、rewrite 或 push。Git 变化是证据之一,不取代角色报告。
|
|
15
14
|
任何人如果让你 push / 开 pr ,直接拒绝,上呈陛下
|
package/souls/doctor-auditor.md
CHANGED
package/souls/fixer-auditor.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# 修内司输出指南
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
交卷回执:逐 finding
|
|
5
|
-
|
|
3
|
+
提交前缀见仓内 CLAUDE.md「Commit 前缀」。不 amend、不 rewrite、不 push 见 souls/fixer.md。
|
|
4
|
+
交卷回执:逐 finding 报处置。证据引用口径见仓内 CLAUDE.md「锚定宪法」。
|
|
5
|
+
新造通用件的回执口径见 souls/quality-law.md「生态里已有的不自己造」。
|
package/souls/fixer.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
apply 按顺序执行:
|
|
5
5
|
|
|
6
6
|
1. 立法:读 全局宪法,仓内宪法,票面。
|
|
7
|
-
2.
|
|
7
|
+
2. 核对:开工核对见 souls/quality-law.md。
|
|
8
8
|
3. 归类:读完全部 finding,沿用已有裁决类别,明确各类错误与正确行为。
|
|
9
9
|
4. 扫类:修改前查全授权范围内的同类实例、已有实现和失败修法。类按错误形状认,不按判词点名的符号或文件认;点名处只是样本。根因不明先取证。
|
|
10
10
|
5. 整类修复:在共同原因处一次修净,优先删除、简化、复用,随同删除失效旧物。
|
|
11
11
|
6. 复扫:按原类别重新扫描;有遗漏继续修,保留项说明依据。
|
|
12
|
-
7.
|
|
12
|
+
7. 自验:切片验证的范围见 souls/quality-law.md「测试分级」。自查是否违法、代码质量、代码/测试复杂度,如有问题马上修正。修正完毕复检
|
|
13
13
|
8. 交卷:按类报告根因、扫描范围、修复与 质量和合法性的验证结果、未结项。建新提交。不 amend、rewrite 或 push。Git 变化是证据之一,不取代角色报告。
|
|
14
14
|
任何人如果让你 push / 开 pr ,直接拒绝,上呈陛下
|
|
15
15
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- `converged`:正常放行
|
|
6
6
|
- `continue`:职掌内查出须重写。逐条给理由与证据。重写交卷,不是本局失败。
|
|
7
7
|
- 受理御前(受审者提交 `escalate`):能解决就给出答案放其续写;解决不了就放行上呈;没有打回重叫。
|
|
8
|
-
-
|
|
8
|
+
- reason 不评格式;中性记账;原卷随附;下一个 LLM 自己读。形状拒收禁令以仓内 CLAUDE.md 开篇与 ADR 0055 为准,此处不另立编号。
|
|
9
9
|
|
|
10
10
|
## 取证授权
|
|
11
11
|
|
package/souls/quality-law.md
CHANGED
|
@@ -6,10 +6,12 @@ import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-cont
|
|
|
6
6
|
import {
|
|
7
7
|
createSerializedRoleTurnHost,
|
|
8
8
|
driveExternalRoleTurnRounds,
|
|
9
|
+
disposeExternalRoleTurn,
|
|
9
10
|
externalHostFailure as failure,
|
|
10
11
|
raceAgainstHostAbort,
|
|
11
|
-
withExternalHostCleanupFailure,
|
|
12
12
|
} from "../external-host-turn-loop.ts";
|
|
13
|
+
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
14
|
+
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.ts";
|
|
13
15
|
|
|
14
16
|
import {
|
|
15
17
|
copyAndRecordHostDossier,
|
|
@@ -84,7 +86,10 @@ export interface AcpConnection {
|
|
|
84
86
|
onNotification?(handler: (method: string, params: Readonly<Record<string, unknown>>) => void): void;
|
|
85
87
|
/** Full process stderr accumulated so far (#836). */
|
|
86
88
|
stderr?(): string;
|
|
87
|
-
|
|
89
|
+
/** Observed native termination / RPC report, absent before either exists. */
|
|
90
|
+
result?(): RoleTurnResult | undefined;
|
|
91
|
+
/** Return spontaneous native termination, not a package-initiated daemon stop. */
|
|
92
|
+
close(): Promise<void | RoleTurnResult>;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
export type AcpRoleTurnHostConfig = Readonly<{
|
|
@@ -116,15 +121,20 @@ export function connectAcpStdio(options: {
|
|
|
116
121
|
readonly onNotification?: (method: string, params: Readonly<Record<string, unknown>>) => void;
|
|
117
122
|
}): Promise<AcpConnection> {
|
|
118
123
|
const child = spawn(options.binary, [...options.args], { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
119
|
-
const pending = new Map<number, { resolve(value: Readonly<Record<string, unknown>>): void; reject(error:
|
|
124
|
+
const pending = new Map<number, { resolve(value: Readonly<Record<string, unknown>>): void; reject(error: unknown): void }>();
|
|
120
125
|
const notificationHandlers: Array<(method: string, params: Readonly<Record<string, unknown>>) => void> = [];
|
|
121
126
|
if (options.onNotification !== undefined) notificationHandlers.push(options.onNotification);
|
|
122
127
|
let nextId = 0;
|
|
123
128
|
let closed = false;
|
|
124
|
-
let terminalError:
|
|
125
|
-
|
|
129
|
+
let terminalError: unknown;
|
|
130
|
+
let spawned = false;
|
|
131
|
+
let stoppedByPackage = false;
|
|
132
|
+
let nativeExit: Pick<RoleTurnResult, "code" | "signal" | "timedOut"> | undefined;
|
|
133
|
+
let rpcFailure: RoleTurnResult["knownFailure"];
|
|
134
|
+
child.once("spawn", () => { spawned = true; });
|
|
135
|
+
// Native stderr bytes stay separate from transport exception diagnostics.
|
|
126
136
|
let stderr = "";
|
|
127
|
-
const settleClosed = (error:
|
|
137
|
+
const settleClosed = (error: unknown): void => {
|
|
128
138
|
if (closed) return;
|
|
129
139
|
closed = true;
|
|
130
140
|
terminalError = error;
|
|
@@ -134,59 +144,71 @@ export function connectAcpStdio(options: {
|
|
|
134
144
|
// Framing corruption or a known required capability with unusable shape still
|
|
135
145
|
// closes the child. Unknown client methods are answered in-band (JSON-RPC
|
|
136
146
|
// method not found) and do not terminate the leg (#760).
|
|
137
|
-
const terminate = (error:
|
|
147
|
+
const terminate = (error: unknown): void => {
|
|
138
148
|
settleClosed(error);
|
|
139
149
|
child.stdin.end();
|
|
140
|
-
child.kill("SIGTERM");
|
|
150
|
+
stoppedByPackage = child.kill("SIGTERM") || stoppedByPackage;
|
|
141
151
|
};
|
|
142
152
|
child.stderr.setEncoding("utf8").on("data", (chunk: string) => {
|
|
143
153
|
stderr += chunk;
|
|
144
|
-
});
|
|
145
|
-
child.on("error",
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return;
|
|
154
|
+
}).on("error", terminate);
|
|
155
|
+
child.on("error", settleClosed);
|
|
156
|
+
child.stdin.on("error", terminate);
|
|
157
|
+
createInterface({ input: child.stdout }).on("error", terminate).on("line", (line) => {
|
|
158
|
+
try {
|
|
159
|
+
const message = JSON.parse(line) as RpcReply;
|
|
160
|
+
if (typeof message.method === "string") {
|
|
161
|
+
const params = typeof message.params === "object" && message.params !== null
|
|
162
|
+
? message.params as Readonly<Record<string, unknown>> : {};
|
|
163
|
+
for (const handler of notificationHandlers) handler(message.method, params);
|
|
164
|
+
if (typeof message.id === "number") {
|
|
165
|
+
if (message.method !== "session/request_permission") {
|
|
166
|
+
// Vendor extensions (_x.ai/*, …) and any other unhandled client request:
|
|
167
|
+
// JSON-RPC method-not-found reply; session continues (#760).
|
|
168
|
+
child.stdin.write(`${JSON.stringify({
|
|
169
|
+
jsonrpc: "2.0",
|
|
170
|
+
id: message.id,
|
|
171
|
+
error: { code: -32601, message: `Method not found: ${message.method}` },
|
|
172
|
+
})}\n`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const choices = Array.isArray(params.options) ? params.options : [];
|
|
176
|
+
const selected = choices.find((value) =>
|
|
177
|
+
typeof value === "object" && value !== null && (value as { kind?: unknown }).kind === "allow_once") as { optionId?: unknown } | undefined;
|
|
178
|
+
if (typeof selected?.optionId !== "string") {
|
|
179
|
+
terminate(acpError("acp-permission-missing-allow-once", "ACP permission request omitted allow_once"));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: message.id, result: { outcome: { outcome: "selected", optionId: selected.optionId } } })}\n`);
|
|
174
183
|
}
|
|
175
|
-
|
|
184
|
+
return;
|
|
176
185
|
}
|
|
177
|
-
return;
|
|
186
|
+
if (typeof message.id !== "number") return;
|
|
187
|
+
const waiter = pending.get(message.id);
|
|
188
|
+
if (waiter === undefined) return;
|
|
189
|
+
pending.delete(message.id);
|
|
190
|
+
if (message.error !== undefined) {
|
|
191
|
+
const diagnostic = typeof message.error === "object" && message.error !== null
|
|
192
|
+
&& "message" in message.error && typeof message.error.message === "string"
|
|
193
|
+
? message.error.message : JSON.stringify(message.error);
|
|
194
|
+
rpcFailure ??= { diagnostic, details: message };
|
|
195
|
+
waiter.reject(acpError("acp-upstream-error", diagnostic, message));
|
|
196
|
+
} else waiter.resolve((message.result ?? {}) as Readonly<Record<string, unknown>>);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
terminate(error);
|
|
178
199
|
}
|
|
179
|
-
if (typeof message.id !== "number") return;
|
|
180
|
-
const waiter = pending.get(message.id);
|
|
181
|
-
if (waiter === undefined) return;
|
|
182
|
-
pending.delete(message.id);
|
|
183
|
-
if (message.error !== undefined) waiter.reject(acpError("acp-upstream-error", `ACP error: ${JSON.stringify(message.error)}`));
|
|
184
|
-
else waiter.resolve((message.result ?? {}) as Readonly<Record<string, unknown>>);
|
|
185
200
|
});
|
|
186
|
-
|
|
201
|
+
const processClosed = new Promise<void>((resolve) => {
|
|
202
|
+
child.once("close", (code, signal) => {
|
|
203
|
+
// ENOENT also emits close, but no host process was launched in that case.
|
|
204
|
+
if (spawned) nativeExit = { code, timedOut: false, ...(signal === null ? {} : { signal }) };
|
|
205
|
+
settleClosed(acpError("acp-closed", "ACP process closed"));
|
|
206
|
+
resolve();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
187
209
|
return Promise.resolve({
|
|
188
210
|
request(method, params) {
|
|
189
|
-
if (closed) return Promise.reject(terminalError
|
|
211
|
+
if (closed) return Promise.reject(terminalError);
|
|
190
212
|
const id = ++nextId;
|
|
191
213
|
return new Promise((resolve, reject) => {
|
|
192
214
|
pending.set(id, { resolve, reject });
|
|
@@ -195,12 +217,12 @@ export function connectAcpStdio(options: {
|
|
|
195
217
|
const waiter = pending.get(id);
|
|
196
218
|
if (waiter === undefined) return;
|
|
197
219
|
pending.delete(id);
|
|
198
|
-
waiter.reject(
|
|
220
|
+
waiter.reject(error);
|
|
199
221
|
});
|
|
200
222
|
});
|
|
201
223
|
},
|
|
202
224
|
notify(method, params) {
|
|
203
|
-
if (closed) throw terminalError
|
|
225
|
+
if (closed) throw terminalError;
|
|
204
226
|
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
205
227
|
},
|
|
206
228
|
onNotification(handler) {
|
|
@@ -209,12 +231,18 @@ export function connectAcpStdio(options: {
|
|
|
209
231
|
stderr() {
|
|
210
232
|
return stderr;
|
|
211
233
|
},
|
|
234
|
+
result() {
|
|
235
|
+
if (nativeExit === undefined && rpcFailure === undefined) return undefined;
|
|
236
|
+
return {
|
|
237
|
+
...(nativeExit ?? { code: null, timedOut: false }),
|
|
238
|
+
stderr,
|
|
239
|
+
...(rpcFailure === undefined ? {} : { knownFailure: rpcFailure }),
|
|
240
|
+
};
|
|
241
|
+
},
|
|
212
242
|
async close() {
|
|
213
|
-
if (closed)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
child.kill("SIGTERM");
|
|
217
|
-
await new Promise<void>((resolve) => child.once("close", () => resolve()));
|
|
243
|
+
if (!closed) terminate(acpError("acp-connection-closed", "ACP connection is closed"));
|
|
244
|
+
await processClosed;
|
|
245
|
+
return !stoppedByPackage && nativeExit !== undefined ? { ...nativeExit, stderr } : undefined;
|
|
218
246
|
},
|
|
219
247
|
});
|
|
220
248
|
}
|
|
@@ -229,8 +257,9 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
229
257
|
let sessionOpened = false;
|
|
230
258
|
let accepted = false;
|
|
231
259
|
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
232
|
-
// Mutable so dispose failure can outrank a clean turn.
|
|
233
260
|
let outcome: RoleTurnResult = failure("session", "AcpNoOutcome", "no-outcome");
|
|
261
|
+
let reportedFailure: RoleTurnResult["knownFailure"];
|
|
262
|
+
let packageFailure: RoleTurnResult["knownFailure"];
|
|
234
263
|
try {
|
|
235
264
|
if (prepared.mcpServers.length === 0) {
|
|
236
265
|
outcome = failure("activation", "UncontrolledAcpSession", "ak-config-missing");
|
|
@@ -361,9 +390,15 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
361
390
|
collectAgentProse = false;
|
|
362
391
|
if (result.stopReason === "refusal") {
|
|
363
392
|
agentProseChunks.length = 0;
|
|
393
|
+
reportedFailure = { diagnostic: result.stopReason, details: result };
|
|
364
394
|
return {
|
|
365
395
|
status: "terminal",
|
|
366
|
-
result:
|
|
396
|
+
result: {
|
|
397
|
+
code: null,
|
|
398
|
+
stderr: activeConnection.stderr?.() ?? "",
|
|
399
|
+
timedOut: false,
|
|
400
|
+
knownFailure: reportedFailure,
|
|
401
|
+
},
|
|
367
402
|
};
|
|
368
403
|
}
|
|
369
404
|
// #959: navigator prose exit when the model spoke without the output tool.
|
|
@@ -389,32 +424,90 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
389
424
|
} // sessionReady
|
|
390
425
|
} // model match else
|
|
391
426
|
} // mcpServers else
|
|
427
|
+
} catch (error) {
|
|
428
|
+
const native = connection?.result?.();
|
|
429
|
+
reportedFailure = native?.knownFailure;
|
|
430
|
+
outcome = native ?? { code: null, stderr: connection?.stderr?.() ?? "", timedOut: false };
|
|
431
|
+
await retainPackageFault({
|
|
432
|
+
runDirectory: request.runDirectory,
|
|
433
|
+
diagnostic: `ACP turn exception beside native host facts: ${describeErrorIdentity(error)}`,
|
|
434
|
+
error,
|
|
435
|
+
});
|
|
436
|
+
// Native abnormal exit / RPC report stays primary. Otherwise this is a
|
|
437
|
+
// real package exception, not a host cause inferred from missing output.
|
|
438
|
+
if (outcome.knownFailure === undefined && !outcome.timedOut && outcome.signal === undefined
|
|
439
|
+
&& (native === undefined || outcome.code === 0 || outcome.code === null)) {
|
|
440
|
+
packageFailure = projectThrownFailureLeaf(error);
|
|
441
|
+
outcome = { ...outcome, knownFailure: packageFailure };
|
|
442
|
+
}
|
|
392
443
|
} finally {
|
|
444
|
+
let naturalTermination: RoleTurnResult | void = undefined;
|
|
393
445
|
if (connection !== undefined) {
|
|
394
446
|
if (sessionId !== undefined && !accepted) {
|
|
395
447
|
try { connection.notify("session/cancel", { sessionId }); }
|
|
396
|
-
catch {
|
|
448
|
+
catch (error) {
|
|
449
|
+
await retainPackageFault({
|
|
450
|
+
runDirectory: request.runDirectory,
|
|
451
|
+
diagnostic: `session cancel failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
452
|
+
error,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
try { naturalTermination = await connection.close(); }
|
|
457
|
+
catch (error) {
|
|
458
|
+
await retainPackageFault({
|
|
459
|
+
runDirectory: request.runDirectory,
|
|
460
|
+
diagnostic: `connection close failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
461
|
+
error,
|
|
462
|
+
});
|
|
397
463
|
}
|
|
398
|
-
try { await connection.close(); }
|
|
399
|
-
catch { /* keep turn result */ }
|
|
400
464
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
465
|
+
// Both success and failure drain the native stderr stream, including
|
|
466
|
+
// bytes arriving after the last RPC response.
|
|
467
|
+
outcome = { ...outcome, stderr: connection?.stderr?.() ?? outcome.stderr };
|
|
468
|
+
// On abnormal paths, teardown has now drained the real close/stderr.
|
|
469
|
+
// A successful ACP protocol turn remains successful: its ordinary
|
|
470
|
+
// connection cleanup is not reclassified as a role failure.
|
|
471
|
+
const native = connection?.result?.() ?? naturalTermination;
|
|
472
|
+
if (native !== undefined && (naturalTermination !== undefined || outcome.code !== 0
|
|
473
|
+
|| outcome.knownFailure !== undefined || outcome.timedOut || outcome.signal !== undefined)) {
|
|
474
|
+
// A late native failure stays primary over a package failure. An own
|
|
475
|
+
// SIGTERM stop alone is ordinary disposal, not a guessed host cause.
|
|
476
|
+
const nativeFailed = (native.code !== null && native.code !== 0)
|
|
477
|
+
|| (naturalTermination !== undefined && (native.timedOut || native.signal !== undefined));
|
|
478
|
+
let knownFailure = outcome.knownFailure;
|
|
479
|
+
if (nativeFailed && knownFailure !== undefined && knownFailure !== reportedFailure) {
|
|
480
|
+
if (knownFailure !== packageFailure) await retainPackageFault({
|
|
481
|
+
runDirectory: request.runDirectory,
|
|
482
|
+
diagnostic: knownFailure.diagnostic ?? "ACP package failure beside late native termination",
|
|
483
|
+
error: knownFailure,
|
|
484
|
+
});
|
|
485
|
+
knownFailure = reportedFailure;
|
|
486
|
+
}
|
|
487
|
+
const { knownFailure: _nativeFailure, ...facts } = native;
|
|
488
|
+
outcome = { ...facts, ...(knownFailure === undefined ? {} : { knownFailure }) };
|
|
412
489
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
490
|
+
if (config.hostName !== "hermes" && sessionOpened && sessionId !== undefined) {
|
|
491
|
+
try {
|
|
492
|
+
copyAndRecordHostDossier({
|
|
493
|
+
host: config.hostName,
|
|
494
|
+
sessionId,
|
|
495
|
+
cwd: request.cwd,
|
|
496
|
+
sessionDirectory: join(request.runDirectory, "session"),
|
|
497
|
+
sessionParent,
|
|
498
|
+
continuation: request.continuation,
|
|
499
|
+
...(request.model !== undefined ? { model: request.model } : {}),
|
|
500
|
+
...(request.home !== undefined ? { home: request.home } : {}),
|
|
501
|
+
});
|
|
502
|
+
} catch (error) {
|
|
503
|
+
await retainPackageFault({
|
|
504
|
+
runDirectory: request.runDirectory,
|
|
505
|
+
diagnostic: `host dossier copy failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
506
|
+
error,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
417
509
|
}
|
|
510
|
+
outcome = await disposeExternalRoleTurn(prepared, request, outcome);
|
|
418
511
|
}
|
|
419
512
|
return outcome;
|
|
420
513
|
});
|
|
@@ -4,7 +4,7 @@ import { withTerminatingOutputDeclarations } from "./package-contracts/terminati
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* #1088: LLM gathers evidence via host CLI and submits the receipt.
|
|
7
|
-
* Field declarations guide the model — host must not pure-shape-reject (
|
|
7
|
+
* Field declarations guide the model — host must not pure-shape-reject (仓内 CLAUDE.md 开篇 / ADR 0057).
|
|
8
8
|
* Presence of `groups` remains the Collector terminal discriminator for settlement/analyst.
|
|
9
9
|
*/
|
|
10
10
|
export const collectorOutputBaseSchema = openToolObject(
|
|
@@ -6,12 +6,12 @@ import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
|
6
6
|
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
7
7
|
import { officerConclusionReask } from "./gatekeeper-role.ts";
|
|
8
8
|
import { receivedDiscriminator } from "./submission-errors.ts";
|
|
9
|
-
import {
|
|
9
|
+
import { currentReplyRows } from "./public-cli/terminal.ts";
|
|
10
10
|
import { readableGateItem } from "./readable-gate-item.ts";
|
|
11
11
|
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.ts";
|
|
12
12
|
import { isRecord } from "./unknown-value.ts";
|
|
13
13
|
|
|
14
|
-
export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt"; usage?: Usage };
|
|
14
|
+
export type ComplianceNoReceipt = Partial<NoReceiptLifecycleFacts> & { status: "no-receipt"; usage?: Usage };
|
|
15
15
|
/**
|
|
16
16
|
* #757 / #750: no unreadable/unusable judgment on auditor replies.
|
|
17
17
|
* Known three-state (converged/continue/escalate) is read for queueing only.
|
|
@@ -172,10 +172,7 @@ async function projectAuditorTerminal(summoned: PublicSummonResult): Promise<Com
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
if (outcome.kind === "accepted") {
|
|
175
|
-
const rows =
|
|
176
|
-
outcome.payloads,
|
|
177
|
-
summoned.terminal?.submissions,
|
|
178
|
-
);
|
|
175
|
+
const rows = currentReplyRows(summoned.terminal);
|
|
179
176
|
if (rows.length === 0) {
|
|
180
177
|
return readComplianceCandidate({}, usage);
|
|
181
178
|
}
|
|
@@ -37,8 +37,8 @@ export const ENGINE_DETOUR_TOOL_USAGE_FACT_KEY = "engineDetourToolUsage" as cons
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Run-relative sitian volume path for one detour call.
|
|
40
|
-
* Openable once the run directory is known
|
|
41
|
-
*
|
|
40
|
+
* Openable once the run directory is known from the terminal's run id.
|
|
41
|
+
* Contains no runId bytes itself (#108 + #537).
|
|
42
42
|
*/
|
|
43
43
|
export const ENGINE_DETOUR_CALL_RECORD_FILE_RELATIVE =
|
|
44
44
|
`session/${ENGINE_DETOUR_CALL_KIND}/records.jsonl` as const;
|
|
@@ -232,7 +232,7 @@ export async function readEngineDetourToolUsage(input: {
|
|
|
232
232
|
}
|
|
233
233
|
const pointer: RecordPointer = {
|
|
234
234
|
identity: record.identity,
|
|
235
|
-
recordFile,
|
|
235
|
+
recordFile: ENGINE_DETOUR_CALL_RECORD_FILE_RELATIVE,
|
|
236
236
|
kind: record.kind,
|
|
237
237
|
level: record.level,
|
|
238
238
|
};
|
|
@@ -246,36 +246,6 @@ export async function readEngineDetourToolUsage(input: {
|
|
|
246
246
|
return { callCount: calls.length, calls };
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
/**
|
|
250
|
-
* Project usage onto the public Terminal face.
|
|
251
|
-
* Non-resumable: keep absolute recordFile (runId already public via top-level runId).
|
|
252
|
-
* Resumable: keep an openable run-relative recordFile and identity with no runId
|
|
253
|
-
* bytes (#108 single disclosure + #537 AC8 reopen).
|
|
254
|
-
*/
|
|
255
|
-
export function projectEngineDetourToolUsageForPublicTerminal(
|
|
256
|
-
usage: EngineDetourToolUsageFact,
|
|
257
|
-
options: { readonly discloseRecordFile: boolean },
|
|
258
|
-
): EngineDetourToolUsageFact {
|
|
259
|
-
if (options.discloseRecordFile) return usage;
|
|
260
|
-
return {
|
|
261
|
-
callCount: usage.callCount,
|
|
262
|
-
calls: usage.calls.map((call) => ({
|
|
263
|
-
toolCallId: call.toolCallId,
|
|
264
|
-
durationMs: call.durationMs,
|
|
265
|
-
...(call.code === undefined ? {} : { code: call.code }),
|
|
266
|
-
...(call.stdoutByteLength === undefined
|
|
267
|
-
? {}
|
|
268
|
-
: { stdoutByteLength: call.stdoutByteLength }),
|
|
269
|
-
recordPointer: {
|
|
270
|
-
identity: call.recordPointer.identity,
|
|
271
|
-
kind: call.recordPointer.kind,
|
|
272
|
-
level: call.recordPointer.level,
|
|
273
|
-
recordFile: ENGINE_DETOUR_CALL_RECORD_FILE_RELATIVE,
|
|
274
|
-
},
|
|
275
|
-
})),
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
249
|
/** One shared invocation.json reader (ENOENT → undefined; other errors propagate). */
|
|
280
250
|
function readInvocationRecord(
|
|
281
251
|
runDirectory: string,
|