@akagilnc/pi-workflow-roles 0.1.1751

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 (192) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +104 -0
  3. package/README.zh-CN.md +133 -0
  4. package/THIRD_PARTY_NOTICES.md +60 -0
  5. package/dist/activation-ledger-git.js +68 -0
  6. package/dist/activation-ledger-session.js +120 -0
  7. package/dist/activation-ledger-topology.js +239 -0
  8. package/dist/activation-reconciliation.js +61 -0
  9. package/dist/audit-escalation.js +108 -0
  10. package/dist/auditor-dossier-tool.js +35 -0
  11. package/dist/canonical-json.js +78 -0
  12. package/dist/compliance-transport.js +77 -0
  13. package/dist/doctor-contracts.js +172 -0
  14. package/dist/dossier-resolution.js +103 -0
  15. package/dist/evidence-child-executor.js +661 -0
  16. package/dist/exact-utf8.js +12 -0
  17. package/dist/git-object-id.js +7 -0
  18. package/dist/in-process-session.js +50 -0
  19. package/dist/merger-contracts.js +76 -0
  20. package/dist/navigator-attendance.js +995 -0
  21. package/dist/navigator-invocation-identity.js +220 -0
  22. package/dist/open-tool-schema.js +39 -0
  23. package/dist/package-contracts/collector-output.js +50 -0
  24. package/dist/package-contracts/fixer-output.js +72 -0
  25. package/dist/package-contracts/fixer-packet.js +77 -0
  26. package/dist/package-contracts/judge-output.js +17 -0
  27. package/dist/package-contracts/reviewer-output.js +82 -0
  28. package/dist/package-contracts/terminating-tools.js +173 -0
  29. package/dist/package-contracts/worker-output.js +13 -0
  30. package/dist/package-owned-tool-idle.js +104 -0
  31. package/dist/packaged-role-registry.js +34 -0
  32. package/dist/public-cli/main.js +23867 -0
  33. package/dist/public-command-renderer.js +20 -0
  34. package/dist/reviewer-agent.js +93 -0
  35. package/dist/reviewer-child-executor.js +23 -0
  36. package/dist/reviewer-construction.js +95 -0
  37. package/dist/reviewer-dispatch.js +77 -0
  38. package/dist/reviewer-execution-ledger.js +160 -0
  39. package/dist/reviewer-failure-diagnostic.js +17 -0
  40. package/dist/reviewer-git-snapshot.js +38 -0
  41. package/dist/reviewer-pinned-git.js +146 -0
  42. package/dist/reviewer-preflight-error.js +15 -0
  43. package/dist/reviewer-prompt-identity.js +10 -0
  44. package/dist/reviewer-scope-prompt.js +21 -0
  45. package/dist/reviewer-workspace.js +151 -0
  46. package/dist/sha256.js +5 -0
  47. package/dist/sitian-record-entry.js +33 -0
  48. package/dist/stderr-jsonl.js +26 -0
  49. package/dist/stream-idle-guard.js +75 -0
  50. package/dist/tool-execution-observation.js +141 -0
  51. package/dist/uuidv7.js +21 -0
  52. package/dist/work-subject-identity.js +53 -0
  53. package/extensions/role-runtime.ts +303 -0
  54. package/package.json +69 -0
  55. package/packets/fixer-prerequisites.json +6 -0
  56. package/packets/fixer-repair.md +5 -0
  57. package/packets/judge-apply.md +77 -0
  58. package/packets/judge-authority.md +64 -0
  59. package/packets/judge-plan.md +55 -0
  60. package/packets/judge-review.md +49 -0
  61. package/packets/judge-submission.md +34 -0
  62. package/resources/methods/code-review/SKILL.md +92 -0
  63. package/resources/methods/code-review/agents/openai.yaml +3 -0
  64. package/resources/methods/code-review/provenance.json +26 -0
  65. package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
  66. package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
  67. package/resources/methods/diagnosing-bugs/provenance.json +31 -0
  68. package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  69. package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
  70. package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
  71. package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
  72. package/resources/methods/tdd/SKILL.md +38 -0
  73. package/resources/methods/tdd/agents/openai.yaml +3 -0
  74. package/resources/methods/tdd/mocking.md +59 -0
  75. package/resources/methods/tdd/provenance.json +36 -0
  76. package/resources/methods/tdd/tests.md +77 -0
  77. package/resources/navigator-route-playbook.md +32 -0
  78. package/schemas/tool-execution-observation.schema.json +107 -0
  79. package/scripts/build-package.mjs +65 -0
  80. package/scripts/generate-tool-execution-observation-schema.ts +7 -0
  81. package/souls/coder.md +10 -0
  82. package/souls/collector.md +11 -0
  83. package/souls/doctor-auditor.md +23 -0
  84. package/souls/doctor.md +8 -0
  85. package/souls/fixer-auditor.md +33 -0
  86. package/souls/fixer.md +13 -0
  87. package/souls/judge-auditor.md +33 -0
  88. package/souls/judge.md +74 -0
  89. package/souls/merger.md +5 -0
  90. package/souls/navigator.md +5 -0
  91. package/souls/reviewer-auditor.md +25 -0
  92. package/souls/reviewer.md +11 -0
  93. package/src/activation-ledger-git.ts +96 -0
  94. package/src/activation-ledger-session.ts +188 -0
  95. package/src/activation-ledger-topology.ts +301 -0
  96. package/src/activation-ledger.ts +240 -0
  97. package/src/activation-reconciliation.ts +163 -0
  98. package/src/activation-trace.ts +38 -0
  99. package/src/audit-escalation.ts +177 -0
  100. package/src/auditor-dossier-tool.ts +48 -0
  101. package/src/auditor-soul.ts +28 -0
  102. package/src/canonical-json.ts +74 -0
  103. package/src/canonical-skill-binding.ts +107 -0
  104. package/src/collector-config.ts +89 -0
  105. package/src/collector-evidence.ts +461 -0
  106. package/src/collector-github.ts +656 -0
  107. package/src/collector-identity.ts +161 -0
  108. package/src/collector-ledger.ts +827 -0
  109. package/src/collector-receipt.ts +87 -0
  110. package/src/collector-role.ts +592 -0
  111. package/src/collector-tool-schemas.ts +19 -0
  112. package/src/compliance-transport.ts +130 -0
  113. package/src/doctor-auditor.ts +53 -0
  114. package/src/doctor-contracts.ts +166 -0
  115. package/src/doctor-evidence.ts +47 -0
  116. package/src/doctor-role.ts +18 -0
  117. package/src/dossier-resolution.ts +137 -0
  118. package/src/evidence-child-executor.ts +775 -0
  119. package/src/exact-utf8.ts +9 -0
  120. package/src/factory-board.ts +1822 -0
  121. package/src/git-object-id.ts +11 -0
  122. package/src/human-format.ts +65 -0
  123. package/src/in-process-session.ts +78 -0
  124. package/src/judge-auditor.ts +55 -0
  125. package/src/judge-recording-anti-forge.ts +53 -0
  126. package/src/judge-role.ts +160 -0
  127. package/src/merger-contracts.ts +71 -0
  128. package/src/merger-git-state.ts +76 -0
  129. package/src/merger-role.ts +60 -0
  130. package/src/navigator-attendance.ts +1254 -0
  131. package/src/navigator-invocation-identity.ts +446 -0
  132. package/src/open-tool-schema.ts +46 -0
  133. package/src/package-contracts/collector-output.ts +109 -0
  134. package/src/package-contracts/fixer-output.ts +81 -0
  135. package/src/package-contracts/fixer-packet.ts +93 -0
  136. package/src/package-contracts/judge-output.ts +39 -0
  137. package/src/package-contracts/reviewer-output.ts +115 -0
  138. package/src/package-contracts/terminating-tools.ts +259 -0
  139. package/src/package-contracts/worker-output.ts +36 -0
  140. package/src/package-owned-tool-idle.ts +134 -0
  141. package/src/package-resources/method-skill-binding.ts +87 -0
  142. package/src/package-resources/method-skill.ts +358 -0
  143. package/src/packaged-role-registry.ts +36 -0
  144. package/src/public-cli/cli-errors.ts +11 -0
  145. package/src/public-cli/cli-io.ts +4 -0
  146. package/src/public-cli/cli.ts +912 -0
  147. package/src/public-cli/coder-run.ts +575 -0
  148. package/src/public-cli/collector-run.ts +375 -0
  149. package/src/public-cli/command-renderer.ts +8 -0
  150. package/src/public-cli/config.ts +346 -0
  151. package/src/public-cli/doctor-run.ts +355 -0
  152. package/src/public-cli/explicit-internal.ts +274 -0
  153. package/src/public-cli/fixer-run.ts +587 -0
  154. package/src/public-cli/host-pi-runtime.ts +112 -0
  155. package/src/public-cli/invocation.ts +1958 -0
  156. package/src/public-cli/judge-run.ts +507 -0
  157. package/src/public-cli/main.ts +15 -0
  158. package/src/public-cli/merger-run.ts +681 -0
  159. package/src/public-cli/public-run-credentials.ts +71 -0
  160. package/src/public-cli/registry.ts +153 -0
  161. package/src/public-cli/reviewer-run.ts +561 -0
  162. package/src/public-cli/run-lifecycle.ts +884 -0
  163. package/src/public-cli/settlement.ts +3765 -0
  164. package/src/public-cli/terminal.ts +325 -0
  165. package/src/public-command-renderer.ts +43 -0
  166. package/src/reviewer-agent.ts +94 -0
  167. package/src/reviewer-auditor.ts +53 -0
  168. package/src/reviewer-child-executor.ts +31 -0
  169. package/src/reviewer-construction.ts +137 -0
  170. package/src/reviewer-dispatch.ts +94 -0
  171. package/src/reviewer-execution-ledger.ts +206 -0
  172. package/src/reviewer-failure-diagnostic.ts +18 -0
  173. package/src/reviewer-git-snapshot.ts +53 -0
  174. package/src/reviewer-pinned-git.ts +144 -0
  175. package/src/reviewer-preflight-error.ts +14 -0
  176. package/src/reviewer-prompt-identity.ts +17 -0
  177. package/src/reviewer-role.ts +193 -0
  178. package/src/reviewer-scope-prompt.ts +24 -0
  179. package/src/reviewer-settlement.ts +63 -0
  180. package/src/reviewer-workspace.ts +111 -0
  181. package/src/role-runtime.ts +884 -0
  182. package/src/sha256.ts +6 -0
  183. package/src/sitian-record-entry.ts +57 -0
  184. package/src/stderr-jsonl.ts +28 -0
  185. package/src/stream-idle-guard.ts +98 -0
  186. package/src/ticket-snapshot.ts +662 -0
  187. package/src/ticket-trajectory.ts +1000 -0
  188. package/src/tool-execution-observation.ts +168 -0
  189. package/src/uuidv7.ts +1 -0
  190. package/src/work-subject-identity.ts +94 -0
  191. package/src/worker-role.ts +434 -0
  192. package/src/worker-submission-gates.ts +225 -0
@@ -0,0 +1,87 @@
1
+ import { COLLECTOR_HOST } from "./collector-config.ts";
2
+ import type { CollectorClock, CollectorEvidenceRecord, CollectorSnapshot } from "./collector-evidence.ts";
3
+ import type { CollectorLedger, CollectorRequestAttempt } from "./collector-ledger.ts";
4
+ import { extractCollectorEvidenceIdentityGroups, type ExtractedCollectorIdentityGroup } from "./collector-identity.ts";
5
+ import { validateAcceptedCollectorReceipt } from "./package-contracts/collector-output.ts";
6
+
7
+ export { validateAcceptedCollectorReceipt };
8
+
9
+ export type CollectorReceipt = {
10
+ host: "github.com";
11
+ repository: string;
12
+ prNumber: number;
13
+ manifestDigest: string;
14
+ activationTime: string;
15
+ deadlineTime: string;
16
+ finalObservationTime: string;
17
+ finalSnapshotId: string;
18
+ targetHead: string;
19
+ groups: ExtractedCollectorIdentityGroup[];
20
+ requestAttempts: CollectorRequestAttempt[];
21
+ snapshots: CollectorSnapshot[];
22
+ evidenceRecords: CollectorEvidenceRecord[];
23
+ };
24
+
25
+ function fail(message: string): never { throw new Error(message); }
26
+
27
+ /** Runtime output is intentionally empty: observed typed groups are the sole receipt source. */
28
+ export function buildCollectorReceipt(
29
+ ledger: CollectorLedger,
30
+ _candidateRaw: unknown,
31
+ clock?: CollectorClock,
32
+ ): CollectorReceipt {
33
+ ledger.assertNotFatal();
34
+ if (ledger.outputAccepted) fail("Collector output is singleton");
35
+ if (ledger.unresolvedTransportFailure) fail("Collector cannot output while a transport failure is unrecovered");
36
+ if (ledger.latestCompleteSnapshotId === undefined) fail("Collector output requires a complete final snapshot");
37
+ if (ledger.activationTime === undefined || ledger.deadlineTime === undefined) fail("Collector output requires activation timeline");
38
+
39
+ if (clock !== undefined) ledger.assertOutputObservationLaw(clock);
40
+ else if (ledger.observedGeneration !== ledger.mutationGeneration ||
41
+ (ledger.finalObservationRequired && !ledger.finalObservationCompleted)) {
42
+ fail("Collector output requires a complete observe after the latest request/wait mutation");
43
+ }
44
+
45
+ const finalSnapshot = ledger.getSnapshot(ledger.latestCompleteSnapshotId);
46
+ if (finalSnapshot === undefined || !finalSnapshot.complete) fail("Collector final snapshot is incomplete");
47
+ if (finalSnapshot.prState !== "OPEN") fail("Collector final snapshot PR state is not OPEN");
48
+
49
+ const evidenceRecords = [...ledger.allEvidence()];
50
+ const snapshots = [...ledger.allSnapshots()];
51
+ const evidenceIndex = new Map(evidenceRecords.map((record) => [record.evidenceId, record]));
52
+ const snapshotIndex = new Map(snapshots.map((snapshot) => [snapshot.snapshotId, snapshot]));
53
+ if (evidenceIndex.size !== evidenceRecords.length) fail("Collector receipt evidenceId collision");
54
+ if (snapshotIndex.size !== snapshots.length) fail("Collector receipt snapshotId collision");
55
+ for (const id of evidenceIndex.keys()) if (snapshotIndex.has(id)) fail(`Collector receipt id "${id}" is ambiguous`);
56
+ if (!snapshotIndex.has(finalSnapshot.snapshotId)) fail("Collector receipt lacks final snapshot");
57
+ for (const snapshot of snapshots) {
58
+ for (const id of snapshot.evidenceIds) if (!evidenceIndex.has(id)) fail(`Collector snapshot ref "${id}" does not resolve`);
59
+ }
60
+
61
+ const groups = extractCollectorEvidenceIdentityGroups(evidenceRecords, finalSnapshot.headOid);
62
+ for (const group of groups) {
63
+ if (group.attendance !== true) fail("Collector group lacks attendance");
64
+ for (const material of group.materials) {
65
+ if (material.evidenceId === undefined || !evidenceIndex.has(material.evidenceId)) fail("Collector material lacks a receipt-local evidence ref");
66
+ }
67
+ for (const finding of group.findings) {
68
+ if (finding.source.evidenceId === undefined || !evidenceIndex.has(finding.source.evidenceId)) fail("Collector finding lacks a receipt-local evidence ref");
69
+ }
70
+ }
71
+
72
+ return {
73
+ host: COLLECTOR_HOST,
74
+ repository: ledger.config.repository.canonical,
75
+ prNumber: ledger.config.prNumber,
76
+ manifestDigest: ledger.config.manifest.digest,
77
+ activationTime: ledger.activationTime.toISOString(),
78
+ deadlineTime: ledger.deadlineTime.toISOString(),
79
+ finalObservationTime: finalSnapshot.completedAt ?? finalSnapshot.observedAt,
80
+ finalSnapshotId: finalSnapshot.snapshotId,
81
+ targetHead: finalSnapshot.headOid,
82
+ groups,
83
+ requestAttempts: [...ledger.requestAttempts()],
84
+ snapshots,
85
+ evidenceRecords,
86
+ };
87
+ }
@@ -0,0 +1,592 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import type { Static } from "typebox";
6
+
7
+ import {
8
+ COLLECTOR_FIXED_KICKOFF,
9
+ emptyCollectorManifest,
10
+ loadCollectorManifest,
11
+ parseCollectorPrNumber,
12
+ parseCollectorRepository,
13
+ type CollectorManifest,
14
+ type CollectorRepository,
15
+ } from "./collector-config.ts";
16
+ import {
17
+ createSystemCollectorClock,
18
+ type CollectorClock,
19
+ } from "./collector-evidence.ts";
20
+ import type { CollectorGitHubTransport } from "./collector-github.ts";
21
+ import {
22
+ COLLECTOR_OBSERVE_TOOL,
23
+ COLLECTOR_OUTPUT_TOOL,
24
+ COLLECTOR_REQUEST_TOOL,
25
+ COLLECTOR_WAIT_TOOL,
26
+ createCollectorLedger,
27
+ type CollectorLedger,
28
+ } from "./collector-ledger.ts";
29
+ import {
30
+ buildCollectorReceipt,
31
+ type CollectorReceipt,
32
+ } from "./collector-receipt.ts";
33
+ import {
34
+ collectorObserveArgsSchema,
35
+ collectorOutputArgsSchema,
36
+ collectorRequestArgsSchema,
37
+ collectorWaitArgsSchema,
38
+ } from "./collector-tool-schemas.ts";
39
+
40
+ export { COLLECTOR_FIXED_KICKOFF } from "./collector-config.ts";
41
+ export {
42
+ COLLECTOR_OBSERVE_TOOL,
43
+ COLLECTOR_OUTPUT_TOOL,
44
+ COLLECTOR_REQUEST_TOOL,
45
+ COLLECTOR_WAIT_TOOL,
46
+ };
47
+
48
+ export const COLLECTOR_REQUIRED_TOOLS = [
49
+ COLLECTOR_OBSERVE_TOOL,
50
+ COLLECTOR_REQUEST_TOOL,
51
+ COLLECTOR_WAIT_TOOL,
52
+ COLLECTOR_OUTPUT_TOOL,
53
+ ] as const;
54
+
55
+ const observeSchema = collectorObserveArgsSchema;
56
+ const requestSchema = collectorRequestArgsSchema;
57
+ const waitSchema = collectorWaitArgsSchema;
58
+ const outputSchema = collectorOutputArgsSchema;
59
+
60
+ // Well below the fixed 183s package-tool silence clock while avoiding noisy UI
61
+ // churn during Collector's legal five-minute wait.
62
+ const COLLECTOR_WAIT_PROGRESS_INTERVAL_MS = 60_000;
63
+
64
+ type RequestParams = Static<typeof requestSchema>;
65
+ type WaitParams = Static<typeof waitSchema>;
66
+ type OutputParams = Static<typeof outputSchema>;
67
+
68
+ export type CollectorRoleDependencies = {
69
+ loadSoul(): Promise<string>;
70
+ createTransport(): CollectorGitHubTransport;
71
+ createClock?(): CollectorClock;
72
+ packageExtensionPath?: string;
73
+ };
74
+
75
+ export type CollectorRoleHostActions = {
76
+ failInfrastructure(error: unknown, ctx: ExtensionContext, toolCallId?: string): never;
77
+ };
78
+
79
+ type CollectorActivation = {
80
+ soul: string;
81
+ repository: CollectorRepository;
82
+ prNumber: number;
83
+ manifest: CollectorManifest;
84
+ ledger: CollectorLedger;
85
+ transport: CollectorGitHubTransport;
86
+ clock: CollectorClock;
87
+ };
88
+
89
+ /** Sole-final at the output execution seam (same shape as other terminating roles). */
90
+ function assertSoleFinalCollectorOutput(
91
+ toolCallId: string,
92
+ ctx: ExtensionContext,
93
+ ): void {
94
+ const leaf = ctx.sessionManager.getLeafEntry();
95
+ if (leaf?.type !== "message" || leaf.message.role !== "assistant") {
96
+ throw new Error("Collector output must be the sole final tool call");
97
+ }
98
+ const calls = leaf.message.content.filter((part) => part.type === "toolCall");
99
+ const call = calls[0];
100
+ if (
101
+ calls.length !== 1 ||
102
+ call === undefined ||
103
+ call.id !== toolCallId ||
104
+ call.name !== COLLECTOR_OUTPUT_TOOL
105
+ ) {
106
+ throw new Error("Collector output must be the sole final tool call");
107
+ }
108
+ }
109
+
110
+ function buildMethodContext(activation: CollectorActivation): string {
111
+ return [
112
+ "<collector_method>",
113
+ "Observe all GitHub review materials; machine identity groups are runtime-owned.",
114
+ "Use ak_collector_request only when an optional configured request is needed.",
115
+ "Wait and cutoff only stop observation; they never imply absent observers.",
116
+ "After a complete final observation, submit ak_collector_output with an empty object.",
117
+ `host: github.com`,
118
+ `repository: ${activation.repository.canonical}`,
119
+ `prNumber: ${activation.prNumber}`,
120
+ `requests: ${JSON.stringify(activation.manifest.requests.map((request) => ({ id: request.id })))}`,
121
+ "</collector_method>",
122
+ ].join("\n");
123
+ }
124
+
125
+ export function createCollectorRoleRuntime(
126
+ pi: ExtensionAPI,
127
+ dependencies: CollectorRoleDependencies,
128
+ hostActions: CollectorRoleHostActions,
129
+ ): {
130
+ activate(
131
+ ctx: ExtensionContext,
132
+ event: { reason: string },
133
+ ): Promise<void>;
134
+ } {
135
+ let activation: CollectorActivation | undefined;
136
+ let inputCount = 0;
137
+ let lifecycleRegistered = false;
138
+ let toolsRegistered = false;
139
+ let firstDispatchDone = false;
140
+
141
+ pi.registerFlag("ak-collector-repo", {
142
+ description:
143
+ "GitHub owner/repo target for Collector (github.com only; conservative ASCII grammar). Collector forbids every Skill, including command-only Skills.",
144
+ type: "string",
145
+ });
146
+ pi.registerFlag("ak-collector-pr", {
147
+ description:
148
+ "Positive safe-integer pull request number for Collector. Supported profile: --no-skills, --no-extensions with only the explicit Collector package extension, no prompt templates/context files, one print/JSON prompt",
149
+ type: "string",
150
+ });
151
+ pi.registerFlag("ak-collector-request-manifest", {
152
+ description:
153
+ "Path to the Collector v1 request manifest JSON file. In Pi latest, late hostile sibling-extension Skill injection is unsupported and fail-closed when detected; drift prevention only, not a security boundary or provider-zero guarantee",
154
+ type: "string",
155
+ });
156
+
157
+ const ensureLifecycle = (): void => {
158
+ if (lifecycleRegistered) return;
159
+ lifecycleRegistered = true;
160
+
161
+ pi.on("input", (event, ctx) => {
162
+ if (activation === undefined) {
163
+ // role not active
164
+ return { action: "continue" as const };
165
+ }
166
+ if (inputCount >= 1) {
167
+ activation.ledger.latchFatal(
168
+ "Collector is one-shot and rejects later inputs",
169
+ );
170
+ if (process.exitCode === undefined || process.exitCode === 0) {
171
+ process.exitCode = 1;
172
+ }
173
+ console.error("Collector rejected later input");
174
+ return { action: "handled" as const };
175
+ }
176
+ inputCount += 1;
177
+ return {
178
+ action: "transform" as const,
179
+ text: COLLECTOR_FIXED_KICKOFF,
180
+ images: [],
181
+ };
182
+ });
183
+
184
+ pi.on("before_agent_start", (event, ctx) => {
185
+ if (activation === undefined) return;
186
+
187
+ // Detectable ambient instruction resources on the supported prompt surface.
188
+ const options = event.systemPromptOptions;
189
+ if (options.skills && options.skills.length > 0) {
190
+ hostActions.failInfrastructure(
191
+ activation.ledger.latchFatal(
192
+ "Collector detected ambient skills in systemPromptOptions",
193
+ ),
194
+ ctx,
195
+ );
196
+ }
197
+ if (options.contextFiles && options.contextFiles.length > 0) {
198
+ hostActions.failInfrastructure(
199
+ activation.ledger.latchFatal(
200
+ "Collector detected ambient context files in systemPromptOptions",
201
+ ),
202
+ ctx,
203
+ );
204
+ }
205
+ if (
206
+ typeof options.appendSystemPrompt === "string" &&
207
+ options.appendSystemPrompt.trim().length > 0
208
+ ) {
209
+ hostActions.failInfrastructure(
210
+ activation.ledger.latchFatal(
211
+ "Collector detected appendSystemPrompt drift",
212
+ ),
213
+ ctx,
214
+ );
215
+ }
216
+
217
+ if (event.prompt !== COLLECTOR_FIXED_KICKOFF) {
218
+ hostActions.failInfrastructure(
219
+ activation.ledger.latchFatal(
220
+ "Collector first prompt must be the fixed packaged kickoff",
221
+ ),
222
+ ctx,
223
+ );
224
+ }
225
+
226
+ if (!firstDispatchDone) {
227
+ firstDispatchDone = true;
228
+ activation.ledger.recordActivation(activation.clock);
229
+ }
230
+
231
+ return {
232
+ systemPrompt: [
233
+ event.systemPrompt,
234
+ "",
235
+ "<collector_soul>",
236
+ activation.soul,
237
+ "</collector_soul>",
238
+ "",
239
+ buildMethodContext(activation),
240
+ ].join("\n"),
241
+ };
242
+ });
243
+
244
+ pi.on("tool_call", (event) => {
245
+ if (activation === undefined) return;
246
+ if (activation.ledger.fatal) {
247
+ return {
248
+ block: true,
249
+ reason: activation.ledger.fatalReason ?? "Collector fatal state",
250
+ };
251
+ }
252
+ if (!(COLLECTOR_REQUIRED_TOOLS as readonly string[]).includes(event.toolName)) {
253
+ return {
254
+ block: true,
255
+ reason: `Collector forbids tool ${event.toolName}`,
256
+ };
257
+ }
258
+ // Concurrency is owned at execute (beginOperational), not announced-batch preflight.
259
+ // Pi may emit tool_call for every part before any execute runs (ADR 0041).
260
+ if (
261
+ activation.ledger.outputAccepted &&
262
+ event.toolName !== COLLECTOR_OUTPUT_TOOL
263
+ ) {
264
+ return {
265
+ block: true,
266
+ reason: "Collector output already accepted; no further operations",
267
+ };
268
+ }
269
+ return undefined;
270
+ });
271
+
272
+ pi.on("tool_result", (event) => {
273
+ if (activation === undefined) return;
274
+ activation.ledger.completeOperational(event.toolCallId);
275
+ });
276
+
277
+ pi.on("session_shutdown", () => {
278
+ if (activation === undefined) return;
279
+ if (!activation.ledger.outputAccepted || activation.ledger.fatal) {
280
+ if (process.exitCode === undefined || process.exitCode === 0) {
281
+ process.exitCode = 1;
282
+ }
283
+ }
284
+ });
285
+ };
286
+
287
+ const registerTools = (): void => {
288
+ if (toolsRegistered) return;
289
+ toolsRegistered = true;
290
+
291
+ pi.registerTool({
292
+ name: COLLECTOR_OBSERVE_TOOL,
293
+ label: "Collector Observe",
294
+ description:
295
+ "Fetch complete GitHub PR evidence for the configured target and store an immutable snapshot in the invocation ledger.",
296
+ promptSnippet: "Observe configured PR evidence",
297
+ promptGuidelines: [
298
+ "Call ak_collector_observe with no arguments to refresh the latest complete snapshot.",
299
+ ],
300
+ parameters: observeSchema,
301
+ async execute(toolCallId, _params, signal, _onUpdate, ctx) {
302
+ if (activation === undefined) {
303
+ throw new Error("Collector is not activated");
304
+ }
305
+ try {
306
+ activation.ledger.beginOperational(COLLECTOR_OBSERVE_TOOL, toolCallId);
307
+ const { snapshot, modelView } = await activation.ledger.observe(
308
+ activation.transport,
309
+ activation.clock,
310
+ signal,
311
+ );
312
+ activation.ledger.completeOperational(toolCallId);
313
+ if (snapshot.prState !== "OPEN") {
314
+ // Non-OPEN observed as latest complete snapshot is target-state failure at output,
315
+ // but observe itself may return the fact. If this is a final observation, still return.
316
+ }
317
+ return {
318
+ content: [{
319
+ type: "text" as const,
320
+ text: JSON.stringify(modelView),
321
+ }],
322
+ details: modelView,
323
+ };
324
+ } catch (error) {
325
+ hostActions.failInfrastructure(error, ctx);
326
+ }
327
+ },
328
+ });
329
+
330
+ pi.registerTool({
331
+ name: COLLECTOR_REQUEST_TOOL,
332
+ label: "Collector Request",
333
+ description:
334
+ "Post one configured request body plus its correlation marker at the cited latest snapshot HEAD.",
335
+ promptSnippet: "Post a configured request",
336
+ promptGuidelines: [
337
+ "Call ak_collector_request only with a configured request-capable requestId and the latest snapshotId.",
338
+ ],
339
+ parameters: requestSchema,
340
+ async execute(toolCallId, params: RequestParams, signal, _onUpdate, ctx) {
341
+ if (activation === undefined) {
342
+ throw new Error("Collector is not activated");
343
+ }
344
+ try {
345
+ activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
346
+ const details = await activation.ledger.request(
347
+ params,
348
+ activation.transport,
349
+ activation.clock,
350
+ signal,
351
+ );
352
+ activation.ledger.completeOperational(toolCallId);
353
+ return {
354
+ content: [{
355
+ type: "text" as const,
356
+ text: `Request attempt recorded for request ${params.requestId}`,
357
+ }],
358
+ details,
359
+ };
360
+ } catch (error) {
361
+ hostActions.failInfrastructure(error, ctx);
362
+ }
363
+ },
364
+ });
365
+
366
+ pi.registerTool({
367
+ name: COLLECTOR_WAIT_TOOL,
368
+ label: "Collector Wait",
369
+ description:
370
+ "Wait before re-observing; each call is capped to five minutes and to remaining eligibility.",
371
+ promptSnippet: "Wait within the eligibility cutoff",
372
+ promptGuidelines: [
373
+ "Call ak_collector_wait with a positive durationMs; runtime caps each wait to five minutes and to remaining eligibility.",
374
+ ],
375
+ parameters: waitSchema,
376
+ async execute(toolCallId, params: WaitParams, signal, onUpdate, ctx) {
377
+ if (activation === undefined) {
378
+ throw new Error("Collector is not activated");
379
+ }
380
+ const currentActivation = activation;
381
+ let progressTimer: ReturnType<typeof setTimeout> | undefined;
382
+ try {
383
+ currentActivation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
384
+ const waitStartedMono = currentActivation.clock.monoNow();
385
+ let lastElapsedMs = 0;
386
+ const scheduleProgress = (): void => {
387
+ if (onUpdate === undefined) return;
388
+ progressTimer = setTimeout(() => {
389
+ const elapsedMs = Math.max(
390
+ 0,
391
+ Math.floor(currentActivation.clock.monoNow() - waitStartedMono),
392
+ );
393
+ if (elapsedMs > lastElapsedMs) {
394
+ lastElapsedMs = elapsedMs;
395
+ onUpdate({
396
+ content: [],
397
+ details: { kind: "collector_wait_progress", elapsedMs },
398
+ });
399
+ }
400
+ scheduleProgress();
401
+ }, COLLECTOR_WAIT_PROGRESS_INTERVAL_MS);
402
+ };
403
+ scheduleProgress();
404
+ const details = await currentActivation.ledger.wait(
405
+ params,
406
+ currentActivation.clock,
407
+ signal,
408
+ );
409
+ currentActivation.ledger.completeOperational(toolCallId);
410
+ return {
411
+ content: [{
412
+ type: "text" as const,
413
+ text: `Waited ${String((details as { effectiveMs: number }).effectiveMs)}ms`,
414
+ }],
415
+ details,
416
+ };
417
+ } catch (error) {
418
+ hostActions.failInfrastructure(error, ctx);
419
+ } finally {
420
+ if (progressTimer !== undefined) clearTimeout(progressTimer);
421
+ }
422
+ },
423
+ });
424
+
425
+ pi.registerTool({
426
+ name: COLLECTOR_OUTPUT_TOOL,
427
+ label: "Collector Output",
428
+ description:
429
+ "Submit after observation. Runtime builds the self-contained typed-group receipt.",
430
+ promptSnippet: "Submit the Collector receipt",
431
+ promptGuidelines: [
432
+ "Use ak_collector_output as the sole final Collector action after a complete observation.",
433
+ ],
434
+ parameters: outputSchema,
435
+ async execute(toolCallId, params: OutputParams, _signal, _onUpdate, ctx) {
436
+ if (activation === undefined) {
437
+ throw new Error("Collector is not activated");
438
+ }
439
+ try {
440
+ assertSoleFinalCollectorOutput(toolCallId, ctx);
441
+ activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, toolCallId);
442
+ const receipt: CollectorReceipt = buildCollectorReceipt(
443
+ activation.ledger,
444
+ params,
445
+ activation.clock,
446
+ );
447
+ activation.ledger.markOutputAccepted();
448
+ activation.ledger.completeOperational(toolCallId);
449
+ return {
450
+ content: [{
451
+ type: "text" as const,
452
+ text: "Collector receipt accepted",
453
+ }],
454
+ details: receipt,
455
+ terminate: true as const,
456
+ };
457
+ } catch (error) {
458
+ // Output validation errors are model-visible rejections when non-fatal.
459
+ if (
460
+ error instanceof Error &&
461
+ (error as { collectorFatal?: boolean }).collectorFatal === true
462
+ ) {
463
+ hostActions.failInfrastructure(error, ctx, toolCallId);
464
+ }
465
+ throw error;
466
+ }
467
+ },
468
+ });
469
+ };
470
+
471
+ return {
472
+ async activate(ctx, event) {
473
+ activation = undefined;
474
+ ensureLifecycle();
475
+
476
+ if (ctx.mode !== "print" && ctx.mode !== "json") {
477
+ throw new Error(
478
+ `Collector supports only print or json mode (got ${ctx.mode})`,
479
+ );
480
+ }
481
+ if (
482
+ event.reason === "resume" ||
483
+ event.reason === "fork" ||
484
+ event.reason === "reload"
485
+ ) {
486
+ throw new Error(
487
+ `Collector does not support session_start reason ${event.reason}`,
488
+ );
489
+ }
490
+
491
+ const soul = (await dependencies.loadSoul()).trim();
492
+ if (soul.length === 0) throw new Error("Collector soul is empty");
493
+
494
+ const repoFlag = pi.getFlag("ak-collector-repo");
495
+ const prFlag = pi.getFlag("ak-collector-pr");
496
+ const requestManifestFlag = pi.getFlag("ak-collector-request-manifest");
497
+ if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
498
+ throw new Error("Collector requires --ak-collector-repo");
499
+ }
500
+ if (typeof prFlag !== "string" && typeof prFlag !== "number") {
501
+ throw new Error("Collector requires --ak-collector-pr");
502
+ }
503
+ const repository = parseCollectorRepository(repoFlag);
504
+ const prNumber = parseCollectorPrNumber(prFlag);
505
+ const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0
506
+ ? await loadCollectorManifest(requestManifestFlag)
507
+ : emptyCollectorManifest();
508
+
509
+ // Detectable ambient command surface (skills/templates) when exposed.
510
+ const commands = pi.getCommands?.() ?? [];
511
+ const ambientCommands = commands.filter((command) => {
512
+ const name = command.name.toLowerCase();
513
+ return (
514
+ name.includes("skill") ||
515
+ name.includes("prompt") ||
516
+ name.startsWith("template")
517
+ );
518
+ });
519
+ if (ambientCommands.length > 0) {
520
+ throw new Error(
521
+ `Collector detected ambient instruction commands: ${
522
+ ambientCommands.map((c) => c.name).join(", ")
523
+ }`,
524
+ );
525
+ }
526
+
527
+ // Fail closed if a required name is already occupied before Collector registers.
528
+ const preExisting = pi.getAllTools();
529
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
530
+ const prior = preExisting.filter((tool) => tool.name === required);
531
+ if (prior.length > 0) {
532
+ throw new Error(`Collector required tool name collision: ${required}`);
533
+ }
534
+ }
535
+
536
+ registerTools();
537
+
538
+ const allTools = pi.getAllTools();
539
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
540
+ const matches = allTools.filter((tool) => tool.name === required);
541
+ if (matches.length === 0) {
542
+ throw new Error(`Collector required tool missing: ${required}`);
543
+ }
544
+ if (matches.length > 1) {
545
+ throw new Error(`Collector required tool name collision: ${required}`);
546
+ }
547
+ const tool = matches[0]!;
548
+ if (
549
+ dependencies.packageExtensionPath !== undefined &&
550
+ tool.sourceInfo?.path !== undefined &&
551
+ tool.sourceInfo.path !== dependencies.packageExtensionPath &&
552
+ !tool.sourceInfo.path.includes("role-runtime")
553
+ ) {
554
+ throw new Error(
555
+ `Collector required tool ${required} is overridden by ${tool.sourceInfo.path}`,
556
+ );
557
+ }
558
+ }
559
+
560
+ pi.setActiveTools([...COLLECTOR_REQUIRED_TOOLS]);
561
+ const active = new Set(pi.getActiveTools());
562
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
563
+ if (!active.has(required)) {
564
+ throw new Error(`Collector failed to activate required tool ${required}`);
565
+ }
566
+ }
567
+ for (const name of active) {
568
+ if (!(COLLECTOR_REQUIRED_TOOLS as readonly string[]).includes(name)) {
569
+ throw new Error(`Collector active tool surface includes unexpected ${name}`);
570
+ }
571
+ }
572
+
573
+ const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
574
+ const transport = dependencies.createTransport();
575
+ const ledger = createCollectorLedger({
576
+ repository,
577
+ prNumber,
578
+ manifest,
579
+ });
580
+
581
+ activation = {
582
+ soul,
583
+ repository,
584
+ prNumber,
585
+ manifest,
586
+ ledger,
587
+ transport,
588
+ clock,
589
+ };
590
+ },
591
+ };
592
+ }
@@ -0,0 +1,19 @@
1
+ import { Type, type Static } from "typebox";
2
+ import { COLLECTOR_ELIGIBILITY_MS } from "./collector-evidence.ts";
3
+
4
+ export const collectorObserveArgsSchema = Type.Object({}, { additionalProperties: false });
5
+ export const collectorRequestArgsSchema = Type.Object({
6
+ requestId: Type.String({ minLength: 1, description: "Configured request identity." }),
7
+ snapshotId: Type.String({ minLength: 1, description: "Latest retained observation snapshot." }),
8
+ }, { additionalProperties: false });
9
+ export const collectorWaitArgsSchema = Type.Object({
10
+ durationMs: Type.Integer({ minimum: 1, maximum: COLLECTOR_ELIGIBILITY_MS }),
11
+ }, { additionalProperties: false });
12
+ /** Runtime owns the observed groups; the model only signals sole-final submission. */
13
+ export const collectorOutputArgsSchema = Type.Object({}, { additionalProperties: true });
14
+ (collectorOutputArgsSchema as unknown as { required: string[] }).required = [];
15
+
16
+ export type CollectorObserveArgs = Static<typeof collectorObserveArgsSchema>;
17
+ export type CollectorRequestArgs = Static<typeof collectorRequestArgsSchema>;
18
+ export type CollectorWaitArgs = Static<typeof collectorWaitArgsSchema>;
19
+ export type CollectorOutputArgs = Static<typeof collectorOutputArgsSchema>;