@akagilnc/pi-workflow-roles 0.1.3565 → 0.1.3621

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.
Files changed (142) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +18477 -8099
  4. package/dist/analyst-gate-cycles-read.js +387 -0
  5. package/dist/archivist-record-entry.js +22 -1
  6. package/dist/audit-escalation.js +6 -0
  7. package/dist/auditor-soul.js +74 -0
  8. package/dist/collector-config.js +86 -0
  9. package/dist/collector-evidence.js +316 -0
  10. package/dist/collector-github.js +527 -0
  11. package/dist/collector-ledger.js +853 -0
  12. package/dist/collector-tool-schemas.js +33 -0
  13. package/dist/compliance-transport.js +104 -52
  14. package/dist/diarist-contracts.js +72 -0
  15. package/dist/diarist-mechanical.js +411 -0
  16. package/dist/diarist-ticket-resolution.js +177 -0
  17. package/dist/diarist.js +311 -0
  18. package/dist/doctor-auditor.js +25 -0
  19. package/dist/doctor-contracts.js +2 -0
  20. package/dist/doctor-evidence.js +142 -0
  21. package/dist/gatekeeper-role.js +104 -81
  22. package/dist/host-transition-prior-native.js +78 -0
  23. package/dist/institutional-resolution.js +1 -95
  24. package/dist/judge-auditor.js +26 -0
  25. package/dist/ledger-session-read.js +227 -0
  26. package/dist/merger-git-state.js +78 -0
  27. package/dist/navigator-attendance.js +15 -7
  28. package/dist/navigator-public-session.js +170 -0
  29. package/dist/navigator-session-contracts.js +36 -40
  30. package/dist/notary-source-run.js +122 -0
  31. package/dist/package-contracts/auditor-output.js +66 -0
  32. package/dist/package-contracts/evidence-child-output.js +34 -0
  33. package/dist/package-contracts/judge-output.js +2 -0
  34. package/dist/package-contracts/terminating-tools.js +44 -3
  35. package/dist/package-resources/method-skill.js +254 -0
  36. package/dist/packaged-role-registry.js +56 -0
  37. package/dist/pi/durable-principal.js +61 -0
  38. package/dist/pi/in-process-session.js +25 -6
  39. package/dist/pi/known-failure.js +52 -0
  40. package/dist/pi/role-turn-host.js +430 -0
  41. package/dist/public-cli/auto-resume.js +414 -0
  42. package/dist/public-cli/cli-errors.js +8 -0
  43. package/dist/public-cli/cli-io.js +1 -0
  44. package/dist/public-cli/command-renderer.js +5 -0
  45. package/dist/public-cli/doctor-run.js +84 -0
  46. package/dist/public-cli/inspector-run.js +136 -0
  47. package/dist/public-cli/instruction-seat-run.js +256 -0
  48. package/dist/public-cli/invocation.js +2225 -0
  49. package/dist/public-cli/judge-run.js +118 -0
  50. package/dist/public-cli/load-production-acp-host.js +40 -0
  51. package/dist/public-cli/main.js +1511 -1124
  52. package/dist/public-cli/notary-run.js +161 -0
  53. package/dist/public-cli/option-definitions.js +1192 -0
  54. package/dist/public-cli/post-admission.js +667 -0
  55. package/dist/public-cli/public-run-credentials.js +49 -0
  56. package/dist/public-cli/registry.js +8 -2
  57. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  58. package/dist/public-cli/run-lifecycle.js +1344 -0
  59. package/dist/public-cli/seat-ticket-binding.js +113 -0
  60. package/dist/public-cli/settlement.js +3441 -0
  61. package/dist/public-cli/terminal.js +135 -0
  62. package/dist/public-cli/turn-request.js +25 -0
  63. package/dist/public-role-summons.js +301 -0
  64. package/dist/receipt-delivery-policy.js +10 -0
  65. package/dist/reviewer-child-executor.js +80 -8
  66. package/dist/reviewer-execution-ledger.js +2 -1
  67. package/dist/run-terminal-artifacts.js +195 -0
  68. package/dist/run-ticket-number.js +40 -0
  69. package/dist/session-assistant-usage.js +107 -0
  70. package/dist/shape-unreadable-failure.js +34 -0
  71. package/dist/submission-errors.js +3 -0
  72. package/dist/submission-ledger.js +388 -0
  73. package/dist/ticket-provenance-contracts.js +115 -0
  74. package/dist/ticket-provenance.js +340 -0
  75. package/extensions/role-runtime.ts +8 -0
  76. package/package.json +1 -1
  77. package/resources/diarist-collect.md +6 -10
  78. package/scripts/build-package.mjs +2 -1
  79. package/souls/diarist.md +9 -0
  80. package/souls/doctor-auditor.md +1 -0
  81. package/souls/evidence-child.md +1 -0
  82. package/src/acp-host/production-host.ts +4 -0
  83. package/src/acp-host/role-envelope.ts +4 -3
  84. package/src/acp-host/role-turn-host.ts +9 -1
  85. package/src/analyst-gate-cycles-read.ts +37 -2
  86. package/src/archivist-record-entry.ts +45 -1
  87. package/src/audit-escalation.ts +17 -0
  88. package/src/auditor-role.ts +22 -0
  89. package/src/auditor-soul.ts +42 -0
  90. package/src/compliance-transport.ts +177 -66
  91. package/src/diarist-contracts.ts +88 -0
  92. package/src/diarist-role.ts +60 -0
  93. package/src/diarist.ts +253 -179
  94. package/src/doctor-auditor.ts +10 -14
  95. package/src/doctor-contracts.ts +1 -0
  96. package/src/doctor-role.ts +2 -2
  97. package/src/evidence-child-role.ts +22 -0
  98. package/src/gatekeeper-pass-envelope.ts +109 -0
  99. package/src/gatekeeper-role.ts +145 -104
  100. package/src/host-contracts.ts +15 -2
  101. package/src/institutional-resolution.ts +8 -163
  102. package/src/judge-auditor.ts +10 -15
  103. package/src/judge-role.ts +8 -0
  104. package/src/navigator-attendance.ts +25 -9
  105. package/src/navigator-public-session.ts +217 -0
  106. package/src/navigator-session-contracts.ts +61 -54
  107. package/src/notary-source-run.ts +3 -1
  108. package/src/package-contracts/auditor-output.ts +82 -0
  109. package/src/package-contracts/evidence-child-output.ts +51 -0
  110. package/src/package-contracts/judge-output.ts +1 -0
  111. package/src/package-contracts/reviewer-output.ts +2 -2
  112. package/src/package-contracts/terminating-tools.ts +53 -3
  113. package/src/packaged-role-registry.ts +63 -0
  114. package/src/pi/adapter.ts +2 -1
  115. package/src/pi/in-process-session.ts +38 -12
  116. package/src/pi/role-turn-host.ts +42 -0
  117. package/src/public-cli/cli.ts +43 -6
  118. package/src/public-cli/countersign-run.ts +9 -129
  119. package/src/public-cli/diarist-run.ts +312 -0
  120. package/src/public-cli/instruction-seat-run.ts +289 -50
  121. package/src/public-cli/invocation.ts +87 -25
  122. package/src/public-cli/option-definitions.ts +79 -0
  123. package/src/public-cli/post-admission.ts +9 -3
  124. package/src/public-cli/registry.ts +7 -1
  125. package/src/public-cli/run-lifecycle.ts +53 -6
  126. package/src/public-cli/settlement.ts +170 -7
  127. package/src/public-cli/terminal.ts +4 -1
  128. package/src/public-role-summons.ts +431 -0
  129. package/src/receipt-delivery-policy.ts +10 -0
  130. package/src/reviewer-agent.ts +1 -1
  131. package/src/reviewer-child-executor.ts +114 -12
  132. package/src/reviewer-execution-ledger.ts +3 -2
  133. package/src/role-runtime.ts +275 -23
  134. package/src/session-assistant-usage.ts +119 -0
  135. package/src/session-opening-materials.ts +1 -1
  136. package/src/shape-unreadable-failure.ts +49 -0
  137. package/src/submission-errors.ts +3 -0
  138. package/src/ticket-provenance-contracts.ts +1 -0
  139. package/src/ticket-provenance.ts +0 -23
  140. package/dist/evidence-child-executor.js +0 -829
  141. package/src/diarist-llm-collector.ts +0 -324
  142. package/src/evidence-child-executor.ts +0 -1140
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Canonical reader for run-directory typed terminal artifacts.
3
+ * Layout owner is settlement publish*Artifacts (report.json / error.json /
4
+ * audit-incomplete.json under artifacts/, plus the same publisher's durable
5
+ * failure fallbacks). This module only reads presence and structural
6
+ * readability — it does not re-derive role outcomes or invent a second
7
+ * candidate algorithm.
8
+ */
9
+ import { readdir, readFile } from "node:fs/promises";
10
+ import { basename, dirname, join } from "node:path";
11
+ export const RUN_TERMINAL_ARTIFACT_FILES = [
12
+ "report.json",
13
+ "error.json",
14
+ "audit-incomplete.json",
15
+ ];
16
+ /**
17
+ * Fixed durable failure paths publishFailureArtifacts may settle when the
18
+ * conventional artifacts/error.json name cannot be written. Shared face so the
19
+ * reader follows the publisher — not a parallel search algorithm.
20
+ * Relative to the run directory.
21
+ */
22
+ export const RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS = [
23
+ "artifacts/error.settlement.json",
24
+ "error.settlement.json",
25
+ ];
26
+ /** Unique open-ended failure names: error.<uuid>.json (publisher stem + uuid). */
27
+ const UNIQUE_ERROR_FALLBACK_NAME = /^error\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.json$/i;
28
+ function isMissingPathError(error) {
29
+ return (error instanceof Error
30
+ && "code" in error
31
+ && (error.code === "ENOENT" || error.code === "ENOTDIR"));
32
+ }
33
+ function errorText(error) {
34
+ return error instanceof Error ? error.message : String(error);
35
+ }
36
+ function isRecord(value) {
37
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38
+ }
39
+ /**
40
+ * Minimum producer-owned face shared by settlement terminal artifacts
41
+ * (report / error / audit-incomplete). Consumer-driven: enough to identify a
42
+ * usable typed terminal artifact; null, arrays, primitives, and role-less
43
+ * objects are unreadable (ADR 0043).
44
+ */
45
+ function readUsableTerminalArtifactBody(body) {
46
+ if (body === null) {
47
+ return { ok: false, reason: "terminal artifact JSON value is null" };
48
+ }
49
+ if (!isRecord(body)) {
50
+ return {
51
+ ok: false,
52
+ reason: `terminal artifact JSON value is not a typed object (${Array.isArray(body) ? "array" : typeof body})`,
53
+ };
54
+ }
55
+ if (typeof body.role !== "string" || body.role.trim() === "") {
56
+ return {
57
+ ok: false,
58
+ reason: "terminal artifact missing nonblank producer-owned role field",
59
+ };
60
+ }
61
+ return { ok: true, body };
62
+ }
63
+ async function readTerminalArtifactAtPath(path, file) {
64
+ let raw;
65
+ try {
66
+ raw = await readFile(path, "utf8");
67
+ }
68
+ catch (error) {
69
+ if (isMissingPathError(error))
70
+ return undefined;
71
+ return {
72
+ status: "unreadable",
73
+ file,
74
+ path,
75
+ reason: errorText(error),
76
+ };
77
+ }
78
+ let parsed;
79
+ try {
80
+ parsed = JSON.parse(raw);
81
+ }
82
+ catch (error) {
83
+ return {
84
+ status: "unreadable",
85
+ file,
86
+ path,
87
+ reason: error instanceof Error
88
+ ? error.message
89
+ : `terminal artifact JSON parse failed: ${String(error)}`,
90
+ };
91
+ }
92
+ const usable = readUsableTerminalArtifactBody(parsed);
93
+ if (!usable.ok) {
94
+ return {
95
+ status: "unreadable",
96
+ file,
97
+ path,
98
+ reason: usable.reason,
99
+ };
100
+ }
101
+ return { status: "present", file, path, body: usable.body };
102
+ }
103
+ async function listUniqueErrorFallbackPaths(directories) {
104
+ const found = [];
105
+ for (const dir of directories) {
106
+ let names;
107
+ try {
108
+ names = await readdir(dir);
109
+ }
110
+ catch (error) {
111
+ if (isMissingPathError(error))
112
+ continue;
113
+ throw error;
114
+ }
115
+ for (const name of names.sort((a, b) => a.localeCompare(b))) {
116
+ if (!UNIQUE_ERROR_FALLBACK_NAME.test(name))
117
+ continue;
118
+ found.push(join(dir, name));
119
+ }
120
+ }
121
+ return found;
122
+ }
123
+ /**
124
+ * Publisher run-directory face is `<runId>@<role>`. Parent-directory unique
125
+ * fallbacks are shared across sibling runs, so binding uses this runId only.
126
+ * Sole authority for runDirectory → runId (last `@` split).
127
+ */
128
+ export function runIdFromRunDirectory(runDirectory) {
129
+ const name = basename(runDirectory);
130
+ const at = name.lastIndexOf("@");
131
+ if (at <= 0 || at === name.length - 1)
132
+ return undefined;
133
+ return name.slice(0, at);
134
+ }
135
+ /**
136
+ * Shared parent-directory unique fallback may be adopted only when the
137
+ * publisher-owned body.runId equals this run directory's runId. Same-run
138
+ * artifactsDir / runDirectory candidates keep path ownership and skip this.
139
+ */
140
+ function presentUniqueFallbackBoundToRun(body, expectedRunId) {
141
+ if (expectedRunId === undefined)
142
+ return false;
143
+ return typeof body.runId === "string" && body.runId === expectedRunId;
144
+ }
145
+ /**
146
+ * Read the first present typed terminal artifact for a run directory.
147
+ * Order:
148
+ * 1) conventional artifacts/{report,error,audit-incomplete}.json
149
+ * 2) publisher fixed failure fallbacks (error.settlement.json faces)
150
+ * 3) publisher unique error.<uuid>.json fallbacks under same-run dirs
151
+ * 4) shared parent-directory unique fallbacks bound by body.runId
152
+ *
153
+ * Absence of every known durable face is a valid no-receipt state (not unreadable).
154
+ * A present file that cannot be parsed as a usable typed JSON object is unreadable.
155
+ */
156
+ export async function readRunTerminalArtifact(runDirectory) {
157
+ const artifactsDir = join(runDirectory, "artifacts");
158
+ for (const file of RUN_TERMINAL_ARTIFACT_FILES) {
159
+ const path = join(artifactsDir, file);
160
+ const read = await readTerminalArtifactAtPath(path, file);
161
+ if (read !== undefined)
162
+ return read;
163
+ }
164
+ // Publisher settled a durable failure outside the conventional error.json name.
165
+ for (const relative of RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS) {
166
+ const path = join(runDirectory, relative);
167
+ const read = await readTerminalArtifactAtPath(path, "error.json");
168
+ if (read !== undefined)
169
+ return read;
170
+ }
171
+ // Same-run unique faces: path ownership is the run itself — no cross-run risk.
172
+ for (const path of await listUniqueErrorFallbackPaths([artifactsDir, runDirectory])) {
173
+ const read = await readTerminalArtifactAtPath(path, "error.json");
174
+ if (read !== undefined)
175
+ return read;
176
+ }
177
+ // Shared parent (runs/) unique faces: require publisher runId binding.
178
+ const expectedRunId = runIdFromRunDirectory(runDirectory);
179
+ for (const path of await listUniqueErrorFallbackPaths([dirname(runDirectory)])) {
180
+ const read = await readTerminalArtifactAtPath(path, "error.json");
181
+ if (read === undefined)
182
+ continue;
183
+ if (read.status === "present") {
184
+ if (!presentUniqueFallbackBoundToRun(read.body, expectedRunId))
185
+ continue;
186
+ return read;
187
+ }
188
+ // Unreadable parent unique file cannot prove run identity — do not adopt.
189
+ }
190
+ return { status: "absent" };
191
+ }
192
+ /** Test/helper: basename face of a unique fallback path, if any. */
193
+ export function isUniqueErrorFallbackName(name) {
194
+ return UNIQUE_ERROR_FALLBACK_NAME.test(basename(name));
195
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Typed ticketNumber reader for a retained run's durable pages.
3
+ * admitted-request.json first, invocation.json fallback. Missing page (ENOENT)
4
+ * → try next / undefined; damage and non-ENOENT IO failures propagate.
5
+ */
6
+ import { readFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ function isEnoent(error) {
9
+ return (error instanceof Error &&
10
+ "code" in error &&
11
+ error.code === "ENOENT");
12
+ }
13
+ function ticketFromRecord(record) {
14
+ const ticketNumber = record.ticketNumber;
15
+ if (typeof ticketNumber === "number" &&
16
+ Number.isSafeInteger(ticketNumber) &&
17
+ ticketNumber >= 1) {
18
+ return ticketNumber;
19
+ }
20
+ return undefined;
21
+ }
22
+ async function readPageTicketNumber(runDirectory, page) {
23
+ try {
24
+ const raw = JSON.parse(await readFile(join(runDirectory, page), "utf8"));
25
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
26
+ return undefined;
27
+ }
28
+ return ticketFromRecord(raw);
29
+ }
30
+ catch (error) {
31
+ if (isEnoent(error))
32
+ return undefined;
33
+ throw error;
34
+ }
35
+ }
36
+ /** Sole reader of ticketNumber from a retained run's admitted then invocation pages. */
37
+ export async function readRunTicketNumber(runDirectory) {
38
+ return ((await readPageTicketNumber(runDirectory, "admitted-request.json")) ??
39
+ (await readPageTicketNumber(runDirectory, "invocation.json")));
40
+ }
@@ -0,0 +1,107 @@
1
+ import { join } from "node:path";
2
+ export async function readAssistantUsageFromSessionFile(sessionFile) {
3
+ const { readFile } = await import("node:fs/promises");
4
+ let text;
5
+ try {
6
+ text = await readFile(sessionFile, "utf8");
7
+ }
8
+ catch (error) {
9
+ if (error?.code === "ENOENT")
10
+ return undefined;
11
+ throw error;
12
+ }
13
+ let rows;
14
+ try {
15
+ rows = text
16
+ .trim()
17
+ .split("\n")
18
+ .filter(Boolean)
19
+ .map((line) => JSON.parse(line));
20
+ }
21
+ catch (error) {
22
+ throw new Error(`session usage parse failed (${sessionFile}): ${error instanceof Error ? error.message : String(error)}`, { cause: error });
23
+ }
24
+ let total = 0;
25
+ let input = 0;
26
+ let output = 0;
27
+ let cacheRead = 0;
28
+ let cacheWrite = 0;
29
+ let costInput = 0;
30
+ let costOutput = 0;
31
+ let costCacheRead = 0;
32
+ let costCacheWrite = 0;
33
+ let costTotal = 0;
34
+ let sawCost = false;
35
+ for (const row of rows) {
36
+ if (row.type !== "message" || row.message?.role !== "assistant")
37
+ continue;
38
+ const usage = row.message.usage;
39
+ if (usage === undefined)
40
+ continue;
41
+ total += usage.totalTokens ?? 0;
42
+ input += usage.input ?? 0;
43
+ output += usage.output ?? 0;
44
+ cacheRead += usage.cacheRead ?? 0;
45
+ cacheWrite += usage.cacheWrite ?? 0;
46
+ if (usage.cost !== undefined) {
47
+ sawCost = true;
48
+ costInput += usage.cost.input ?? 0;
49
+ costOutput += usage.cost.output ?? 0;
50
+ costCacheRead += usage.cost.cacheRead ?? 0;
51
+ costCacheWrite += usage.cost.cacheWrite ?? 0;
52
+ costTotal += usage.cost.total ?? 0;
53
+ }
54
+ }
55
+ if (total <= 0 && input <= 0 && output <= 0)
56
+ return undefined;
57
+ const base = {
58
+ input,
59
+ output,
60
+ cacheRead,
61
+ cacheWrite,
62
+ totalTokens: total > 0 ? total : input + output + cacheRead + cacheWrite,
63
+ };
64
+ // Cost only when session rows carried it — unknown stays absent (not zero).
65
+ if (!sawCost)
66
+ return base;
67
+ return {
68
+ ...base,
69
+ cost: {
70
+ input: costInput,
71
+ output: costOutput,
72
+ cacheRead: costCacheRead,
73
+ cacheWrite: costCacheWrite,
74
+ total: costTotal,
75
+ },
76
+ };
77
+ }
78
+ /**
79
+ * Resolve the independent role run's session.jsonl from a public summon result.
80
+ * Prefers explicit runDirectory, then terminal artifacts, then no_receipt runPointer.
81
+ */
82
+ export function sessionFileFromPublicSummon(summoned) {
83
+ if (typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== "") {
84
+ return join(summoned.runDirectory, "session", "session.jsonl");
85
+ }
86
+ const fromArtifacts = summoned.terminal?.artifacts
87
+ ?.map((a) => a.path)
88
+ .find((p) => typeof p === "string" && p.endsWith("session.jsonl"));
89
+ if (fromArtifacts !== undefined)
90
+ return fromArtifacts;
91
+ const outcome = summoned.terminal?.roleOutcome;
92
+ if (outcome === undefined)
93
+ return undefined;
94
+ const facts = outcome.decisiveFacts;
95
+ const pointer = facts?.runPointer;
96
+ if (typeof pointer === "string" && pointer.trim() !== "") {
97
+ return join(pointer, "session", "session.jsonl");
98
+ }
99
+ return undefined;
100
+ }
101
+ /** Shared usage projection for nested public summons — undefined when unknown. */
102
+ export async function usageFromPublicSummon(summoned) {
103
+ const sessionFile = sessionFileFromPublicSummon(summoned);
104
+ if (sessionFile === undefined)
105
+ return undefined;
106
+ return readAssistantUsageFromSessionFile(sessionFile);
107
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Single authoritative shape-unreadable marker (#675 / ADR 0055).
3
+ * Settlement stamps it; consumers only read it — never re-derive from cause=output.
4
+ */
5
+ export const SHAPE_UNREADABLE_KEY = "shapeUnreadable";
6
+ function isRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ /** Settlement seat path: stamp retained candidate as shape-unreadable (not infrastructure). */
10
+ export function stampShapeUnreadableDetails(candidate) {
11
+ return {
12
+ [SHAPE_UNREADABLE_KEY]: true,
13
+ candidate,
14
+ acceptedReceipt: false,
15
+ };
16
+ }
17
+ /**
18
+ * Read the settlement-stamped shape candidate from a failure decisiveFacts payload.
19
+ * Looks at root and secondaryEvidence only for the typed marker — no cause inference.
20
+ */
21
+ export function retainedShapeUnreadable(decisiveFacts) {
22
+ if (!isRecord(decisiveFacts))
23
+ return undefined;
24
+ if (decisiveFacts[SHAPE_UNREADABLE_KEY] === true) {
25
+ return { candidate: decisiveFacts.candidate };
26
+ }
27
+ const secondary = isRecord(decisiveFacts.secondaryEvidence)
28
+ ? decisiveFacts.secondaryEvidence
29
+ : undefined;
30
+ if (secondary !== undefined && secondary[SHAPE_UNREADABLE_KEY] === true) {
31
+ return { candidate: secondary.candidate };
32
+ }
33
+ return undefined;
34
+ }
@@ -3,6 +3,9 @@ function gatekeeperNonPassMessage(result) {
3
3
  const findings = result.findings.length === 0 ? "(无 findings)" : result.findings.join("; ");
4
4
  return `门下省打回重写,findings:${findings}`;
5
5
  }
6
+ if (result.status === "unreadable") {
7
+ return `门下省官回执形状不可读(${result.officer}):${result.reason}`;
8
+ }
6
9
  return `门下省 ${result.status}(${result.stage}):${result.reason}`;
7
10
  }
8
11
  /** Structured non-pass; `.result` is session-projected via tool_result, message feeds the model. */