@akagilnc/pi-workflow-roles 0.1.3356 → 0.1.3384
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 -2
- package/README.zh-CN.md +3 -3
- package/dist/auditor-dossier-tool.js +51 -2
- package/dist/gatekeeper-role.js +18 -62
- package/dist/grok/production-host.js +142 -248
- package/dist/public-cli/main.js +269 -227
- package/package.json +1 -1
- package/src/analyst-gate-cycles-read.ts +99 -26
- package/src/analyst-metric-families/gate-cycles.ts +7 -1
- package/src/auditor-dossier-tool.ts +72 -2
- package/src/gatekeeper-role.ts +37 -74
- package/src/grok/role-envelope.ts +6 -7
- package/src/host-contracts.ts +1 -2
- package/src/judge-role.ts +1 -1
- package/src/pi/role-turn-host.ts +2 -7
- package/src/public-cli/settlement.ts +16 -13
- package/src/public-cli/terminal.ts +21 -18
- package/src/role-runtime.ts +4 -143
- package/src/worker-role.ts +36 -20
- package/src/worker-submission-gates.ts +3 -2
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ Seat and Gate-officer configuration:
|
|
|
47
47
|
```bash
|
|
48
48
|
ak-role config set judge <provider/model[:thinking]>
|
|
49
49
|
ak-role config set navigator <provider/model[:thinking]>
|
|
50
|
-
# Gate officers (
|
|
50
|
+
# Gate officers (direct summons on DONE submissions; province remains independently callable)
|
|
51
51
|
ak-role config set gatekeeper <provider/model[:thinking]>
|
|
52
52
|
ak-role config set inspector <provider/model[:thinking]>
|
|
53
53
|
ak-role config set notary <provider/model[:thinking]>
|
|
@@ -67,7 +67,7 @@ For Gate officers (`gatekeeper` / `inspector` / `notary`) resolution is officer
|
|
|
67
67
|
|
|
68
68
|
Receipts are typed, so callers compose roles without parsing prose; ordering and stopping stay caller-owned ([ADR 0010](docs/adr/0010-callers-own-role-composition-and-repetition.md)). Programmatic consumers derive contracts from the exported schemas in `src/package-contracts/`, not from this guide.
|
|
69
69
|
|
|
70
|
-
Gate submission gate: on
|
|
70
|
+
Gate submission gate: on DONE-side submissions (`completed` / `partially_completed`) the package summons the subject officer directly (`inspector` for worker completion; `notary` for judge draft / countersign verdict) — it does not spawn a Gatekeeper child to choose the seat; bounce means rewrite-and-resubmit in that same session, not role failure; `planned` / `refused` / `unfinished` skip the officer summons and settle; `ak-role gatekeeper` remains available for independent province dispatch/pass; read gate history from the typed gate section of the receipt, never from session prose. Pointers: [ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md), [ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md), [ADR 0079](docs/adr/0079-direct-officer-summons-ticket-memory-pointer-input.md). A labor-engine detour that fails to spawn, exits nonzero, or produces no usable output stops the run through the existing infrastructure-failure path with the original cause visible ([ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md)).
|
|
71
71
|
|
|
72
72
|
## Call the roles
|
|
73
73
|
|
package/README.zh-CN.md
CHANGED
|
@@ -47,7 +47,7 @@ ak-role judge --attach ./plan.md "Review this plan." > result.txt
|
|
|
47
47
|
```bash
|
|
48
48
|
ak-role config set judge <provider/model[:thinking]>
|
|
49
49
|
ak-role config set navigator <provider/model[:thinking]>
|
|
50
|
-
#
|
|
50
|
+
# 门下省官席(DONE 交卷直接传召察院/符宝郎;门下省仍可独立直调;给事中票庭由调用者直召,见「调用百官」)
|
|
51
51
|
ak-role config set gatekeeper <provider/model[:thinking]>
|
|
52
52
|
ak-role config set inspector <provider/model[:thinking]>
|
|
53
53
|
ak-role config set notary <provider/model[:thinking]>
|
|
@@ -67,7 +67,7 @@ ak-role config set-auto-resume-limit 3
|
|
|
67
67
|
|
|
68
68
|
回执是 typed 的,调用者不必解析散文即可组合角色;顺序与停止归调用者([ADR 0010](docs/adr/0010-callers-own-role-composition-and-repetition.md))。编程消费者从 `src/package-contracts/` 导出推导契约,不从本文。
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
门下省交卷闸:DONE 侧交卷(`completed`/`partially_completed`)时包按受审物直接传召官(将作监/修内司→`inspector`;大理寺判牒/给事中署章→`notary`),不再起门下省子 session 选席;封驳=当场重写重交,不是角色失败;`planned`/`refused`/`unfinished` 不传召官、直接结算;`ak-role gatekeeper` 仍可独立 dispatch/pass;闸史读回执 typed gate 段,勿刮 session 散文。指针:[ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md)、[ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md)、[ADR 0079](docs/adr/0079-direct-officer-summons-ticket-memory-pointer-input.md)。劳务引擎绕行失败沿既有基础设施故障路径停止、真因可见([ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md))。
|
|
71
71
|
|
|
72
72
|
## 调用百官
|
|
73
73
|
|
|
@@ -135,7 +135,7 @@ ak-role resume <runId> "<裁定>"
|
|
|
135
135
|
| **御史台** | reviewer | **察举百弊,风闻奏事。** 置身事外审视成果;Standards/Spec 两条取证腿由 runtime 代跑,本席收腿报告出薄回执与 amendment。弹章须指明所劾之处,言不为狱——不负坐实义务,坐实归大理寺。 |
|
|
136
136
|
| **大理寺** | judge | **审理定谳。** 承接各方意见与材料,依照既定规则逐项判断,辨明是非曲直。可以准行、退回或请示更高决定,但自身不参与建设与修改。 |
|
|
137
137
|
| **审刑院** | judge-auditor/doctor-auditor(无 CLI,共享内部接缝;御史台侧闸已退役) | **复核成案。** 不重新争论事情本身,而是检查整个办理过程是否合乎规矩。关注是否有人越过职责、是否遗漏必要步骤、是否以错误方式得出正确结果。直属陛下,不入门下省编制。 |
|
|
138
|
-
| **门下省** | gatekeeper
|
|
138
|
+
| **门下省** | gatekeeper(交卷闸不再自动出席;可 `ak-role gatekeeper` 独立直调) | **审署诏敕与质量保证的省。** 交卷闸按受审物直接传召察院/符宝郎,本省不介入选席;调用者仍可独立传召本省作 dispatch/pass;给事中票庭由调用者开工前传召;左拾遗由调用者合并前传召;省内政,不是外层编排器。规范见 [ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md)、[ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md)、[ADR 0074](docs/adr/0074-gate-province-reorg-jishizhong-chaiyuan-split.md)、[ADR 0079](docs/adr/0079-direct-officer-summons-ticket-memory-pointer-input.md)。 |
|
|
139
139
|
| **给事中** | countersign(无交卷闸派发;开工前由调用者传召) | **票庭审读五问。** 制度符合/授权真实(以起居录为据)/文书符意/退回重议/发布资格;读码取证是本职,实现细节不上票面。票庭流水线在本席 turn 前跑起居郎工序(调用者无感);交卷闸出席符宝郎。署=放行开工,封驳=退票重议,上呈=陛下裁决。规范见 [ADR 0074](docs/adr/0074-gate-province-reorg-jishizhong-chaiyuan-split.md)、[ADR 0075](docs/adr/0075-ticket-provenance-diarist-pipeline.md)。 |
|
|
140
140
|
| **左拾遗** | gleaner-left(无交卷闸派发;合并前由调用者传召) | **合并前无锚定风闻。** 对全幅合并候选作冷眼评审;只上弹章、不封驳不裁决。规范见 [ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md) 修正案。 |
|
|
141
141
|
| **察院** | inspector | **事后察举:复杂度与测试质量两轴。** 受审物是将作监/修内司完成侧交卷;封驳=当场打回重写,不是本局失败。可被门下省派发,也可 `ak-role inspector` 单独调。原给事中,ADR 0074 分立。 |
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
1
2
|
import { dirname, join, resolve } from "node:path";
|
|
2
3
|
import { Type } from "typebox";
|
|
3
4
|
export const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier";
|
|
5
|
+
/** Run-relative leaf written before gate officer summons (#632 Grok header-only session). */
|
|
6
|
+
export const GATE_SUBMISSION_CANDIDATE_FILE = "gate-submission-candidate.json";
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
4
10
|
/** Resolve the exact run binding already carried by the parent record session. */
|
|
5
11
|
export function auditorRunDirectory(context) {
|
|
6
12
|
const sessionFile = context.sessionManager?.getSessionFile?.();
|
|
@@ -9,8 +15,46 @@ export function auditorRunDirectory(context) {
|
|
|
9
15
|
// Sole layout: <run>/session/session.jsonl → climb two levels to the run directory.
|
|
10
16
|
return resolve(dirname(dirname(sessionFile)));
|
|
11
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Latest assistant toolCall leaf on parent session books (memory or disk-backed).
|
|
20
|
+
* Grok books the candidate in memory only (#617 DK-4); Pi books the same shape on JSONL.
|
|
21
|
+
*/
|
|
22
|
+
export function readLatestToolCallLeaf(context) {
|
|
23
|
+
const entries = [...(context.sessionManager?.getEntries?.() ?? [])];
|
|
24
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
25
|
+
const entry = entries[i];
|
|
26
|
+
if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message))
|
|
27
|
+
continue;
|
|
28
|
+
const message = entry.message;
|
|
29
|
+
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
30
|
+
continue;
|
|
31
|
+
for (const part of message.content) {
|
|
32
|
+
if (isRecord(part) && part.type === "toolCall") {
|
|
33
|
+
return entry;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
export function gateSubmissionCandidatePath(runDirectory) {
|
|
40
|
+
return join(runDirectory, "artifacts", GATE_SUBMISSION_CANDIDATE_FILE);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Persist the in-flight tool-call leaf as a run-directory artifact so pointer-only
|
|
44
|
+
* officer summons resolve on hosts whose session.jsonl is header-only (#632 / DK-4).
|
|
45
|
+
* Returns the written path, or undefined when no toolCall leaf is on the books.
|
|
46
|
+
*/
|
|
47
|
+
export function persistGateSubmissionCandidate(runDirectory, context) {
|
|
48
|
+
const leaf = readLatestToolCallLeaf(context);
|
|
49
|
+
if (leaf === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
const path = gateSubmissionCandidatePath(runDirectory);
|
|
52
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
53
|
+
writeFileSync(path, `${JSON.stringify(leaf)}\n`, "utf8");
|
|
54
|
+
return path;
|
|
55
|
+
}
|
|
12
56
|
/** The one shared, run-bound dossier locator exposed to every auditor seat. */
|
|
13
|
-
export function createAuditorDossierTool(runDirectory) {
|
|
57
|
+
export function createAuditorDossierTool(runDirectory, options) {
|
|
14
58
|
return {
|
|
15
59
|
name: AUDITOR_DOSSIER_TOOL_NAME,
|
|
16
60
|
description: "定位本审计席绑定的 run 卷宗及其证据入口。",
|
|
@@ -22,12 +66,17 @@ export function createAuditorDossierTool(runDirectory) {
|
|
|
22
66
|
details: undefined,
|
|
23
67
|
};
|
|
24
68
|
}
|
|
69
|
+
const sessionPath = join(runDirectory, "session", "session.jsonl");
|
|
70
|
+
const submissionCandidate = options?.submissionCandidate;
|
|
71
|
+
// Prefer the persisted gate leaf when present: Grok session.jsonl is header-only
|
|
72
|
+
// (#617 DK-4) and is not a candidate source after material deletion (#632).
|
|
25
73
|
const details = {
|
|
26
74
|
runDirectory,
|
|
27
75
|
admittedRequest: join(runDirectory, "admitted-request.json"),
|
|
28
|
-
parentSessionCandidate:
|
|
76
|
+
parentSessionCandidate: submissionCandidate ?? sessionPath,
|
|
29
77
|
attachments: join(runDirectory, "attachments"),
|
|
30
78
|
artifacts: join(runDirectory, "artifacts"),
|
|
79
|
+
...(submissionCandidate === undefined ? {} : { submissionCandidate }),
|
|
31
80
|
};
|
|
32
81
|
return {
|
|
33
82
|
content: [{ type: "text", text: JSON.stringify(details) }],
|
package/dist/gatekeeper-role.js
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { auditorRunDirectory, createAuditorDossierTool, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
|
|
2
3
|
import { executeAuditorChild } from "./evidence-child-executor.js";
|
|
3
4
|
import { openToolObject } from "./open-tool-schema.js";
|
|
4
5
|
import { loadGatekeeperSessionMaterials } from "./session-opening-materials.js";
|
|
5
6
|
import { GatekeeperDecisionError } from "./submission-errors.js";
|
|
6
7
|
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
|
|
7
|
-
import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema,
|
|
8
|
+
import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
|
|
8
9
|
export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
|
|
9
10
|
export const NOTARY_OUTPUT_TOOL = "ak_notary_output";
|
|
10
|
-
const SUBJECT_TOOL = "ak_gatekeeper_subject";
|
|
11
11
|
function gateSeatLabel(stage) {
|
|
12
|
-
|
|
13
|
-
case "gatekeeper":
|
|
14
|
-
return "门下省";
|
|
15
|
-
case "inspector":
|
|
16
|
-
return "察院";
|
|
17
|
-
case "notary":
|
|
18
|
-
return "符宝郎";
|
|
19
|
-
}
|
|
12
|
+
return stage === "inspector" ? "察院" : "符宝郎";
|
|
20
13
|
}
|
|
21
14
|
export { GatekeeperDecisionError } from "./submission-errors.js";
|
|
22
15
|
export class GatekeeperEscalationError extends Error {
|
|
@@ -49,14 +42,6 @@ export const GATEKEEPER_TOOL_SPEC = {
|
|
|
49
42
|
function result(content, details) {
|
|
50
43
|
return { content: [{ type: "text", text: content }], details };
|
|
51
44
|
}
|
|
52
|
-
function subjectTool(subject) {
|
|
53
|
-
return {
|
|
54
|
-
name: SUBJECT_TOOL,
|
|
55
|
-
description: "读取已受理卷宗;只供取阅,不评判不改动。",
|
|
56
|
-
parameters: Type.Object({}, { additionalProperties: false }),
|
|
57
|
-
async execute() { return result(JSON.stringify(subject), subject); },
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
45
|
/** Shared officer decision tool — open transport; projection owns legality. */
|
|
61
46
|
export function createOfficerDecisionTool(name) {
|
|
62
47
|
return {
|
|
@@ -110,7 +95,7 @@ function noUsableReleaseFailure(stage, decision) {
|
|
|
110
95
|
return {
|
|
111
96
|
status: "transport_failure",
|
|
112
97
|
stage,
|
|
113
|
-
reason:
|
|
98
|
+
reason: "decision 无显式 pass/bounce/escalate",
|
|
114
99
|
submission: retainedSubmission(decision),
|
|
115
100
|
};
|
|
116
101
|
}
|
|
@@ -119,19 +104,6 @@ function readRecord(value) {
|
|
|
119
104
|
return undefined;
|
|
120
105
|
return value;
|
|
121
106
|
}
|
|
122
|
-
function projectProvinceDecision(decision) {
|
|
123
|
-
// Lawful dispatch/pass discriminant is owned by package-contracts; province
|
|
124
|
-
// wraps undefined into transport_failure while retaining the submission.
|
|
125
|
-
const projected = projectLawfulGatekeeperOutput(decision);
|
|
126
|
-
if (projected === undefined)
|
|
127
|
-
return noUsableReleaseFailure("gatekeeper", decision);
|
|
128
|
-
if (projected.status === "dispatch") {
|
|
129
|
-
return { status: "dispatch", officer: projected.officer };
|
|
130
|
-
}
|
|
131
|
-
// Lawful non-dispatch release — province may pass without dispatching an officer
|
|
132
|
-
// (ADR 0074 gate-non-mandatory; gate-output-guide pass = 正常放行; #597).
|
|
133
|
-
return { status: "pass", findings: projected.findings ?? [] };
|
|
134
|
-
}
|
|
135
107
|
function projectOfficerDecision(officer, decision) {
|
|
136
108
|
const record = readRecord(decision);
|
|
137
109
|
if (record === undefined)
|
|
@@ -163,45 +135,29 @@ function projectOfficerDecision(officer, decision) {
|
|
|
163
135
|
}
|
|
164
136
|
return noUsableReleaseFailure(officer, decision);
|
|
165
137
|
}
|
|
138
|
+
/** Submission-gate summons: subject kind determines the officer without a province child. */
|
|
166
139
|
export async function runGatekeeper(options) {
|
|
167
140
|
const loadSoul = options.loadSoul ?? defaultLoadSoul;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
prompt: "卷宗已受理。",
|
|
177
|
-
tool: createGatekeeperOutputTool(),
|
|
178
|
-
dossierTool: subjectTool(options.subject),
|
|
179
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
180
|
-
...(options.runDirectory === undefined ? {} : { runDirectory: options.runDirectory }),
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
return { status: "transport_failure", stage: "gatekeeper", reason: failureReason(error) };
|
|
185
|
-
}
|
|
186
|
-
if (provinceRun.noReceiptLifecycle !== undefined) {
|
|
187
|
-
return { status: "no_receipt", stage: "gatekeeper", reason: `${gateSeatLabel("gatekeeper")}未产生已接受回执即散局`, facts: provinceRun.noReceiptLifecycle };
|
|
188
|
-
}
|
|
189
|
-
const province = projectProvinceDecision(provinceRun.decision);
|
|
190
|
-
if (province.status !== "dispatch")
|
|
191
|
-
return province;
|
|
192
|
-
const officer = province.officer;
|
|
141
|
+
const officer = options.subject.kind === "worker_completion" ? "inspector" : "notary";
|
|
142
|
+
// Resolve once so dossier tool and child session share the same run binding (#632).
|
|
143
|
+
const runDirectory = options.runDirectory ?? auditorRunDirectory(options.context);
|
|
144
|
+
// Pointer-only summons need a resolvable leaf: Grok session.jsonl is header-only
|
|
145
|
+
// (#617 DK-4); write the in-memory tool-call candidate as a run artifact first (#632).
|
|
146
|
+
const submissionCandidate = runDirectory === undefined
|
|
147
|
+
? undefined
|
|
148
|
+
: persistGateSubmissionCandidate(runDirectory, options.context);
|
|
193
149
|
try {
|
|
194
|
-
const roleLabel = officer === "inspector" ? "Inspector" : "Notary";
|
|
195
150
|
const officerRun = await executeAuditorChild({
|
|
196
151
|
context: options.context,
|
|
197
|
-
roleLabel,
|
|
152
|
+
roleLabel: officer === "inspector" ? "Inspector" : "Notary",
|
|
198
153
|
gateSeat: officer,
|
|
199
154
|
systemPrompt: await loadSoul(officer),
|
|
200
155
|
prompt: "卷宗已受理。",
|
|
201
156
|
tool: createOfficerDecisionTool(officer === "inspector" ? INSPECTOR_OUTPUT_TOOL : NOTARY_OUTPUT_TOOL),
|
|
202
|
-
|
|
157
|
+
// Same locator as 审刑院 — run directory + path identifiers only; no receipt body (#632).
|
|
158
|
+
dossierTool: createAuditorDossierTool(runDirectory, submissionCandidate === undefined ? undefined : { submissionCandidate }),
|
|
203
159
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
204
|
-
...(
|
|
160
|
+
...(runDirectory === undefined ? {} : { runDirectory }),
|
|
205
161
|
});
|
|
206
162
|
if (officerRun.noReceiptLifecycle !== undefined) {
|
|
207
163
|
return { status: "no_receipt", stage: officer, reason: `${gateSeatLabel(officer)}未产生已接受回执即散局`, facts: officerRun.noReceiptLifecycle };
|
|
@@ -223,7 +179,7 @@ export async function requireGatekeeperPass(options) {
|
|
|
223
179
|
return;
|
|
224
180
|
if (gatekeeper.status === "transport_failure") {
|
|
225
181
|
// Typed stage/reason/submission ride failInfrastructure → durable tool_result (#475).
|
|
226
|
-
const error = new Error(
|
|
182
|
+
const error = new Error(`交卷闸 transport_failure(${gatekeeper.stage}):${gatekeeper.reason}`);
|
|
227
183
|
error.stage = gatekeeper.stage;
|
|
228
184
|
error.reason = gatekeeper.reason;
|
|
229
185
|
if (gatekeeper.submission !== undefined)
|