@akagilnc/pi-workflow-roles 0.1.3749 → 0.1.3771

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 (49) hide show
  1. package/CLAUDE.md +4 -0
  2. package/README.md +3 -2
  3. package/README.zh-CN.md +3 -2
  4. package/dist/acp-host/production-host.js +1389 -751
  5. package/dist/collector-config.js +0 -1
  6. package/dist/collector-github.js +199 -2
  7. package/dist/collector-identity.js +128 -41
  8. package/dist/collector-ledger.js +38 -9
  9. package/dist/collector-receipt.js +19 -7
  10. package/dist/collector-role.js +330 -369
  11. package/dist/collector-target.js +169 -0
  12. package/dist/collector-tool-schemas.js +51 -14
  13. package/dist/package-contracts/collector-output.js +32 -0
  14. package/dist/package-contracts/terminating-infrastructure.js +13 -12
  15. package/dist/pi/role-turn-host.js +1 -2
  16. package/dist/public-cli/github-remote.js +45 -0
  17. package/dist/public-cli/invocation.js +53 -54
  18. package/dist/public-cli/main.js +615 -148
  19. package/dist/public-cli/option-definitions.js +6 -4
  20. package/dist/public-cli/run-lifecycle.js +3 -3
  21. package/dist/public-cli/settlement.js +83 -6
  22. package/dist/role-runtime.js +137 -7
  23. package/dist/submission-correctable-error.js +24 -0
  24. package/extensions/role-runtime.ts +0 -1
  25. package/package.json +1 -1
  26. package/souls/coder.md +11 -6
  27. package/souls/fixer.md +10 -9
  28. package/src/acp-host/role-envelope.ts +8 -22
  29. package/src/collector-config.ts +0 -1
  30. package/src/collector-github.ts +236 -2
  31. package/src/collector-identity.ts +148 -40
  32. package/src/collector-ledger.ts +48 -10
  33. package/src/collector-receipt.ts +33 -14
  34. package/src/collector-role.ts +376 -450
  35. package/src/collector-target.ts +207 -0
  36. package/src/collector-tool-schemas.ts +62 -15
  37. package/src/host-contracts.ts +2 -1
  38. package/src/package-contracts/collector-output.ts +72 -0
  39. package/src/package-contracts/terminating-infrastructure.ts +24 -13
  40. package/src/pi/role-turn-host.ts +1 -2
  41. package/src/public-cli/cli.ts +10 -1
  42. package/src/public-cli/collector-run.ts +3 -2
  43. package/src/public-cli/github-remote.ts +45 -0
  44. package/src/public-cli/invocation.ts +60 -59
  45. package/src/public-cli/option-definitions.ts +6 -4
  46. package/src/public-cli/run-lifecycle.ts +2 -2
  47. package/src/public-cli/settlement.ts +82 -6
  48. package/src/role-runtime.ts +166 -13
  49. package/src/submission-correctable-error.ts +38 -0
@@ -1,8 +1,9 @@
1
- import { COLLECTOR_FIXED_KICKOFF, emptyCollectorManifest, loadCollectorManifest, parseCollectorPrNumber, parseCollectorRepository, } from "./collector-config.js";
1
+ import { emptyCollectorManifest, loadCollectorManifest, parseCollectorPrNumber, parseCollectorRepository, } from "./collector-config.js";
2
2
  import { createSystemCollectorClock, } from "./collector-evidence.js";
3
- import { COLLECTOR_OBSERVE_TOOL, COLLECTOR_OUTPUT_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, projectEvidenceEntryView, } from "./collector-ledger.js";
3
+ import { createGhApiRunner, listPullRequestNumbersByTicket, } from "./collector-github.js";
4
+ import { COLLECTOR_BIND_TARGET_TOOL, COLLECTOR_OBSERVE_TOOL, COLLECTOR_OUTPUT_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, projectEvidenceEntryView, } from "./collector-ledger.js";
4
5
  import { buildCollectorReceipt, } from "./collector-receipt.js";
5
- import { collectorObserveArgsSchema, collectorOutputArgsSchema, collectorReadArgsSchema, collectorRequestArgsSchema, collectorWaitArgsSchema, } from "./collector-tool-schemas.js";
6
+ import { collectorBindTargetArgsSchema, collectorObserveArgsSchema, collectorOutputArgsSchema, collectorReadArgsSchema, collectorRequestArgsSchema, collectorWaitArgsSchema, } from "./collector-tool-schemas.js";
6
7
  import { COLLECTOR_ACCEPTED_TEXT } from "./package-contracts/collector-output.js";
7
8
  import { CorrectableSubmissionError, isCorrectableExecuteError } from "./submission-correctable-error.js";
8
9
  import { CollectorUnknownEvidenceError } from "./collector-identity.js";
@@ -18,122 +19,133 @@ export class CollectorNormalCompletionDeclarationError extends CorrectableSubmis
18
19
  this.name = "CollectorNormalCompletionDeclarationError";
19
20
  }
20
21
  }
21
- export { COLLECTOR_FIXED_KICKOFF, } from "./collector-config.js";
22
- export { COLLECTOR_OBSERVE_TOOL, COLLECTOR_OUTPUT_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, };
22
+ /** #676 A: role-chosen target could not bind uniquely — correctable, not host failure. */
23
+ export class CollectorTargetBindError extends CorrectableSubmissionError {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = "CollectorTargetBindError";
27
+ }
28
+ }
29
+ export { COLLECTOR_BIND_TARGET_TOOL, COLLECTOR_OBSERVE_TOOL, COLLECTOR_OUTPUT_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, };
23
30
  export const COLLECTOR_REQUIRED_TOOLS = [
31
+ COLLECTOR_BIND_TARGET_TOOL,
24
32
  COLLECTOR_OBSERVE_TOOL,
25
33
  COLLECTOR_READ_TOOL,
26
34
  COLLECTOR_REQUEST_TOOL,
27
35
  COLLECTOR_WAIT_TOOL,
28
36
  COLLECTOR_OUTPUT_TOOL,
29
37
  ];
38
+ /** Envelope-owned transport flags (ADR 0018 / #676 E). */
39
+ export const COLLECTOR_TRANSPORT_FLAGS = Object.freeze([
40
+ Object.freeze({
41
+ name: "ak-collector-repo",
42
+ definition: Object.freeze({
43
+ description: "GitHub owner/repo target for Collector (github.com only; conservative ASCII grammar).",
44
+ type: "string",
45
+ }),
46
+ }),
47
+ Object.freeze({
48
+ name: "ak-collector-pr",
49
+ definition: Object.freeze({
50
+ description: "Optional positive safe-integer pull request number for Collector. Omit when the role will bind from task materials.",
51
+ type: "string",
52
+ }),
53
+ }),
54
+ Object.freeze({
55
+ name: "ak-collector-request-manifest",
56
+ definition: Object.freeze({
57
+ description: "Path to the Collector v1 request manifest JSON file.",
58
+ type: "string",
59
+ }),
60
+ }),
61
+ ]);
30
62
  const observeSchema = collectorObserveArgsSchema;
31
63
  const readSchema = collectorReadArgsSchema;
32
64
  const requestSchema = collectorRequestArgsSchema;
33
65
  const waitSchema = collectorWaitArgsSchema;
66
+ const bindSchema = collectorBindTargetArgsSchema;
34
67
  const outputSchema = collectorOutputArgsSchema;
35
- /** Sole-final at the output execution seam (same shape as other terminating roles). */
36
68
  function buildMethodContext(activation) {
69
+ const pr = activation.ledger.config.prNumber;
37
70
  return [
38
71
  "<collector_method>",
39
72
  `host: github.com`,
40
73
  `repository: ${activation.repository.canonical}`,
41
- `prNumber: ${activation.prNumber}`,
74
+ `prNumber: ${pr === undefined ? "unbound — call ak_collector_bind_target with the role-decided issue/PR before observe" : String(pr)}`,
42
75
  `requests: ${JSON.stringify(activation.manifest.requests.map((request) => ({ id: request.id })))}`,
43
76
  "</collector_method>",
44
77
  ].join("\n");
45
78
  }
79
+ function parsePositiveTicket(raw, label) {
80
+ if (raw === undefined || raw === null)
81
+ return undefined;
82
+ if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1)
83
+ return raw;
84
+ if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim()))
85
+ return Number(raw.trim());
86
+ throw new CollectorTargetBindError(`ak_collector_bind_target ${label} must be a positive safe integer`);
87
+ }
88
+ /**
89
+ * Shared envelope installs business tools and material callback after lifecycle gates.
90
+ * Role module does not self-hook events, setActiveTools, or mode/fork checks (ADR 0018 / #676 E).
91
+ */
46
92
  export function createCollectorRoleRuntime(pi, dependencies, hostActions) {
47
- let activation;
48
- let inputCount = 0;
49
- let lifecycleRegistered = false;
50
93
  let toolsRegistered = false;
51
- let firstDispatchDone = false;
52
- pi.registerFlag("ak-collector-repo", {
53
- description: "GitHub owner/repo target for Collector (github.com only; conservative ASCII grammar). Collector forbids every Skill, including command-only Skills.",
54
- type: "string",
55
- });
56
- pi.registerFlag("ak-collector-pr", {
57
- description: "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",
58
- type: "string",
59
- });
60
- pi.registerFlag("ak-collector-request-manifest", {
61
- description: "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",
62
- type: "string",
63
- });
64
- const ensureLifecycle = () => {
65
- if (lifecycleRegistered)
66
- return;
67
- lifecycleRegistered = true;
68
- pi.on("input", (event, ctx) => {
69
- if (activation === undefined) {
70
- // role not active
71
- return { action: "continue" };
72
- }
73
- if (inputCount >= 1) {
74
- activation.ledger.latchFatal("通进司已拒绝后续输入");
75
- if (process.exitCode === undefined || process.exitCode === 0) {
76
- process.exitCode = 1;
77
- }
78
- console.error("Collector rejected later input");
79
- return { action: "handled" };
80
- }
81
- inputCount += 1;
82
- return {
83
- action: "transform",
84
- text: COLLECTOR_FIXED_KICKOFF,
85
- images: [],
86
- };
87
- });
88
- pi.on("before_agent_start", (event, ctx) => {
89
- if (activation === undefined)
90
- return;
91
- // Detectable ambient instruction resources on the supported prompt surface.
92
- const options = event.systemPromptOptions;
93
- if (options.skills && options.skills.length > 0) {
94
- hostActions.failInfrastructure(activation.ledger.latchFatal("通进司检测到系统提示中的环境 skills"), ctx);
95
- }
96
- if (options.contextFiles && options.contextFiles.length > 0) {
97
- hostActions.failInfrastructure(activation.ledger.latchFatal("通进司检测到系统提示中的环境 context files"), ctx);
98
- }
99
- if (typeof options.appendSystemPrompt === "string" &&
100
- options.appendSystemPrompt.trim().length > 0) {
101
- hostActions.failInfrastructure(activation.ledger.latchFatal("通进司检测到 appendSystemPrompt 漂移"), ctx);
94
+ return {
95
+ async activate(ctx) {
96
+ const soul = (await dependencies.loadSoul()).trim();
97
+ if (soul.length === 0)
98
+ throw new Error("Collector soul is empty");
99
+ // Flags registered by the shared envelope (COLLECTOR_TRANSPORT_FLAGS).
100
+ const repoFlag = pi.getFlag("ak-collector-repo");
101
+ const prFlag = pi.getFlag("ak-collector-pr");
102
+ const requestManifestFlag = pi.getFlag("ak-collector-request-manifest");
103
+ if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
104
+ throw new Error("Collector requires --ak-collector-repo");
102
105
  }
103
- if (event.prompt !== COLLECTOR_FIXED_KICKOFF) {
104
- hostActions.failInfrastructure(activation.ledger.latchFatal("通进司首条提示不是固定开场令"), ctx);
106
+ const repository = parseCollectorRepository(repoFlag);
107
+ // #676 A: PR may be unbound — role binds via ak_collector_bind_target from materials.
108
+ let prNumber;
109
+ if (typeof prFlag === "string" && prFlag.trim().length > 0) {
110
+ prNumber = parseCollectorPrNumber(prFlag);
105
111
  }
106
- if (!firstDispatchDone) {
107
- firstDispatchDone = true;
108
- activation.ledger.recordActivation(activation.clock);
112
+ else if (typeof prFlag === "number") {
113
+ prNumber = parseCollectorPrNumber(prFlag);
109
114
  }
115
+ const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0
116
+ ? await loadCollectorManifest(requestManifestFlag)
117
+ : emptyCollectorManifest();
118
+ const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
119
+ const transport = dependencies.createTransport();
120
+ const ledger = dependencies.createLedger({ repository, prNumber, manifest }, clock, ctx);
110
121
  return {
111
- systemPrompt: [
112
- event.systemPrompt,
113
- "",
114
- "<collector_soul>",
115
- activation.soul,
116
- "</collector_soul>",
117
- "",
118
- buildMethodContext(activation),
119
- ].join("\n"),
122
+ soul,
123
+ repository,
124
+ manifest,
125
+ ledger,
126
+ transport,
127
+ clock,
120
128
  };
121
- });
122
- pi.on("tool_call", (event) => {
123
- if (activation === undefined)
124
- return;
129
+ },
130
+ assembleMaterials(activation, baseSystemPrompt) {
131
+ return [
132
+ baseSystemPrompt,
133
+ "",
134
+ "<collector_soul>",
135
+ activation.soul,
136
+ "</collector_soul>",
137
+ "",
138
+ buildMethodContext(activation),
139
+ ].join("\n");
140
+ },
141
+ onToolCall(activation, event) {
142
+ // Business ledger facts only — allowed-tool / mode gates live on the envelope.
125
143
  if (activation.ledger.fatal) {
126
144
  return {
127
145
  block: true,
128
146
  reason: activation.ledger.fatalReason ?? "通进司致命状态",
129
147
  };
130
148
  }
131
- if (!COLLECTOR_REQUIRED_TOOLS.includes(event.toolName)) {
132
- return {
133
- block: true,
134
- reason: `通进司禁用工具 ${event.toolName}`,
135
- };
136
- }
137
149
  if (event.toolName === COLLECTOR_OUTPUT_TOOL) {
138
150
  activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, event.toolCallId);
139
151
  }
@@ -145,297 +157,246 @@ export function createCollectorRoleRuntime(pi, dependencies, hostActions) {
145
157
  };
146
158
  }
147
159
  return undefined;
148
- });
149
- pi.on("tool_result", (event) => {
150
- if (activation === undefined)
151
- return;
160
+ },
161
+ onToolResult(activation, event) {
152
162
  activation.ledger.completeOperational(event.toolCallId);
153
- });
154
- pi.on("session_shutdown", () => {
155
- if (activation === undefined)
163
+ },
164
+ registerBusinessTools(getActivation) {
165
+ if (toolsRegistered)
156
166
  return;
157
- if (activation.ledger.fatal) {
158
- if (process.exitCode === undefined || process.exitCode === 0) {
159
- process.exitCode = 1;
160
- }
161
- }
162
- });
163
- };
164
- const registerTools = () => {
165
- if (toolsRegistered)
166
- return;
167
- toolsRegistered = true;
168
- pi.registerTool({
169
- name: COLLECTOR_OBSERVE_TOOL,
170
- label: "通进司观察",
171
- description: "抓取配置目标的完整 GitHub PR 证据,存不可变快照入卷。正文在上下文中只给头部摘录加指针;需要头部之外的正文时,用 ak_collector_read 按 evidenceId 开卷;findings 的拆分与归类由你在交件时完成。",
172
- promptSnippet: "抓取配置目标 PR 证据",
173
- parameters: observeSchema,
174
- async execute(toolCallId, _params, signal, _onUpdate, ctx) {
175
- if (activation === undefined) {
176
- throw new Error("通进司未激活");
177
- }
178
- try {
179
- activation.ledger.beginOperational(COLLECTOR_OBSERVE_TOOL, toolCallId);
180
- const { snapshot, contextView } = await activation.ledger.observe(activation.transport, activation.clock, signal);
181
- activation.ledger.completeOperational(toolCallId);
182
- if (snapshot.prState !== "OPEN") {
183
- // Non-OPEN observed as latest complete snapshot is target-state failure at output,
184
- // but observe itself may return the fact. If this is a final observation, still return.
167
+ toolsRegistered = true;
168
+ pi.registerTool({
169
+ name: COLLECTOR_BIND_TARGET_TOOL,
170
+ label: "通进司认票绑定",
171
+ description: "角色判定任务材料后绑定本仓唯一 PR 目标。可提交 prNumber 或 issueNumber(线上关联唯一 PR);显式 --pr 已绑定时无需再调。多义或无法确定时会正确驳回,要求调用方明确 --pr。",
172
+ promptSnippet: "绑定角色判定的 issue/PR 目标",
173
+ parameters: bindSchema,
174
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
175
+ const activation = getActivation();
176
+ if (activation === undefined)
177
+ throw new Error("通进司未激活");
178
+ try {
179
+ activation.ledger.beginOperational(COLLECTOR_BIND_TARGET_TOOL, toolCallId);
180
+ const prNumber = parsePositiveTicket(params.prNumber, "prNumber");
181
+ const issueNumber = parsePositiveTicket(params.issueNumber, "issueNumber");
182
+ if (prNumber === undefined && issueNumber === undefined) {
183
+ throw new CollectorTargetBindError("ak_collector_bind_target requires role-decided prNumber and/or issueNumber");
184
+ }
185
+ let bound = prNumber;
186
+ if (issueNumber !== undefined) {
187
+ const associated = await listPullRequestNumbersByTicket(createGhApiRunner(), {
188
+ owner: activation.repository.owner,
189
+ repo: activation.repository.repo,
190
+ ticketNumber: issueNumber,
191
+ });
192
+ if (associated.length === 0) {
193
+ throw new CollectorTargetBindError(`no PR associated with issue #${issueNumber} in ${activation.repository.canonical}; pass an explicit --pr or a different issueNumber`);
194
+ }
195
+ if (associated.length > 1) {
196
+ throw new CollectorTargetBindError(`multiple PRs associated with issue #${issueNumber}: ${associated.join(", ")}; pass an explicit prNumber or --pr`);
197
+ }
198
+ const fromIssue = associated[0];
199
+ if (prNumber !== undefined && prNumber !== fromIssue) {
200
+ throw new CollectorTargetBindError(`prNumber ${prNumber} conflicts with issue #${issueNumber} association PR ${fromIssue}`);
201
+ }
202
+ bound = fromIssue;
203
+ }
204
+ activation.ledger.bindTarget(bound);
205
+ activation.ledger.completeOperational(toolCallId);
206
+ return {
207
+ content: [{
208
+ type: "text",
209
+ text: `目标已绑定:${activation.repository.canonical}#${bound}`,
210
+ }],
211
+ details: {
212
+ repository: activation.repository.canonical,
213
+ prNumber: bound,
214
+ ...(issueNumber === undefined ? {} : { issueNumber }),
215
+ },
216
+ };
185
217
  }
186
- return {
187
- content: [{
188
- type: "text",
189
- // #641 chain①: model context carries bounded body heads + pointers only.
190
- text: JSON.stringify(contextView),
191
- }],
192
- // #641 the bounded projection is the only provider-visible face on every host
193
- // (Grok/ACP relays tool details as MCP structuredContent); full bodies stay
194
- // in the ledger volume and enter context only by explicit ak_collector_read.
195
- details: contextView,
196
- };
197
- }
198
- catch (error) {
199
- hostActions.failInfrastructure(error, ctx, toolCallId);
200
- }
201
- },
202
- });
203
- pi.registerTool({
204
- name: COLLECTOR_READ_TOOL,
205
- label: "通进司开卷",
206
- description: "按 evidenceId 开卷读取一条已观测材料的全量正文与指针;只在观察头部摘录不足以判读时调用。",
207
- promptSnippet: "按指针开卷读材料",
208
- parameters: readSchema,
209
- async execute(toolCallId, params, _signal, _onUpdate, ctx) {
210
- if (activation === undefined) {
211
- throw new Error("通进司未激活");
212
- }
213
- try {
214
- activation.ledger.beginOperational(COLLECTOR_READ_TOOL, toolCallId);
215
- const record = activation.ledger.getEvidence(params.evidenceId);
216
- if (record === undefined ||
217
- (record.kind !== "review" && record.kind !== "issue_comment" && record.kind !== "review_comment" && record.kind !== "reaction") ||
218
- typeof record.body !== "string") {
219
- throw new CollectorUnknownEvidenceError(params.evidenceId);
218
+ catch (error) {
219
+ if (isCorrectableExecuteError(error))
220
+ throw error;
221
+ hostActions.failInfrastructure(error, ctx, toolCallId);
220
222
  }
221
- const material = projectEvidenceEntryView(record);
222
- activation.ledger.completeOperational(toolCallId);
223
- return {
224
- content: [{
225
- type: "text",
226
- // #641 chain①: full bodies enter provider context only by explicit pointer.
227
- text: JSON.stringify(material),
228
- }],
229
- details: material,
230
- };
231
- }
232
- catch (error) {
233
- if (isCorrectableExecuteError(error))
234
- throw error;
235
- // typed toolCallId books the shared infrastructure fact so settlement can
236
- // tell a read tool's real host failure from a correctable pointer bounce.
237
- hostActions.failInfrastructure(error, ctx, toolCallId);
238
- }
239
- },
240
- });
241
- pi.registerTool({
242
- name: COLLECTOR_REQUEST_TOOL,
243
- label: "通进司请求",
244
- description: "按配置请求体与关联标记,在所引最新快照 HEAD 发一次请求。",
245
- promptSnippet: "按配置发一次请求",
246
- parameters: requestSchema,
247
- async execute(toolCallId, params, signal, _onUpdate, ctx) {
248
- if (activation === undefined) {
249
- throw new Error("通进司未激活");
250
- }
251
- try {
252
- activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
253
- const details = await activation.ledger.request(params, activation.transport, activation.clock, signal);
254
- activation.ledger.completeOperational(toolCallId);
255
- return {
256
- content: [{
257
- type: "text",
258
- text: `请求尝试已记录:request ${params.requestId}`,
259
- }],
260
- details,
261
- };
262
- }
263
- catch (error) {
264
- hostActions.failInfrastructure(error, ctx, toolCallId);
265
- }
266
- },
267
- });
268
- pi.registerTool({
269
- name: COLLECTOR_WAIT_TOOL,
270
- label: "通进司等待",
271
- description: "再观察前等待;单次上限五分钟且不超剩余资格。",
272
- promptSnippet: "资格截止前等待",
273
- parameters: waitSchema,
274
- async execute(toolCallId, params, signal, _onUpdate, ctx) {
275
- if (activation === undefined) {
276
- throw new Error("通进司未激活");
277
- }
278
- try {
279
- activation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
280
- const details = await activation.ledger.wait(params, activation.clock, signal);
281
- activation.ledger.completeOperational(toolCallId);
282
- return {
283
- content: [{
284
- type: "text",
285
- text: `已等待 ${String(details.effectiveMs)}ms`,
286
- }],
287
- details,
288
- };
289
- }
290
- catch (error) {
291
- hostActions.failInfrastructure(error, ctx, toolCallId);
292
- }
293
- },
294
- });
295
- pi.registerTool({
296
- name: COLLECTOR_OUTPUT_TOOL,
297
- label: "通进司输出",
298
- description: "观察完成后提交;回执由 runtime 组装。正常完工提交空对象 {}(如需报 finding,填 findings 指针数组);仅在基础设施真实失败时才可填 infrastructureFailure,无失败时必须省略该字段。",
299
- promptSnippet: "提交通进司回执",
300
- // #641 chain②: the seat owns the normal-completion decision. The probe
301
- // runs the exact receipt assembly (validation only, no accept side
302
- // effects); success ⇒ machine-verified normal completion ⇒ bounce.
303
- bounceInfrastructureDeclaration(params) {
304
- if (activation === undefined)
305
- return undefined;
306
- try {
307
- buildCollectorReceipt(activation.ledger, params, activation.clock);
308
- }
309
- catch {
310
- return undefined;
311
- }
312
- return new CollectorNormalCompletionDeclarationError();
313
- },
314
- parameters: outputSchema,
315
- async execute(toolCallId, params, _signal, _onUpdate, ctx) {
316
- if (activation === undefined) {
317
- throw new Error("通进司未激活");
318
- }
319
- try {
320
- activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, toolCallId);
321
- const receipt = buildCollectorReceipt(activation.ledger, params, activation.clock);
322
- activation.ledger.recordOutputCandidate();
323
- const acceptedDetails = receipt;
324
- return {
325
- content: [{
326
- type: "text",
327
- text: COLLECTOR_ACCEPTED_TEXT,
328
- }],
329
- details: acceptedDetails,
330
- terminate: true,
331
- };
332
- }
333
- catch (error) {
334
- // Output validation errors are model-visible rejections when non-fatal.
335
- if (error instanceof Error &&
336
- error.collectorFatal === true) {
223
+ finally {
224
+ try {
225
+ activation.ledger.completeOperational(toolCallId);
226
+ }
227
+ catch {
228
+ // already completed or not begun
229
+ }
230
+ }
231
+ },
232
+ });
233
+ pi.registerTool({
234
+ name: COLLECTOR_OBSERVE_TOOL,
235
+ label: "通进司观察",
236
+ description: "抓取配置目标的完整 GitHub PR 证据,存不可变快照入卷。正文在上下文中只给头部摘录加指针;需要头部之外的正文时,用 ak_collector_read 按 evidenceId 开卷;findings 的拆分与归类由你在交件时完成。目标未绑定前须先 ak_collector_bind_target。",
237
+ promptSnippet: "抓取配置目标 PR 证据",
238
+ parameters: observeSchema,
239
+ async execute(toolCallId, _params, signal, _onUpdate, ctx) {
240
+ const activation = getActivation();
241
+ if (activation === undefined)
242
+ throw new Error("通进司未激活");
243
+ try {
244
+ activation.ledger.beginOperational(COLLECTOR_OBSERVE_TOOL, toolCallId);
245
+ const { snapshot, contextView } = await activation.ledger.observe(activation.transport, activation.clock, signal);
246
+ activation.ledger.completeOperational(toolCallId);
247
+ return {
248
+ content: [{
249
+ type: "text",
250
+ text: JSON.stringify(contextView),
251
+ }],
252
+ details: contextView,
253
+ };
254
+ }
255
+ catch (error) {
256
+ if (isCorrectableExecuteError(error))
257
+ throw error;
337
258
  hostActions.failInfrastructure(error, ctx, toolCallId);
338
259
  }
339
- throw error;
340
- }
341
- finally {
342
- activation.ledger.completeOperational(toolCallId);
343
- }
344
- },
345
- });
346
- };
347
- return {
348
- async activate(ctx, event) {
349
- activation = undefined;
350
- ensureLifecycle();
351
- if (ctx.mode !== "print" && ctx.mode !== "json") {
352
- throw new Error(`Collector supports only print or json mode (got ${ctx.mode})`);
353
- }
354
- if (event.reason === "fork" ||
355
- event.reason === "reload") {
356
- throw new Error(`Collector does not support session_start reason ${event.reason}`);
357
- }
358
- const soul = (await dependencies.loadSoul()).trim();
359
- if (soul.length === 0)
360
- throw new Error("Collector soul is empty");
361
- const repoFlag = pi.getFlag("ak-collector-repo");
362
- const prFlag = pi.getFlag("ak-collector-pr");
363
- const requestManifestFlag = pi.getFlag("ak-collector-request-manifest");
364
- if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
365
- throw new Error("Collector requires --ak-collector-repo");
366
- }
367
- if (typeof prFlag !== "string" && typeof prFlag !== "number") {
368
- throw new Error("Collector requires --ak-collector-pr");
369
- }
370
- const repository = parseCollectorRepository(repoFlag);
371
- const prNumber = parseCollectorPrNumber(prFlag);
372
- const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0
373
- ? await loadCollectorManifest(requestManifestFlag)
374
- : emptyCollectorManifest();
375
- // Detectable ambient command surface (skills/templates) when exposed.
376
- const commands = pi.getCommands?.() ?? [];
377
- const ambientCommands = commands.filter((command) => {
378
- const name = command.name.toLowerCase();
379
- return (name.includes("skill") ||
380
- name.includes("prompt") ||
381
- name.startsWith("template"));
260
+ },
261
+ });
262
+ pi.registerTool({
263
+ name: COLLECTOR_READ_TOOL,
264
+ label: "通进司开卷",
265
+ description: "按 evidenceId 开卷读取一条已观测材料的全量正文与指针;只在观察头部摘录不足以判读时调用。",
266
+ promptSnippet: "按指针开卷读材料",
267
+ parameters: readSchema,
268
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
269
+ const activation = getActivation();
270
+ if (activation === undefined)
271
+ throw new Error("通进司未激活");
272
+ try {
273
+ activation.ledger.beginOperational(COLLECTOR_READ_TOOL, toolCallId);
274
+ const record = activation.ledger.getEvidence(params.evidenceId);
275
+ if (record === undefined ||
276
+ (record.kind !== "review" && record.kind !== "issue_comment" && record.kind !== "review_comment" && record.kind !== "reaction") ||
277
+ typeof record.body !== "string") {
278
+ throw new CollectorUnknownEvidenceError(params.evidenceId);
279
+ }
280
+ const material = projectEvidenceEntryView(record);
281
+ activation.ledger.completeOperational(toolCallId);
282
+ return {
283
+ content: [{
284
+ type: "text",
285
+ text: JSON.stringify(material),
286
+ }],
287
+ details: material,
288
+ };
289
+ }
290
+ catch (error) {
291
+ if (isCorrectableExecuteError(error))
292
+ throw error;
293
+ hostActions.failInfrastructure(error, ctx, toolCallId);
294
+ }
295
+ },
296
+ });
297
+ pi.registerTool({
298
+ name: COLLECTOR_REQUEST_TOOL,
299
+ label: "通进司请求",
300
+ description: "按配置请求体与关联标记,在所引最新快照 HEAD 发一次请求。",
301
+ promptSnippet: "按配置发一次请求",
302
+ parameters: requestSchema,
303
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
304
+ const activation = getActivation();
305
+ if (activation === undefined)
306
+ throw new Error("通进司未激活");
307
+ try {
308
+ activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
309
+ const details = await activation.ledger.request(params, activation.transport, activation.clock, signal);
310
+ activation.ledger.completeOperational(toolCallId);
311
+ return {
312
+ content: [{
313
+ type: "text",
314
+ text: `请求尝试已记录:request ${params.requestId}`,
315
+ }],
316
+ details,
317
+ };
318
+ }
319
+ catch (error) {
320
+ if (isCorrectableExecuteError(error))
321
+ throw error;
322
+ hostActions.failInfrastructure(error, ctx, toolCallId);
323
+ }
324
+ },
325
+ });
326
+ pi.registerTool({
327
+ name: COLLECTOR_WAIT_TOOL,
328
+ label: "通进司等待",
329
+ description: "再观察前等待;单次上限五分钟且不超剩余资格。",
330
+ promptSnippet: "资格截止前等待",
331
+ parameters: waitSchema,
332
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
333
+ const activation = getActivation();
334
+ if (activation === undefined)
335
+ throw new Error("通进司未激活");
336
+ try {
337
+ activation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
338
+ const details = await activation.ledger.wait(params, activation.clock, signal);
339
+ activation.ledger.completeOperational(toolCallId);
340
+ return {
341
+ content: [{
342
+ type: "text",
343
+ text: `已等待 ${String(details.effectiveMs)}ms`,
344
+ }],
345
+ details,
346
+ };
347
+ }
348
+ catch (error) {
349
+ hostActions.failInfrastructure(error, ctx, toolCallId);
350
+ }
351
+ },
352
+ });
353
+ pi.registerTool({
354
+ name: COLLECTOR_OUTPUT_TOOL,
355
+ label: "通进司输出",
356
+ description: "观察完成后提交;回执由 runtime 组装。正常完工提交空对象 {}(如需报 finding,填 findings 指针数组);仅在基础设施真实失败时才可填 infrastructureFailure,无失败时必须省略该字段。",
357
+ promptSnippet: "提交通进司回执",
358
+ bounceInfrastructureDeclaration(params) {
359
+ const activation = getActivation();
360
+ if (activation === undefined)
361
+ return undefined;
362
+ try {
363
+ buildCollectorReceipt(activation.ledger, params, activation.clock);
364
+ }
365
+ catch {
366
+ return undefined;
367
+ }
368
+ return new CollectorNormalCompletionDeclarationError();
369
+ },
370
+ parameters: outputSchema,
371
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
372
+ const activation = getActivation();
373
+ if (activation === undefined)
374
+ throw new Error("通进司未激活");
375
+ try {
376
+ activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, toolCallId);
377
+ const receipt = buildCollectorReceipt(activation.ledger, params, activation.clock);
378
+ activation.ledger.recordOutputCandidate();
379
+ return {
380
+ content: [{
381
+ type: "text",
382
+ text: COLLECTOR_ACCEPTED_TEXT,
383
+ }],
384
+ details: receipt,
385
+ terminate: true,
386
+ };
387
+ }
388
+ catch (error) {
389
+ if (error instanceof Error &&
390
+ error.collectorFatal === true) {
391
+ hostActions.failInfrastructure(error, ctx, toolCallId);
392
+ }
393
+ throw error;
394
+ }
395
+ finally {
396
+ activation.ledger.completeOperational(toolCallId);
397
+ }
398
+ },
382
399
  });
383
- if (ambientCommands.length > 0) {
384
- throw new Error(`Collector detected ambient instruction commands: ${ambientCommands.map((c) => c.name).join(", ")}`);
385
- }
386
- // Fail closed if a required name is already occupied before Collector registers.
387
- const preExisting = pi.getAllTools();
388
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
389
- const prior = preExisting.filter((tool) => tool.name === required);
390
- if (prior.length > 0) {
391
- throw new Error(`Collector required tool name collision: ${required}`);
392
- }
393
- }
394
- registerTools();
395
- const allTools = pi.getAllTools();
396
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
397
- const matches = allTools.filter((tool) => tool.name === required);
398
- if (matches.length === 0) {
399
- throw new Error(`Collector required tool missing: ${required}`);
400
- }
401
- if (matches.length > 1) {
402
- throw new Error(`Collector required tool name collision: ${required}`);
403
- }
404
- const tool = matches[0];
405
- if (dependencies.packageExtensionPath !== undefined &&
406
- tool.sourceInfo?.path !== undefined &&
407
- tool.sourceInfo.path !== dependencies.packageExtensionPath &&
408
- !tool.sourceInfo.path.includes("role-runtime")) {
409
- throw new Error(`Collector required tool ${required} is overridden by ${tool.sourceInfo.path}`);
410
- }
411
- }
412
- pi.setActiveTools([...COLLECTOR_REQUIRED_TOOLS]);
413
- const active = new Set(pi.getActiveTools());
414
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
415
- if (!active.has(required)) {
416
- throw new Error(`Collector failed to activate required tool ${required}`);
417
- }
418
- }
419
- for (const name of active) {
420
- if (!COLLECTOR_REQUIRED_TOOLS.includes(name)) {
421
- throw new Error(`Collector active tool surface includes unexpected ${name}`);
422
- }
423
- }
424
- const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
425
- const transport = dependencies.createTransport();
426
- const ledger = dependencies.createLedger({ repository, prNumber, manifest }, clock, ctx);
427
- if (ledger.activationRecorded) {
428
- firstDispatchDone = true;
429
- }
430
- activation = {
431
- soul,
432
- repository,
433
- prNumber,
434
- manifest,
435
- ledger,
436
- transport,
437
- clock,
438
- };
439
400
  },
440
401
  };
441
402
  }