@akagilnc/pi-workflow-roles 0.1.5375 → 0.1.5406
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 +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/acp-host/production-host.js +6381 -5684
- package/dist/archivist-record-pointer.js +38 -0
- package/dist/collector-tool-schemas.js +18 -0
- package/dist/compliance-transport.js +5 -0
- package/dist/diarist-contracts.js +26 -0
- package/dist/doctor-auditor.js +1 -0
- package/dist/engine-detour-usage.js +6 -6
- package/dist/filed-submission.js +28 -0
- package/dist/gatekeeper-role.js +6 -10
- package/dist/gleaner-left-contracts.js +16 -0
- package/dist/headless-host/production-host.js +1644 -947
- package/dist/judge-role.js +21 -16
- package/dist/migrate-book-topology.js +452 -81
- package/dist/navigator-attendance.js +10 -9
- package/dist/navigator-invocation-identity.js +18 -0
- package/dist/package-contracts/reviewer-output.js +16 -0
- package/dist/package-contracts/worker-output.js +18 -2
- package/dist/pi/role-turn-host.js +9 -0
- package/dist/public-cli/cli.js +1 -0
- package/dist/public-cli/instruction-seat-run.js +258 -55
- package/dist/public-cli/main.js +1209 -346
- package/dist/public-cli/post-admission.js +427 -244
- package/dist/public-cli/settlement.js +111 -48
- package/dist/public-cli/terminal.js +12 -1
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/receipt-delivery-policy.js +152 -17
- package/dist/role-submission-declarations.js +131 -0
- package/dist/secretariat-contracts.js +23 -1
- package/dist/submission-errors.js +11 -1
- package/dist/submission-gate.js +32 -12
- package/package.json +1 -1
- package/src/archivist-record-pointer.ts +48 -0
- package/src/auditor-role.ts +2 -8
- package/src/collector-role.ts +5 -28
- package/src/compliance-transport.ts +10 -2
- package/src/countersign-role.ts +2 -7
- package/src/diarist-contracts.ts +37 -0
- package/src/diarist-role.ts +4 -44
- package/src/doctor-auditor.ts +3 -0
- package/src/doctor-role.ts +16 -11
- package/src/engine-detour-usage.ts +7 -6
- package/src/external-host-turn-loop.ts +24 -12
- package/src/filed-submission.ts +44 -0
- package/src/gatekeeper-role.ts +8 -16
- package/src/gleaner-left-contracts.ts +26 -1
- package/src/gleaner-left-role.ts +4 -32
- package/src/host-contracts.ts +7 -0
- package/src/inspector-role.ts +2 -7
- package/src/judge-role.ts +31 -24
- package/src/merger-role.ts +5 -8
- package/src/navigator-attendance.ts +17 -12
- package/src/navigator-invocation-identity.ts +31 -0
- package/src/navigator-role.ts +2 -8
- package/src/notary-role.ts +5 -20
- package/src/package-contracts/reviewer-output.ts +25 -0
- package/src/package-contracts/worker-output.ts +43 -5
- package/src/pi/adapter.ts +33 -8
- package/src/pi/role-turn-host.ts +7 -0
- package/src/public-cli/cli.ts +1 -0
- package/src/public-cli/instruction-seat-run.ts +292 -61
- package/src/public-cli/post-admission.ts +556 -324
- package/src/public-cli/settlement.ts +112 -50
- package/src/public-cli/terminal.ts +16 -4
- package/src/public-cli/turn-request.ts +10 -1
- package/src/receipt-delivery-policy.ts +209 -17
- package/src/reviewer-role.ts +7 -35
- package/src/role-envelope.ts +5 -1
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +72 -56
- package/src/role-submission-declarations.ts +143 -0
- package/src/secretariat-contracts.ts +35 -0
- package/src/secretariat-role.ts +4 -40
- package/src/submission-correctable-error.ts +5 -2
- package/src/submission-errors.ts +12 -1
- package/src/submission-gate.ts +41 -12
- package/src/worker-role.ts +81 -86
- package/src/worker-submission-gates.ts +32 -7
package/dist/submission-gate.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { bookDirectOfficerRunPointer } from "./archivist-record-pointer.js";
|
|
2
2
|
import { GatekeeperDecisionError, officerConclusionReask, projectGatekeeperRun, } from "./gatekeeper-role.js";
|
|
3
|
-
|
|
3
|
+
import { receivedDiscriminator } from "./submission-errors.js";
|
|
4
|
+
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.js";
|
|
5
|
+
/** Received discriminator of the latest payload, retained verbatim for re-ask. */
|
|
4
6
|
export function latestQueueStatus(terminal) {
|
|
5
7
|
const outcome = terminal?.roleOutcome;
|
|
6
8
|
if (outcome === undefined)
|
|
@@ -10,14 +12,10 @@ export function latestQueueStatus(terminal) {
|
|
|
10
12
|
if (outcome.kind !== "accepted")
|
|
11
13
|
return undefined;
|
|
12
14
|
const latest = outcome.payloads?.[outcome.payloads.length - 1];
|
|
13
|
-
if (isRecord(latest)) {
|
|
14
|
-
|
|
15
|
-
if (typeof status === "string" && status.trim() !== "")
|
|
16
|
-
return status;
|
|
15
|
+
if (isRecord(latest) && Object.hasOwn(latest, "status")) {
|
|
16
|
+
return receivedDiscriminator(latest, "status");
|
|
17
17
|
}
|
|
18
|
-
return
|
|
19
|
-
? outcome.status
|
|
20
|
-
: undefined;
|
|
18
|
+
return outcome.status;
|
|
21
19
|
}
|
|
22
20
|
export function latestQueuePayload(terminal) {
|
|
23
21
|
const outcome = terminal?.roleOutcome;
|
|
@@ -57,7 +55,7 @@ export function createDefaultGateOfficerSummon(options) {
|
|
|
57
55
|
* Book a typed pointer under parent session/auditor-roles (archivist-owned write).
|
|
58
56
|
* Offline mocks without a real session leave no nested volume (lawful zero).
|
|
59
57
|
*/
|
|
60
|
-
function bookDirectOfficerPointer(context, officer, result, summoned) {
|
|
58
|
+
function bookDirectOfficerPointer(context, officer, result, summoned, toolCallId) {
|
|
61
59
|
if (result.status !== "converged"
|
|
62
60
|
&& result.status !== "continue"
|
|
63
61
|
&& result.status !== "escalate"
|
|
@@ -80,16 +78,20 @@ function bookDirectOfficerPointer(context, officer, result, summoned) {
|
|
|
80
78
|
...(typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== ""
|
|
81
79
|
? { runDirectory: summoned.runDirectory }
|
|
82
80
|
: {}),
|
|
81
|
+
...(toolCallId.trim() === "" ? {} : { submissionToolCallId: toolCallId }),
|
|
83
82
|
});
|
|
84
83
|
}
|
|
85
84
|
/**
|
|
86
85
|
* Shared envelope: project gate, book officer pointer, map onto host actions.
|
|
87
|
-
*
|
|
86
|
+
* A real converged / continue / escalate returns immediately. An unreadable
|
|
87
|
+
* conclusion reasks that officer against the configured ceiling; exhaustion
|
|
88
|
+
* returns the receipt actually received.
|
|
88
89
|
* On converged, returns the officer snapshot (receipt + nested runId) for seat projection.
|
|
89
90
|
*/
|
|
90
91
|
export async function requireSubmissionGate(options) {
|
|
91
92
|
let reask;
|
|
92
|
-
|
|
93
|
+
let reasksSpent = 0;
|
|
94
|
+
const reaskLimit = deliveryLimitFromConfig(options.autoResumeLimit);
|
|
93
95
|
const summonOfficer = options.summonOfficer ??
|
|
94
96
|
createDefaultGateOfficerSummon({
|
|
95
97
|
cwd: options.context.cwd ?? process.cwd(),
|
|
@@ -107,7 +109,7 @@ export async function requireSubmissionGate(options) {
|
|
|
107
109
|
// Envelope-owned pointer book. Failure is host infrastructure — single face.
|
|
108
110
|
if (projected.summoned !== undefined) {
|
|
109
111
|
try {
|
|
110
|
-
bookDirectOfficerPointer(options.context, projected.officer, gatekeeper, projected.summoned);
|
|
112
|
+
bookDirectOfficerPointer(options.context, projected.officer, gatekeeper, projected.summoned, options.toolCallId);
|
|
111
113
|
}
|
|
112
114
|
catch (error) {
|
|
113
115
|
options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
|
|
@@ -134,6 +136,24 @@ export async function requireSubmissionGate(options) {
|
|
|
134
136
|
};
|
|
135
137
|
}
|
|
136
138
|
if (gatekeeper.status === "needs_reask") {
|
|
139
|
+
if (reasksSpent >= reaskLimit) {
|
|
140
|
+
return {
|
|
141
|
+
status: "needs_reask",
|
|
142
|
+
officer: projected.officer,
|
|
143
|
+
receipt: gatekeeper.receipt,
|
|
144
|
+
...(typeof gatekeeper.runId === "string" && gatekeeper.runId.trim() !== ""
|
|
145
|
+
? { runId: gatekeeper.runId }
|
|
146
|
+
: {}),
|
|
147
|
+
...(typeof projected.summoned?.runDirectory === "string"
|
|
148
|
+
&& projected.summoned.runDirectory.trim() !== ""
|
|
149
|
+
? { runDirectory: projected.summoned.runDirectory }
|
|
150
|
+
: {}),
|
|
151
|
+
...(projected.summoned?.terminal === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: { terminal: projected.summoned.terminal }),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
reasksSpent += 1;
|
|
137
157
|
reask = officerConclusionReask(gatekeeper.receivedStatus);
|
|
138
158
|
continue;
|
|
139
159
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { isRecord } from "./unknown-value.ts";
|
|
3
5
|
|
|
4
6
|
/** Typed parent-side pointer to an independent officer run 正本 (ADR 0079 / #675). */
|
|
5
7
|
export const DIRECT_OFFICER_RUN_POINTER_KIND = "direct-officer-run-pointer" as const;
|
|
@@ -12,6 +14,11 @@ export type DirectOfficerRunPointer = {
|
|
|
12
14
|
readonly sessionFile: string;
|
|
13
15
|
/** Officer run directory when known. */
|
|
14
16
|
readonly runDirectory?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Parent submission tool call this officer run was summoned for.
|
|
19
|
+
* Absent on pointers booked before that binding existed.
|
|
20
|
+
*/
|
|
21
|
+
readonly submissionToolCallId?: string;
|
|
15
22
|
};
|
|
16
23
|
|
|
17
24
|
/**
|
|
@@ -28,9 +35,11 @@ export function bookDirectOfficerRunPointer(options: {
|
|
|
28
35
|
readonly officer: "inspector" | "notary" | "auditor" | "countersign";
|
|
29
36
|
readonly sessionFile: string;
|
|
30
37
|
readonly runDirectory?: string;
|
|
38
|
+
readonly submissionToolCallId?: string;
|
|
31
39
|
}): DirectOfficerRunPointer {
|
|
32
40
|
const nest = join(dirname(options.parentSessionFile), "auditor-roles");
|
|
33
41
|
mkdirSync(nest, { recursive: true });
|
|
42
|
+
const submissionToolCallId = options.submissionToolCallId?.trim() ?? "";
|
|
34
43
|
const pointer: DirectOfficerRunPointer = {
|
|
35
44
|
version: 1,
|
|
36
45
|
kind: DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
@@ -39,6 +48,7 @@ export function bookDirectOfficerRunPointer(options: {
|
|
|
39
48
|
...(options.runDirectory !== undefined && options.runDirectory.trim() !== ""
|
|
40
49
|
? { runDirectory: options.runDirectory }
|
|
41
50
|
: {}),
|
|
51
|
+
...(submissionToolCallId === "" ? {} : { submissionToolCallId }),
|
|
42
52
|
};
|
|
43
53
|
writeFileSync(
|
|
44
54
|
join(nest, `${options.officer}.pointer.json`),
|
|
@@ -47,3 +57,41 @@ export function bookDirectOfficerRunPointer(options: {
|
|
|
47
57
|
);
|
|
48
58
|
return pointer;
|
|
49
59
|
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Read the one officer pointer. Missing or unreadable is not a pass:
|
|
63
|
+
* the caller re-runs the gate.
|
|
64
|
+
*/
|
|
65
|
+
export async function readDirectOfficerRunPointer(
|
|
66
|
+
parentSessionFile: string,
|
|
67
|
+
officer: DirectOfficerRunPointer["officer"],
|
|
68
|
+
): Promise<DirectOfficerRunPointer | undefined> {
|
|
69
|
+
try {
|
|
70
|
+
const raw: unknown = JSON.parse(await readFile(
|
|
71
|
+
join(dirname(parentSessionFile), "auditor-roles", `${officer}.pointer.json`),
|
|
72
|
+
"utf8",
|
|
73
|
+
));
|
|
74
|
+
if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
if (raw.officer !== officer) return undefined;
|
|
78
|
+
if (typeof raw.sessionFile !== "string" || raw.sessionFile.trim() === "") return undefined;
|
|
79
|
+
const runDirectory = typeof raw.runDirectory === "string" && raw.runDirectory.trim() !== ""
|
|
80
|
+
? raw.runDirectory
|
|
81
|
+
: undefined;
|
|
82
|
+
const submissionToolCallId = typeof raw.submissionToolCallId === "string"
|
|
83
|
+
&& raw.submissionToolCallId.trim() !== ""
|
|
84
|
+
? raw.submissionToolCallId
|
|
85
|
+
: undefined;
|
|
86
|
+
return {
|
|
87
|
+
version: 1,
|
|
88
|
+
kind: DIRECT_OFFICER_RUN_POINTER_KIND,
|
|
89
|
+
officer,
|
|
90
|
+
sessionFile: raw.sessionFile,
|
|
91
|
+
...(runDirectory === undefined ? {} : { runDirectory }),
|
|
92
|
+
...(submissionToolCallId === undefined ? {} : { submissionToolCallId }),
|
|
93
|
+
};
|
|
94
|
+
} catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/auditor-role.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import {
|
|
5
5
|
AUDITOR_OUTPUT_TOOL_NAME,
|
|
6
|
-
auditorOutputSchema,
|
|
7
6
|
} from "./package-contracts/auditor-output.ts";
|
|
7
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
8
8
|
|
|
9
9
|
export { AUDITOR_OUTPUT_TOOL_NAME };
|
|
10
10
|
|
|
@@ -12,10 +12,4 @@ export type AuditorRuntimeDependencies = {
|
|
|
12
12
|
loadSoul(): Promise<string>;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export const AUDITOR_TOOL_SPEC =
|
|
16
|
-
name: AUDITOR_OUTPUT_TOOL_NAME,
|
|
17
|
-
label: "审刑院输出",
|
|
18
|
-
description: "审刑院终局回执,status 为 converged、continue 或 escalate。",
|
|
19
|
-
promptSnippet: "审刑院终局回执",
|
|
20
|
-
parameters: auditorOutputSchema,
|
|
21
|
-
} as const;
|
|
15
|
+
export const AUDITOR_TOOL_SPEC = roleSubmissionDeclaration("auditor");
|
package/src/collector-role.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* #1088: code no longer observes/requests/merges GitHub findings — the LLM
|
|
4
4
|
* uses host CLI tools. Envelope owns host-surface + construction seatbelt.
|
|
5
5
|
*/
|
|
6
|
-
import type { RoleHost, HostContext
|
|
7
|
-
import
|
|
6
|
+
import type { RoleHost, HostContext } from "./host-contracts.ts";
|
|
7
|
+
import { registerFiledSubmissionTool } from "./filed-submission.ts";
|
|
8
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
8
9
|
|
|
9
10
|
import {
|
|
10
11
|
emptyCollectorManifest,
|
|
@@ -17,8 +18,6 @@ import {
|
|
|
17
18
|
import {
|
|
18
19
|
COLLECTOR_OUTPUT_TOOL,
|
|
19
20
|
} from "./package-contracts/collector-output.ts";
|
|
20
|
-
import { collectorOutputArgsSchema } from "./collector-tool-schemas.ts";
|
|
21
|
-
|
|
22
21
|
export { COLLECTOR_OUTPUT_TOOL };
|
|
23
22
|
|
|
24
23
|
export const COLLECTOR_REQUIRED_TOOLS = [COLLECTOR_OUTPUT_TOOL] as const;
|
|
@@ -54,8 +53,6 @@ export const COLLECTOR_TRANSPORT_FLAGS = Object.freeze([
|
|
|
54
53
|
}),
|
|
55
54
|
] as const);
|
|
56
55
|
|
|
57
|
-
const outputSchema = collectorOutputArgsSchema;
|
|
58
|
-
type OutputParams = Static<typeof outputSchema>;
|
|
59
56
|
|
|
60
57
|
export type CollectorRoleDependencies = {
|
|
61
58
|
loadSoul(): Promise<string>;
|
|
@@ -164,28 +161,8 @@ export function createCollectorRoleRuntime(
|
|
|
164
161
|
if (toolsRegistered) return;
|
|
165
162
|
toolsRegistered = true;
|
|
166
163
|
|
|
167
|
-
pi
|
|
168
|
-
|
|
169
|
-
label: "通进司输出",
|
|
170
|
-
description: "提交通进司回执。",
|
|
171
|
-
promptSnippet: "提交通进司回执",
|
|
172
|
-
parameters: outputSchema,
|
|
173
|
-
async execute(
|
|
174
|
-
_toolCallId: string,
|
|
175
|
-
params: OutputParams,
|
|
176
|
-
_signal: AbortSignal | undefined,
|
|
177
|
-
_onUpdate: unknown,
|
|
178
|
-
_ctx: HostContext,
|
|
179
|
-
): Promise<HostToolResult<unknown>> {
|
|
180
|
-
const activation = getActivation();
|
|
181
|
-
if (activation === undefined) throw new Error("通进司未激活");
|
|
182
|
-
// #1088 / #836: record as submitted — runtime does not merge findings.
|
|
183
|
-
return {
|
|
184
|
-
content: [],
|
|
185
|
-
details: params,
|
|
186
|
-
terminate: true as const,
|
|
187
|
-
};
|
|
188
|
-
},
|
|
164
|
+
registerFiledSubmissionTool(pi, roleSubmissionDeclaration("collector"), {
|
|
165
|
+
readyError: () => (getActivation() === undefined ? "通进司未激活" : undefined),
|
|
189
166
|
});
|
|
190
167
|
},
|
|
191
168
|
};
|
|
@@ -8,14 +8,16 @@ import { officerConclusionReask } from "./gatekeeper-role.ts";
|
|
|
8
8
|
import { receivedDiscriminator } from "./submission-errors.ts";
|
|
9
9
|
import { coalesceSubmissionRows } from "./public-cli/terminal.ts";
|
|
10
10
|
import { readableGateItem } from "./readable-gate-item.ts";
|
|
11
|
+
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.ts";
|
|
11
12
|
import { isRecord } from "./unknown-value.ts";
|
|
12
13
|
|
|
13
14
|
export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt"; usage?: Usage };
|
|
14
15
|
/**
|
|
15
16
|
* #757 / #750: no unreadable/unusable judgment on auditor replies.
|
|
16
17
|
* Known three-state (converged/continue/escalate) is read for queueing only.
|
|
17
|
-
* Unknown accepted shape rides as `received` with the raw reply
|
|
18
|
-
*
|
|
18
|
+
* Unknown accepted shape rides as `received` with the raw reply. The summon
|
|
19
|
+
* loop reasks until the configured ceiling, then keeps that reply. No forged
|
|
20
|
+
* pass, no shape-death label.
|
|
19
21
|
*/
|
|
20
22
|
export type ComplianceReceived = {
|
|
21
23
|
readonly status: "received";
|
|
@@ -120,6 +122,8 @@ export type RunComplianceAuditOptions = {
|
|
|
120
122
|
submission?: unknown;
|
|
121
123
|
runDirectory?: string | undefined;
|
|
122
124
|
signal?: AbortSignal;
|
|
125
|
+
/** This loop's ceiling. Absent uses the package default. */
|
|
126
|
+
autoResumeLimit?: number;
|
|
123
127
|
/** Test seam — production uses summonPublicRole({ role: "auditor", argv: ["--subject", subject, "--source-run", …] }). */
|
|
124
128
|
summonAuditor?: AuditorSummon;
|
|
125
129
|
};
|
|
@@ -224,10 +228,14 @@ export async function runComplianceAudit(options: RunComplianceAuditOptions): Pr
|
|
|
224
228
|
});
|
|
225
229
|
});
|
|
226
230
|
let reask: string | undefined;
|
|
231
|
+
let reasksSpent = 0;
|
|
232
|
+
const reaskLimit = deliveryLimitFromConfig(options.autoResumeLimit);
|
|
227
233
|
for (;;) {
|
|
228
234
|
const summoned = await summon(subject, runDirectory, options.signal, reask, submission);
|
|
229
235
|
const decision = await projectAuditorTerminal(summoned);
|
|
230
236
|
if (decision.status === "received") {
|
|
237
|
+
if (reasksSpent >= reaskLimit) return decision;
|
|
238
|
+
reasksSpent += 1;
|
|
231
239
|
reask = officerConclusionReask(receivedDiscriminator(decision.reply, "status"));
|
|
232
240
|
continue;
|
|
233
241
|
}
|
package/src/countersign-role.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reviewSubmissionSchema } from "./review-submission.ts";
|
|
2
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
2
3
|
import {
|
|
3
4
|
COUNTERSIGN_OUTPUT_TOOL_NAME,
|
|
4
5
|
validateRecordedCountersignOutput,
|
|
@@ -26,10 +27,4 @@ export type CountersignRuntimeDependencies = {
|
|
|
26
27
|
* 决定工具规格。生命周期装配(注册、activate、prompt 注入、singleton 检查、
|
|
27
28
|
* terminate)归注册信封 owner——src/role-runtime.ts(ADR 0018 / #572 R2 判词)。
|
|
28
29
|
*/
|
|
29
|
-
export const COUNTERSIGN_TOOL_SPEC =
|
|
30
|
-
name: COUNTERSIGN_OUTPUT_TOOL_NAME,
|
|
31
|
-
label: "给事中输出",
|
|
32
|
-
description: "给事中决议。",
|
|
33
|
-
promptSnippet: "给事中决议",
|
|
34
|
-
parameters: countersignVerdictSchema,
|
|
35
|
-
} as const;
|
|
30
|
+
export const COUNTERSIGN_TOOL_SPEC = roleSubmissionDeclaration("countersign");
|
package/src/diarist-contracts.ts
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* #901: LLM submits bounds; the mechanical layer projects source bytes.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { Type } from "typebox";
|
|
11
|
+
|
|
12
|
+
import { openToolObject } from "./open-tool-schema.ts";
|
|
13
|
+
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
10
14
|
import {
|
|
11
15
|
projectTicketProvenanceSessions,
|
|
12
16
|
type TicketProvenanceSession,
|
|
@@ -88,3 +92,36 @@ export function projectDiaristTicketSessions(
|
|
|
88
92
|
}
|
|
89
93
|
return Array.from(perTicket, ([ticketNumber, sessions]) => ({ ticketNumber, sessions }));
|
|
90
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 起居郎交卷形状。
|
|
98
|
+
* #901:交边界(sessions);正文与不可解析原字节由机械投影。
|
|
99
|
+
* status 的合法词(completed | escalate)写在 description,结算认这些词。
|
|
100
|
+
* sessions / ticketSessions 是投影所读的边界:卷路径或端点读不出时走既有 reask,不中止本轮。
|
|
101
|
+
* 声明只留字段名和语义说明,不留类型、嵌套、长度、必填。
|
|
102
|
+
*/
|
|
103
|
+
const DIARIST_SESSIONS_DESCRIPTION =
|
|
104
|
+
"对话边界。每卷含 path(会话卷绝对或可读路径)与 ranges(本卷本轮各区间)。每段区间含 from / to;起点与终点各写 id(原生 id)或 line(本轮行号),二选一。空列表=本轮无对话可划。端点无法指名时走 reask,不中止。" as const;
|
|
105
|
+
|
|
106
|
+
export const diaristOutputSchema = withTerminatingOutputDeclarations(
|
|
107
|
+
openToolObject(
|
|
108
|
+
Type.Object({
|
|
109
|
+
status: Type.Unknown({
|
|
110
|
+
description: "completed | escalate",
|
|
111
|
+
}),
|
|
112
|
+
ticketNumber: Type.Optional(
|
|
113
|
+
Type.Unknown({
|
|
114
|
+
description:
|
|
115
|
+
"本庭对象票号(正整数)或 null/省略=真无票;下游走 typed 键;认不出用 status=escalate,不洗成无录。机械层不重判。",
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
reason: Type.Unknown({
|
|
119
|
+
description: "status 为 escalate 时:认不出本庭对象的原因",
|
|
120
|
+
}),
|
|
121
|
+
sessions: Type.Optional(Type.Unknown({ description: DIARIST_SESSIONS_DESCRIPTION })),
|
|
122
|
+
ticketSessions: Type.Unknown({
|
|
123
|
+
description: `多票庭逐票对话边界;每票含 ticketNumber(本条边界所归票号,正整数)与 sessions(${DIARIST_SESSIONS_DESCRIPTION})。一次交卷各票各自分录。与单票 sessions 二选一。`,
|
|
124
|
+
}),
|
|
125
|
+
}),
|
|
126
|
+
),
|
|
127
|
+
);
|
package/src/diarist-role.ts
CHANGED
|
@@ -1,53 +1,20 @@
|
|
|
1
1
|
import type { Static } from "typebox";
|
|
2
|
-
import { Type } from "typebox";
|
|
3
2
|
|
|
4
|
-
import { openToolObject } from "./open-tool-schema.ts";
|
|
5
|
-
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
6
3
|
import {
|
|
7
4
|
DIARIST_OUTPUT_TOOL_NAME,
|
|
5
|
+
diaristOutputSchema,
|
|
8
6
|
validateRecordedDiaristOutput,
|
|
9
7
|
type DiaristOutput,
|
|
10
8
|
} from "./diarist-contracts.ts";
|
|
9
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
11
10
|
|
|
12
11
|
export {
|
|
13
12
|
DIARIST_OUTPUT_TOOL_NAME,
|
|
13
|
+
diaristOutputSchema,
|
|
14
14
|
} from "./diarist-contracts.ts";
|
|
15
15
|
export type { DiaristOutput };
|
|
16
16
|
export { validateRecordedDiaristOutput };
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* 起居郎交卷形状。
|
|
20
|
-
* #901:交边界(sessions);正文与不可解析原字节由机械投影。
|
|
21
|
-
* status 的合法词(completed | escalate)写在 description,结算认这些词。
|
|
22
|
-
* sessions / ticketSessions 是投影所读的边界:卷路径或端点读不出时走既有 reask,不中止本轮。
|
|
23
|
-
* 声明只留字段名和语义说明,不留类型、嵌套、长度、必填。
|
|
24
|
-
*/
|
|
25
|
-
const DIARIST_SESSIONS_DESCRIPTION =
|
|
26
|
-
"对话边界。每卷含 path(会话卷绝对或可读路径)与 ranges(本卷本轮各区间)。每段区间含 from / to;起点与终点各写 id(原生 id)或 line(本轮行号),二选一。空列表=本轮无对话可划。端点无法指名时走 reask,不中止。" as const;
|
|
27
|
-
|
|
28
|
-
export const diaristOutputSchema = withTerminatingOutputDeclarations(
|
|
29
|
-
openToolObject(
|
|
30
|
-
Type.Object({
|
|
31
|
-
status: Type.Unknown({
|
|
32
|
-
description: "completed | escalate",
|
|
33
|
-
}),
|
|
34
|
-
ticketNumber: Type.Optional(
|
|
35
|
-
Type.Unknown({
|
|
36
|
-
description:
|
|
37
|
-
"本庭对象票号(正整数)或 null/省略=真无票;下游走 typed 键;认不出用 status=escalate,不洗成无录。机械层不重判。",
|
|
38
|
-
}),
|
|
39
|
-
),
|
|
40
|
-
reason: Type.Unknown({
|
|
41
|
-
description: "status 为 escalate 时:认不出本庭对象的原因",
|
|
42
|
-
}),
|
|
43
|
-
sessions: Type.Optional(Type.Unknown({ description: DIARIST_SESSIONS_DESCRIPTION })),
|
|
44
|
-
ticketSessions: Type.Unknown({
|
|
45
|
-
description: `多票庭逐票对话边界;每票含 ticketNumber(本条边界所归票号,正整数)与 sessions(${DIARIST_SESSIONS_DESCRIPTION})。一次交卷各票各自分录。与单票 sessions 二选一。`,
|
|
46
|
-
}),
|
|
47
|
-
}),
|
|
48
|
-
),
|
|
49
|
-
);
|
|
50
|
-
|
|
51
18
|
export type DiaristOutputParameters = Static<typeof diaristOutputSchema>;
|
|
52
19
|
|
|
53
20
|
export type DiaristRuntimeDependencies = {
|
|
@@ -57,11 +24,4 @@ export type DiaristRuntimeDependencies = {
|
|
|
57
24
|
/**
|
|
58
25
|
* 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
|
|
59
26
|
*/
|
|
60
|
-
export const DIARIST_TOOL_SPEC =
|
|
61
|
-
name: DIARIST_OUTPUT_TOOL_NAME,
|
|
62
|
-
label: "起居郎输出",
|
|
63
|
-
description:
|
|
64
|
-
"起居郎交单票 sessions 或多票逐票 ticketSessions 边界;认不出本庭对象则 escalate。",
|
|
65
|
-
promptSnippet: "起居郎交边界",
|
|
66
|
-
parameters: diaristOutputSchema,
|
|
67
|
-
} as const;
|
|
27
|
+
export const DIARIST_TOOL_SPEC = roleSubmissionDeclaration("diarist");
|
package/src/doctor-auditor.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type DoctorAuditOptions = {
|
|
|
16
16
|
context: HostContext;
|
|
17
17
|
submission: unknown;
|
|
18
18
|
signal?: AbortSignal;
|
|
19
|
+
/** Same ceiling the public execution seam already resolved. */
|
|
20
|
+
autoResumeLimit?: number;
|
|
19
21
|
/** Same seam as runComplianceAudit options — offline tracers only. */
|
|
20
22
|
summonAuditor?: AuditorSummon;
|
|
21
23
|
};
|
|
@@ -39,6 +41,7 @@ export function createPiDoctorAuditor(): (options: DoctorAuditOptions) => Promis
|
|
|
39
41
|
? {}
|
|
40
42
|
: { runDirectory: auditorRunDirectory(options.context) }),
|
|
41
43
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
44
|
+
...(options.autoResumeLimit === undefined ? {} : { autoResumeLimit: options.autoResumeLimit }),
|
|
42
45
|
...(options.summonAuditor === undefined ? {} : { summonAuditor: options.summonAuditor }),
|
|
43
46
|
});
|
|
44
47
|
};
|
package/src/doctor-role.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { RoleHost, HostContext
|
|
1
|
+
import type { RoleHost, HostContext } from "./host-contracts.ts";
|
|
2
|
+
import { registerFiledSubmissionTool } from "./filed-submission.ts";
|
|
3
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
2
4
|
import { ComplianceResponseRetentionError } from "./compliance-transport.ts";
|
|
3
5
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
|
|
4
|
-
import { DOCTOR_EVIDENCE_TOOL_NAME,
|
|
6
|
+
import { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
|
|
5
7
|
|
|
6
8
|
import { sitianReport } from "./sitian-facade.ts";
|
|
7
9
|
|
|
@@ -36,15 +38,18 @@ export function createDoctorRoleRuntime(pi: RoleHost, dependencies: DoctorRoleDe
|
|
|
36
38
|
let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
|
|
37
39
|
return { async activate() { const path = pi.getFlag(DOCTOR_CASE_FLAG.name); if (typeof path !== "string" || !path.trim()) throw new Error("Doctor requires --ak-doctor-case"); const soul = (await dependencies.loadSoul()).trim(); if (!soul) throw new Error("Doctor soul is empty"); const patient = await dependencies.loadCase(path); activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
|
|
38
40
|
if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "太医署证据", description: "分页读取留存的 Pi session 字节。", parameters: doctorEvidenceReadSchema, async execute(_id: string, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("太医署未激活"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
|
|
39
|
-
pi
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
registerFiledSubmissionTool(pi, roleSubmissionDeclaration("doctor"), {
|
|
42
|
+
readyError: () => (activation === undefined ? "太医署未激活" : undefined),
|
|
43
|
+
beforeAccept: async ({ toolCallId, parameters, ctx }) => {
|
|
44
|
+
if (activation === undefined) throw new Error("太医署未激活");
|
|
45
|
+
const active = activation;
|
|
46
|
+
const testimony = validateDoctorOutput(parameters, active.patient, active.store);
|
|
47
|
+
try {
|
|
48
|
+
appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity });
|
|
49
|
+
} catch (error) { host.failInfrastructure(error, ctx, toolCallId); }
|
|
50
|
+
return testimony;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
48
53
|
pi.on("before_agent_start", (event) => { if (!activation) throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; }); }
|
|
49
54
|
const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME]; const names = pi.getAllTools().map((tool) => tool.name); for (const name of required) if (names.filter((item) => item === name).length !== 1) throw new Error(`Doctor required tool collision or missing: ${name}`); pi.setActiveTools(required); const active = pi.getActiveTools?.() ?? required; if (active.length !== 2 || !required.every((name) => active.includes(name))) throw new Error("Doctor active tool narrowing failed"); } };
|
|
50
55
|
}
|
|
@@ -329,15 +329,16 @@ export function sessionFileFromSessionDirectory(sessionDirectory: string): strin
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
/**
|
|
332
|
-
* Mint one public-invocation scope id
|
|
332
|
+
* Mint one public-invocation scope id.
|
|
333
333
|
* Call once at the public-entry boundary — never inside the auto-resume loop.
|
|
334
|
-
*
|
|
334
|
+
* Auto-resume reuses it; an explicit new public call, including manual resume,
|
|
335
|
+
* mints another. The id lives on RoleTurnRequest / HostContext only.
|
|
336
|
+
* Receipt facts and detour rows both use this id, including calls with no
|
|
337
|
+
* engine mounted — a missing engine must not merge two public calls.
|
|
335
338
|
*/
|
|
336
|
-
export function mintEngineDetourInvocationScope(
|
|
339
|
+
export function mintEngineDetourInvocationScope(_input: {
|
|
337
340
|
readonly effectiveEngine?: string;
|
|
338
|
-
}): string
|
|
339
|
-
const engine = input.effectiveEngine?.trim();
|
|
340
|
-
if (engine === undefined || engine.length === 0) return undefined;
|
|
341
|
+
}): string {
|
|
341
342
|
return randomUUID();
|
|
342
343
|
}
|
|
343
344
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Middle external-host role-turn loop (#820 / ADR 0082).
|
|
3
|
-
* One copy: serial, abort merge/race,
|
|
3
|
+
* One copy: serial, abort merge/race, closeRound re-ask rounds,
|
|
4
4
|
* host-aborted, round-limit. Last hop = ExternalHostTurnDriver (four verbs).
|
|
5
5
|
*/
|
|
6
6
|
import type {
|
|
@@ -10,11 +10,10 @@ import type {
|
|
|
10
10
|
RoleTurnResult,
|
|
11
11
|
} from "./host-contracts.ts";
|
|
12
12
|
import type { PreparedRoleTurn } from "./prepared-role-turn.ts";
|
|
13
|
-
|
|
13
|
+
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.ts";
|
|
14
|
+
import { isOneShotWorkerReminderCode } from "./submission-errors.ts";
|
|
14
15
|
import { errorText } from "./unknown-value.ts";
|
|
15
16
|
|
|
16
|
-
export const EXTERNAL_ROLE_TURN_ROUND_LIMIT = 8 as const;
|
|
17
|
-
|
|
18
17
|
export type ExternalPreparedTurn = Pick<PreparedRoleTurn, "prompt" | "abortSignal" | "closeRound">;
|
|
19
18
|
|
|
20
19
|
export type ExternalHostRoundOutcome =
|
|
@@ -144,8 +143,14 @@ export async function driveExternalRoleTurnRounds(
|
|
|
144
143
|
let prompt = prepared.prompt;
|
|
145
144
|
const abortSignal = mergeRoleTurnAbortSignals(prepared.abortSignal, request.signal);
|
|
146
145
|
let stderr = "";
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
// #1132: the first round is the initial delivery. Each later counted re-ask
|
|
147
|
+
// spends the configured ceiling. The one commit reminder and the one prefix
|
|
148
|
+
// reminder are not counted re-asks (ADR 0066/0070).
|
|
149
|
+
const countedReaskLimit = deliveryLimitFromConfig(request.deliveryRequestLimit);
|
|
150
|
+
const exemptReminders = new Set<string>();
|
|
151
|
+
let countedReasks = 0;
|
|
152
|
+
|
|
153
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
149
154
|
if (abortSignal?.aborted) return settleHostAborted(prepared, driver.currentSessionId());
|
|
150
155
|
|
|
151
156
|
let round: ExternalHostRoundOutcome;
|
|
@@ -168,15 +173,22 @@ export async function driveExternalRoleTurnRounds(
|
|
|
168
173
|
return { code: 0, stderr, timedOut: false };
|
|
169
174
|
}
|
|
170
175
|
if ("failure" in closure) return asFailure(closure.failure, stderr);
|
|
176
|
+
const reminderCode = closure.retry.code;
|
|
177
|
+
const exempt = isOneShotWorkerReminderCode(reminderCode) && !exemptReminders.has(reminderCode);
|
|
178
|
+
if (exempt) exemptReminders.add(reminderCode);
|
|
179
|
+
else {
|
|
180
|
+
countedReasks += 1;
|
|
181
|
+
if (countedReasks > countedReaskLimit) {
|
|
182
|
+
return asFailure({
|
|
183
|
+
cause: "output",
|
|
184
|
+
identity: { name: driver.roundLimitName, code: "round-retry-limit" },
|
|
185
|
+
...(driver.currentSessionId() === undefined ? {} : { details: { sessionId: driver.currentSessionId() } }),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
171
189
|
prompt = closure.retry.message;
|
|
172
190
|
driver.afterRetry?.();
|
|
173
191
|
}
|
|
174
|
-
|
|
175
|
-
return asFailure({
|
|
176
|
-
cause: "output",
|
|
177
|
-
identity: { name: driver.roundLimitName, code: "round-retry-limit" },
|
|
178
|
-
...(driver.currentSessionId() === undefined ? {} : { details: { sessionId: driver.currentSessionId() } }),
|
|
179
|
-
});
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
export function createSerializedRoleTurnHost(
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HostContext, HostToolResult, RoleHost } from "./host-contracts.ts";
|
|
2
|
+
import type { RoleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
3
|
+
|
|
4
|
+
export type FiledSubmissionBeforeAccept = (input: {
|
|
5
|
+
readonly toolCallId: string;
|
|
6
|
+
readonly parameters: unknown;
|
|
7
|
+
readonly signal: AbortSignal | undefined;
|
|
8
|
+
readonly ctx: HostContext;
|
|
9
|
+
}) => Promise<unknown>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One host-neutral submission execute for every public seat.
|
|
13
|
+
* Records nothing itself — the ledger seam already appends the original payload.
|
|
14
|
+
* Ends this tool call. Does not summon an auditor and does not end the role run.
|
|
15
|
+
* Shape is not an admission gate (ADR 0055). Seat reminders stay on beforeAccept.
|
|
16
|
+
*/
|
|
17
|
+
export function registerFiledSubmissionTool(
|
|
18
|
+
roleHost: RoleHost,
|
|
19
|
+
tool: RoleSubmissionDeclaration,
|
|
20
|
+
options: {
|
|
21
|
+
readonly readyError: () => string | undefined;
|
|
22
|
+
readonly beforeAccept?: FiledSubmissionBeforeAccept;
|
|
23
|
+
},
|
|
24
|
+
): void {
|
|
25
|
+
roleHost.registerTool({
|
|
26
|
+
name: tool.name,
|
|
27
|
+
label: tool.label,
|
|
28
|
+
description: tool.description,
|
|
29
|
+
...(tool.promptSnippet === undefined ? {} : { promptSnippet: tool.promptSnippet }),
|
|
30
|
+
parameters: tool.parameters as never,
|
|
31
|
+
async execute(toolCallId, parameters, signal, _onUpdate, ctx): Promise<HostToolResult<unknown>> {
|
|
32
|
+
const blocked = options.readyError();
|
|
33
|
+
if (blocked !== undefined) throw new Error(blocked);
|
|
34
|
+
const projected = options.beforeAccept === undefined
|
|
35
|
+
? undefined
|
|
36
|
+
: await options.beforeAccept({ toolCallId, parameters, signal, ctx });
|
|
37
|
+
return {
|
|
38
|
+
content: [],
|
|
39
|
+
details: projected === undefined ? parameters : projected,
|
|
40
|
+
terminate: true as const,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|