@akagilnc/pi-workflow-roles 0.1.3377 → 0.1.3391

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 CHANGED
@@ -11,6 +11,8 @@ pi install npm:@akagilnc/pi-workflow-roles
11
11
  export PATH="$HOME/.pi/agent/npm/node_modules/.bin:$PATH"
12
12
  ```
13
13
 
14
+ Four court seats (`coder` / `fixer` / `judge` / `countersign`) resolve an unbound ticket from the instruction via a Hermes labor-engine detour before the turn runs. Keep a working `hermes` executable on `PATH` for those seats; spawn failure, nonzero exit, or unusable 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)). Pi-only installs that never dispatch those seats do not need Hermes.
15
+
14
16
  Update with `pi update npm:@akagilnc/pi-workflow-roles`—never a second global `npm install -g`. Inspect with `ak-role roles` and `ak-role help <role>`; seat and Gate-officer configuration lives under Reading results below.
15
17
 
16
18
  ### Test channel (`next`)
@@ -75,7 +77,7 @@ The examples below are usage sketches; option identity, aliases, requiredness, a
75
77
 
76
78
  ```bash
77
79
  # countersign — ticket-court review before work starts; resume continues the exact session
78
- ak-role countersign --attach ./ticket.md "裁:本票是否足以开工。"
80
+ ak-role countersign --attach ./ticket.md "裁:本票 #582 是否足以开工。"
79
81
 
80
82
  # gleaner-left — unanchored pre-merge memorials; resume continues the exact session; --base required; instruction may be empty; callers must not pass directional instruction
81
83
  ak-role gleaner-left --base main
@@ -102,9 +104,8 @@ ak-role doctor --issue 115 "Diagnose this retained case."
102
104
  # merger — resolve one merge already in conflict (start it with Git's ort first)
103
105
  ak-role merger --project /path/to/worktree "Reconcile the active merge."
104
106
 
105
- # notary — document-fidelity check on one retained source run; optional --ticket for court diary
107
+ # notary — document-fidelity check on one retained source run; ticket key inherited from source-run admitted form
106
108
  ak-role notary --source-run <runId@role|path>
107
- ak-role notary --source-run <runId@role|path> --ticket 582
108
109
 
109
110
  # inspector — direct complexity and test-quality check
110
111
  ak-role inspector --attach ./change.patch "Review this material."
@@ -115,8 +116,8 @@ ak-role gatekeeper --attach ./submission.json "审:这批材料该谁审?"
115
116
  # navigator — direct route advice (ordered next-role candidates); automatic attendance unchanged
116
117
  ak-role navigator "刚完成 coder apply 收敛,下一步?"
117
118
 
118
- # countersign — ticket-court five questions; optional --ticket (diarist pipeline refreshes court diary first)
119
- ak-role countersign --ticket 582 --attach ./ticket.md "裁:本票是否足以开工。"
119
+ # countersign — ticket-court five questions; ticket recognition via instruction
120
+ ak-role countersign --attach ./ticket.md "裁:本票 #582 是否足以开工。"
120
121
 
121
122
  # analyst — deterministic metrics; bare call = whole book
122
123
  ak-role analyst
package/README.zh-CN.md CHANGED
@@ -96,9 +96,8 @@ ak-role doctor --issue 115 "Diagnose this retained case."
96
96
  # 校书郎——调和已在冲突的 merge(先用 Git ort 起动)
97
97
  ak-role merger --project /path/to/worktree "Reconcile the active merge."
98
98
 
99
- # 符宝郎——文书核验一份留存 source run;可选 --ticket 调起居录
99
+ # 符宝郎——文书核验一份留存 source run;票号从 source-run admitted form 继承
100
100
  ak-role notary --source-run <runId@role|path>
101
- ak-role notary --source-run <runId@role|path> --ticket 582
102
101
 
103
102
  # 察院——直调复杂度与测试质量两轴
104
103
  ak-role inspector --attach ./change.patch "Review this material."
@@ -109,8 +108,8 @@ ak-role gatekeeper --attach ./submission.json "审:这批材料该谁审?"
109
108
  # 游奕使——直调路线建议(有序的下一步角色候选);自动出席不变
110
109
  ak-role navigator "刚完成 coder apply 收敛,下一步?"
111
110
 
112
- # 给事中——票庭五问;可选 --ticket(起居郎流水线前序工序按票刷新起居录)
113
- ak-role countersign --ticket 582 --attach ./ticket.md "裁:本票是否足以开工。"
111
+ # 给事中——票庭五问;票号经由 instruction 识别
112
+ ak-role countersign --attach ./ticket.md "裁:本票 #582 是否足以开工。"
114
113
 
115
114
  # 左拾遗——合并前无锚定风闻;可 resume 续同一 session;--base 必填;instruction 可空;调用者不得传方向性 instruction
116
115
  ak-role gleaner-left --base main
@@ -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: join(runDirectory, "session", "session.jsonl"),
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) }],
@@ -1,4 +1,5 @@
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";
@@ -7,7 +8,6 @@ import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
7
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
12
  return stage === "inspector" ? "察院" : "符宝郎";
13
13
  }
@@ -42,14 +42,6 @@ export const GATEKEEPER_TOOL_SPEC = {
42
42
  function result(content, details) {
43
43
  return { content: [{ type: "text", text: content }], details };
44
44
  }
45
- function subjectTool(subject) {
46
- return {
47
- name: SUBJECT_TOOL,
48
- description: "读取已受理卷宗;只供取阅,不评判不改动。",
49
- parameters: Type.Object({}, { additionalProperties: false }),
50
- async execute() { return result(JSON.stringify(subject), subject); },
51
- };
52
- }
53
45
  /** Shared officer decision tool — open transport; projection owns legality. */
54
46
  export function createOfficerDecisionTool(name) {
55
47
  return {
@@ -147,6 +139,13 @@ function projectOfficerDecision(officer, decision) {
147
139
  export async function runGatekeeper(options) {
148
140
  const loadSoul = options.loadSoul ?? defaultLoadSoul;
149
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);
150
149
  try {
151
150
  const officerRun = await executeAuditorChild({
152
151
  context: options.context,
@@ -155,9 +154,10 @@ export async function runGatekeeper(options) {
155
154
  systemPrompt: await loadSoul(officer),
156
155
  prompt: "卷宗已受理。",
157
156
  tool: createOfficerDecisionTool(officer === "inspector" ? INSPECTOR_OUTPUT_TOOL : NOTARY_OUTPUT_TOOL),
158
- dossierTool: subjectTool(options.subject),
157
+ // Same locator as 审刑院 — run directory + path identifiers only; no receipt body (#632).
158
+ dossierTool: createAuditorDossierTool(runDirectory, submissionCandidate === undefined ? undefined : { submissionCandidate }),
159
159
  ...(options.signal === undefined ? {} : { signal: options.signal }),
160
- ...(options.runDirectory === undefined ? {} : { runDirectory: options.runDirectory }),
160
+ ...(runDirectory === undefined ? {} : { runDirectory }),
161
161
  });
162
162
  if (officerRun.noReceiptLifecycle !== undefined) {
163
163
  return { status: "no_receipt", stage: officer, reason: `${gateSeatLabel(officer)}未产生已接受回执即散局`, facts: officerRun.noReceiptLifecycle };