@akagilnc/pi-workflow-roles 0.1.3758 → 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 (46) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +3 -2
  3. package/dist/acp-host/production-host.js +1389 -751
  4. package/dist/collector-config.js +0 -1
  5. package/dist/collector-github.js +199 -2
  6. package/dist/collector-identity.js +128 -41
  7. package/dist/collector-ledger.js +38 -9
  8. package/dist/collector-receipt.js +19 -7
  9. package/dist/collector-role.js +330 -369
  10. package/dist/collector-target.js +169 -0
  11. package/dist/collector-tool-schemas.js +51 -14
  12. package/dist/package-contracts/collector-output.js +32 -0
  13. package/dist/package-contracts/terminating-infrastructure.js +13 -12
  14. package/dist/pi/role-turn-host.js +1 -2
  15. package/dist/public-cli/github-remote.js +45 -0
  16. package/dist/public-cli/invocation.js +53 -54
  17. package/dist/public-cli/main.js +615 -148
  18. package/dist/public-cli/option-definitions.js +6 -4
  19. package/dist/public-cli/run-lifecycle.js +3 -3
  20. package/dist/public-cli/settlement.js +83 -6
  21. package/dist/role-runtime.js +137 -7
  22. package/dist/submission-correctable-error.js +24 -0
  23. package/extensions/role-runtime.ts +0 -1
  24. package/package.json +1 -1
  25. package/src/acp-host/role-envelope.ts +8 -22
  26. package/src/collector-config.ts +0 -1
  27. package/src/collector-github.ts +236 -2
  28. package/src/collector-identity.ts +148 -40
  29. package/src/collector-ledger.ts +48 -10
  30. package/src/collector-receipt.ts +33 -14
  31. package/src/collector-role.ts +376 -450
  32. package/src/collector-target.ts +207 -0
  33. package/src/collector-tool-schemas.ts +62 -15
  34. package/src/host-contracts.ts +2 -1
  35. package/src/package-contracts/collector-output.ts +72 -0
  36. package/src/package-contracts/terminating-infrastructure.ts +24 -13
  37. package/src/pi/role-turn-host.ts +1 -2
  38. package/src/public-cli/cli.ts +10 -1
  39. package/src/public-cli/collector-run.ts +3 -2
  40. package/src/public-cli/github-remote.ts +45 -0
  41. package/src/public-cli/invocation.ts +60 -59
  42. package/src/public-cli/option-definitions.ts +6 -4
  43. package/src/public-cli/run-lifecycle.ts +2 -2
  44. package/src/public-cli/settlement.ts +82 -6
  45. package/src/role-runtime.ts +166 -13
  46. package/src/submission-correctable-error.ts +38 -0
@@ -13,9 +13,88 @@ var __export = (target, all) => {
13
13
  __defProp(target, name, { get: all[name], enumerable: true });
14
14
  };
15
15
 
16
+ // src/collector-config.ts
17
+ import { createHash } from "node:crypto";
18
+ import { readFile as readFile2 } from "node:fs/promises";
19
+ function fail(message, cause) {
20
+ throw new Error(message, cause === void 0 ? void 0 : { cause });
21
+ }
22
+ function conservativeAscii(input) {
23
+ for (let i = 0; i < input.length; i += 1) {
24
+ const code = input.charCodeAt(i);
25
+ if (code <= 31 || code === 127 || code > 127) return false;
26
+ }
27
+ return true;
28
+ }
29
+ function parseCollectorRepository(raw) {
30
+ if (typeof raw !== "string" || raw.trim() !== raw || raw.length === 0) fail("Collector repository must be a string owner/repo");
31
+ if (!conservativeAscii(raw) || raw.includes("://") || /[?#@%\\ ]/.test(raw)) fail("Collector repository rejects URL syntax and non-identity bytes");
32
+ const parts = raw.split("/");
33
+ if (parts.length !== 2) fail("Collector repository must contain exactly one '/' separating owner and repo");
34
+ const [ownerDisplay, repoDisplay] = parts;
35
+ if (!COLLECTOR_OWNER_PATTERN.test(ownerDisplay) || !COLLECTOR_REPO_PATTERN.test(repoDisplay)) fail("Collector repository does not match the conservative owner/repo grammar");
36
+ const owner = ownerDisplay.toLowerCase();
37
+ const repo = repoDisplay.toLowerCase();
38
+ return { display: raw, canonical: `${owner}/${repo}`, owner, repo };
39
+ }
40
+ function parseCollectorPrNumber(raw) {
41
+ if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw)) fail("Collector pull request number must be a positive safe integer string");
42
+ if (typeof raw !== "string" && typeof raw !== "number") fail("Collector pull request number is required");
43
+ const value = Number(raw);
44
+ if (!Number.isSafeInteger(value) || value < 1) fail("Collector pull request number must be a positive safe integer");
45
+ return value;
46
+ }
47
+ function record(value) {
48
+ return typeof value === "object" && value !== null && !Array.isArray(value);
49
+ }
50
+ function canonicalManifest(requests) {
51
+ return `${JSON.stringify({ requests: requests.map((request) => ({ id: request.id, body: request.requestBody })) })}
52
+ `;
53
+ }
54
+ function emptyCollectorManifest() {
55
+ const canonicalJson2 = canonicalManifest([]);
56
+ return { requests: [], canonicalJson: canonicalJson2, digest: createHash("sha256").update(canonicalJson2).digest("hex") };
57
+ }
58
+ async function loadCollectorManifest(path) {
59
+ let bytes;
60
+ try {
61
+ bytes = await readFile2(path);
62
+ } catch (error) {
63
+ fail(`Collector request manifest is unreadable at ${path}`, error);
64
+ }
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
68
+ } catch (error) {
69
+ fail("Collector request manifest must be UTF-8 JSON", error);
70
+ }
71
+ if (!record(parsed)) fail("Collector request manifest must be an object");
72
+ const rawRequests = parsed.requests ?? [];
73
+ if (!Array.isArray(rawRequests)) fail("Collector request manifest requests must be an array");
74
+ const requests = [];
75
+ const ids = /* @__PURE__ */ new Set();
76
+ for (const [index, item] of rawRequests.entries()) {
77
+ if (!record(item) || typeof item.id !== "string" || item.id.length === 0 || typeof item.body !== "string" || item.body.trim() === "") fail(`Collector request manifest requests[${index}] is invalid`);
78
+ if (ids.has(item.id)) fail(`Collector request manifest has duplicate request id "${item.id}"`);
79
+ ids.add(item.id);
80
+ requests.push({ id: item.id, requestBody: item.body });
81
+ }
82
+ const canonicalJson2 = canonicalManifest(requests);
83
+ return { requests, canonicalJson: canonicalJson2, digest: createHash("sha256").update(canonicalJson2).digest("hex"), sourcePath: path };
84
+ }
85
+ var COLLECTOR_HOST, COLLECTOR_OWNER_PATTERN, COLLECTOR_REPO_PATTERN;
86
+ var init_collector_config = __esm({
87
+ "src/collector-config.ts"() {
88
+ "use strict";
89
+ COLLECTOR_HOST = "github.com";
90
+ COLLECTOR_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
91
+ COLLECTOR_REPO_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
92
+ }
93
+ });
94
+
16
95
  // src/collector-github.ts
17
96
  import { spawn } from "node:child_process";
18
- import { createHash } from "node:crypto";
97
+ import { createHash as createHash2 } from "node:crypto";
19
98
  function isRecord(value) {
20
99
  return typeof value === "object" && value !== null && !Array.isArray(value);
21
100
  }
@@ -49,6 +128,178 @@ function parseJson(text, label) {
49
128
  throw new Error(`GitHub ${label} returned malformed JSON`, { cause: error });
50
129
  }
51
130
  }
131
+ function parsePullRequestNumberList(raw, label) {
132
+ if (!Array.isArray(raw)) {
133
+ throw new Error(`GitHub ${label} payload is not a list`);
134
+ }
135
+ const numbers = [];
136
+ for (const item of raw) {
137
+ if (!isRecord(item)) {
138
+ throw new Error(`GitHub ${label} payload contains a non-object pull request entry`);
139
+ }
140
+ try {
141
+ numbers.push(parseCollectorPrNumber(item["number"]));
142
+ } catch (error) {
143
+ throw new Error(`GitHub ${label} payload contains an invalid pull request number`, {
144
+ cause: error
145
+ });
146
+ }
147
+ }
148
+ return numbers;
149
+ }
150
+ async function listPullRequestNumbersByHead(runner, input) {
151
+ const head = `${input.headOwner}:${input.headRef}`;
152
+ const path = `/repos/${input.owner}/${input.repo}/pulls?head=${encodeURIComponent(head)}&state=all&per_page=100`;
153
+ const response = await runner(
154
+ ["api", "--hostname", "github.com", "--include", "-X", "GET", path],
155
+ input.signal === void 0 ? {} : { signal: input.signal }
156
+ );
157
+ if (response.status < 200 || response.status >= 300) {
158
+ throw new Error(`GitHub ${path} failed with HTTP ${response.status}`, {
159
+ cause: {
160
+ endpoint: path,
161
+ status: response.status,
162
+ headers: response.headers,
163
+ body: response.bodyText
164
+ }
165
+ });
166
+ }
167
+ return parsePullRequestNumberList(parseJson(response.bodyText, path), path);
168
+ }
169
+ async function listPullRequestNumbersByCommit(runner, input) {
170
+ const path = `/repos/${input.owner}/${input.repo}/commits/${encodeURIComponent(input.commitSha)}/pulls`;
171
+ const response = await runner(
172
+ ["api", "--hostname", "github.com", "--include", "-X", "GET", path],
173
+ input.signal === void 0 ? {} : { signal: input.signal }
174
+ );
175
+ if (response.status < 200 || response.status >= 300) {
176
+ throw new Error(`GitHub ${path} failed with HTTP ${response.status}`, {
177
+ cause: {
178
+ endpoint: path,
179
+ status: response.status,
180
+ headers: response.headers,
181
+ body: response.bodyText
182
+ }
183
+ });
184
+ }
185
+ return parsePullRequestNumberList(parseJson(response.bodyText, path), path);
186
+ }
187
+ async function listPullRequestNumbersByTicket(runner, input) {
188
+ const issuePath = `/repos/${input.owner}/${input.repo}/issues/${input.ticketNumber}`;
189
+ const issueResponse = await runner(
190
+ ["api", "--hostname", "github.com", "--include", "-X", "GET", issuePath],
191
+ input.signal === void 0 ? {} : { signal: input.signal }
192
+ );
193
+ if (issueResponse.status === 404) return [];
194
+ if (issueResponse.status < 200 || issueResponse.status >= 300) {
195
+ throw new Error(`GitHub ${issuePath} failed with HTTP ${issueResponse.status}`, {
196
+ cause: {
197
+ endpoint: issuePath,
198
+ status: issueResponse.status,
199
+ headers: issueResponse.headers,
200
+ body: issueResponse.bodyText
201
+ }
202
+ });
203
+ }
204
+ const issueRaw = parseJson(issueResponse.bodyText, issuePath);
205
+ if (!isRecord(issueRaw)) {
206
+ throw new Error(`GitHub ${issuePath} payload is not an object`);
207
+ }
208
+ if (Object.hasOwn(issueRaw, "pull_request")) {
209
+ return [parseCollectorPrNumber(issueRaw["number"] ?? input.ticketNumber)];
210
+ }
211
+ const query = `query($owner: String!, $repo: String!, $number: Int!) {
212
+ repository(owner: $owner, name: $repo) {
213
+ issue(number: $number) {
214
+ closedByPullRequestsReferences(first: 50) { nodes { number } }
215
+ timelineItems(first: 100, itemTypes: [CROSS_REFERENCED_EVENT, CONNECTED_EVENT]) {
216
+ nodes {
217
+ __typename
218
+ ... on CrossReferencedEvent {
219
+ source { ... on PullRequest { number } }
220
+ }
221
+ ... on ConnectedEvent {
222
+ subject { ... on PullRequest { number } }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }`;
229
+ const args = [
230
+ "api",
231
+ "graphql",
232
+ "--hostname",
233
+ "github.com",
234
+ "--include",
235
+ "-f",
236
+ `query=${query}`,
237
+ "-f",
238
+ `owner=${input.owner}`,
239
+ "-f",
240
+ `repo=${input.repo}`,
241
+ "-F",
242
+ `number=${input.ticketNumber}`
243
+ ];
244
+ const gqlResponse = await runner(
245
+ args,
246
+ input.signal === void 0 ? {} : { signal: input.signal }
247
+ );
248
+ if (gqlResponse.status < 200 || gqlResponse.status >= 300) {
249
+ throw new Error(`GitHub GraphQL issue\u2192PR failed with HTTP ${gqlResponse.status}`, {
250
+ cause: {
251
+ endpoint: "graphql",
252
+ status: gqlResponse.status,
253
+ headers: gqlResponse.headers,
254
+ body: gqlResponse.bodyText
255
+ }
256
+ });
257
+ }
258
+ let payload;
259
+ try {
260
+ payload = JSON.parse(gqlResponse.bodyText);
261
+ } catch (error) {
262
+ throw new Error("GitHub GraphQL issue\u2192PR returned malformed JSON", { cause: error });
263
+ }
264
+ if (!isRecord(payload)) {
265
+ throw new Error("GitHub GraphQL issue\u2192PR payload is not an object");
266
+ }
267
+ if (payload.errors !== void 0) {
268
+ throw new Error(`GitHub GraphQL issue\u2192PR errors: ${JSON.stringify(payload.errors).slice(0, 600)}`, {
269
+ cause: { body: gqlResponse.bodyText, errors: payload.errors }
270
+ });
271
+ }
272
+ const data = payload.data;
273
+ if (!isRecord(data)) return [];
274
+ const repository = data["repository"];
275
+ if (!isRecord(repository)) return [];
276
+ const issue = repository["issue"];
277
+ if (!isRecord(issue)) return [];
278
+ const numbers = [];
279
+ const closedBy = issue["closedByPullRequestsReferences"];
280
+ if (isRecord(closedBy) && Array.isArray(closedBy["nodes"])) {
281
+ for (const node of closedBy["nodes"]) {
282
+ if (isRecord(node) && typeof node["number"] === "number") {
283
+ numbers.push(parseCollectorPrNumber(node["number"]));
284
+ }
285
+ }
286
+ }
287
+ const timeline = issue["timelineItems"];
288
+ if (isRecord(timeline) && Array.isArray(timeline["nodes"])) {
289
+ for (const node of timeline["nodes"]) {
290
+ if (!isRecord(node)) continue;
291
+ const source = node["source"];
292
+ if (isRecord(source) && typeof source["number"] === "number") {
293
+ numbers.push(parseCollectorPrNumber(source["number"]));
294
+ }
295
+ const subject = node["subject"];
296
+ if (isRecord(subject) && typeof subject["number"] === "number") {
297
+ numbers.push(parseCollectorPrNumber(subject["number"]));
298
+ }
299
+ }
300
+ }
301
+ return [...new Set(numbers)];
302
+ }
52
303
  function commentFailureCause(error) {
53
304
  return {
54
305
  name: error instanceof Error ? error.name : typeof error,
@@ -89,11 +340,13 @@ function normalizePullRequest(raw) {
89
340
  throw new Error("GitHub pull request payload missing head.sha");
90
341
  }
91
342
  const number = requireNumber(raw["number"], "number");
92
- const state = requireString(raw["state"], "state").toUpperCase();
343
+ const mergedFlag = raw["merged"] === true || typeof raw["merged_at"] === "string" && raw["merged_at"].length > 0;
344
+ const rawState = requireString(raw["state"], "state").toUpperCase();
345
+ const state = mergedFlag ? "MERGED" : rawState;
93
346
  const htmlUrl = typeof raw["html_url"] === "string" ? raw["html_url"] : `https://github.com/unknown/unknown/pull/${number}`;
94
347
  return {
95
348
  number,
96
- state: state === "OPEN" || state === "open" ? "OPEN" : state,
349
+ state,
97
350
  headOid: head["sha"],
98
351
  ...typeof raw["updated_at"] === "string" ? { updatedAt: raw["updated_at"] } : {},
99
352
  url: htmlUrl,
@@ -483,7 +736,7 @@ function createGhCollectorGitHubTransport(runner = createGhApiRunner()) {
483
736
  }
484
737
  function buildCollectorRequestMarker(input) {
485
738
  const prefix = input.manifestDigest.slice(0, 12);
486
- const requestMarkerId = createHash("sha256").update(input.requestId).digest("hex");
739
+ const requestMarkerId = createHash2("sha256").update(input.requestId).digest("hex");
487
740
  return `<!-- ak-collector:v1 manifest=${prefix} request=${requestMarkerId} head=${input.headOid} -->`;
488
741
  }
489
742
  function buildCollectorRequestBody(input) {
@@ -498,6 +751,7 @@ var commentFailureEvidence;
498
751
  var init_collector_github = __esm({
499
752
  "src/collector-github.ts"() {
500
753
  "use strict";
754
+ init_collector_config();
501
755
  commentFailureEvidence = 0;
502
756
  }
503
757
  });
@@ -1370,7 +1624,7 @@ var init_sitian_contracts = __esm({
1370
1624
  });
1371
1625
 
1372
1626
  // src/sitian-appender.ts
1373
- import { createHash as createHash2, randomUUID } from "node:crypto";
1627
+ import { createHash as createHash3, randomUUID } from "node:crypto";
1374
1628
  import {
1375
1629
  appendFileSync,
1376
1630
  existsSync,
@@ -1386,7 +1640,7 @@ function errorCodeOf(error) {
1386
1640
  return error.code;
1387
1641
  }
1388
1642
  function identityClaimPath(recordFile, identity) {
1389
- const digest = createHash2("sha256").update(identity, "utf8").digest("hex");
1643
+ const digest = createHash3("sha256").update(identity, "utf8").digest("hex");
1390
1644
  return `${recordFile}.id-${digest}`;
1391
1645
  }
1392
1646
  function createExclusiveFile(path, contents) {
@@ -1530,7 +1784,7 @@ function resolveSitianRecordPathInLedger(input, ledgerHome) {
1530
1784
  } else {
1531
1785
  subjectStr = JSON.stringify(input.subject);
1532
1786
  }
1533
- const digest = createHash2("sha256").update(subjectStr).digest("hex").slice(0, 32);
1787
+ const digest = createHash3("sha256").update(subjectStr).digest("hex").slice(0, 32);
1534
1788
  sessionDir = join6(bookDir, category, digest);
1535
1789
  } else {
1536
1790
  sessionDir = join6(bookDir, category);
@@ -1594,7 +1848,7 @@ var init_sitian_appender = __esm({
1594
1848
 
1595
1849
  // src/sitian-reader.ts
1596
1850
  import { existsSync as existsSync2 } from "node:fs";
1597
- import { readFile as readFile2 } from "node:fs/promises";
1851
+ import { readFile as readFile3 } from "node:fs/promises";
1598
1852
  function isRecord4(value) {
1599
1853
  return typeof value === "object" && value !== null && !Array.isArray(value);
1600
1854
  }
@@ -1602,7 +1856,7 @@ async function readSitianRecords(recordFile) {
1602
1856
  if (!existsSync2(recordFile)) {
1603
1857
  return { records: [], diagnostics: [] };
1604
1858
  }
1605
- const text = await readFile2(recordFile, "utf8");
1859
+ const text = await readFile3(recordFile, "utf8");
1606
1860
  const lines = text.split("\n");
1607
1861
  const records2 = [];
1608
1862
  const diagnostics = [];
@@ -1666,7 +1920,7 @@ __export(role_turn_host_exports, {
1666
1920
  });
1667
1921
  import { execFile, spawn as spawn3 } from "node:child_process";
1668
1922
  import { constants } from "node:fs";
1669
- import { access, appendFile, readFile as readFile3, realpath as realpath2 } from "node:fs/promises";
1923
+ import { access, appendFile, readFile as readFile4, realpath as realpath2 } from "node:fs/promises";
1670
1924
  import { delimiter, isAbsolute as isAbsolute3, join as join7, resolve as resolve6 } from "node:path";
1671
1925
  import { platform } from "node:process";
1672
1926
  import { promisify } from "node:util";
@@ -1727,8 +1981,7 @@ function buildActivationFlagArgs(activation) {
1727
1981
  "collector",
1728
1982
  "--ak-collector-repo",
1729
1983
  activation.repo,
1730
- "--ak-collector-pr",
1731
- activation.pr,
1984
+ ...activation.pr === void 0 ? [] : ["--ak-collector-pr", activation.pr],
1732
1985
  ...activation.requestManifestPath === void 0 ? [] : ["--ak-collector-request-manifest", activation.requestManifestPath]
1733
1986
  ];
1734
1987
  case "doctor":
@@ -1975,7 +2228,7 @@ ${paths.join("\n")}`
1975
2228
  }
1976
2229
  async function appendPiSessionCustomEntry(authority, principal, customType, data) {
1977
2230
  const { sessionFile } = authority.decode(principal);
1978
- const text = await readFile3(sessionFile, "utf8");
2231
+ const text = await readFile4(sessionFile, "utf8");
1979
2232
  let parentId = null;
1980
2233
  for (const line2 of text.trim().split("\n").filter(Boolean)) {
1981
2234
  const entry = JSON.parse(line2);
@@ -2024,7 +2277,7 @@ var init_role_turn_host = __esm({
2024
2277
  });
2025
2278
 
2026
2279
  // src/run-ticket-number.ts
2027
- import { readFile as readFile4 } from "node:fs/promises";
2280
+ import { readFile as readFile5 } from "node:fs/promises";
2028
2281
  import { join as join8 } from "node:path";
2029
2282
  function isEnoent(error) {
2030
2283
  return error instanceof Error && "code" in error && error.code === "ENOENT";
@@ -2038,7 +2291,7 @@ function ticketFromRecord(record4) {
2038
2291
  }
2039
2292
  async function readPageTicketNumber(runDirectory, page) {
2040
2293
  try {
2041
- const raw = JSON.parse(await readFile4(join8(runDirectory, page), "utf8"));
2294
+ const raw = JSON.parse(await readFile5(join8(runDirectory, page), "utf8"));
2042
2295
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
2043
2296
  return void 0;
2044
2297
  }
@@ -2058,9 +2311,9 @@ var init_run_ticket_number = __esm({
2058
2311
  });
2059
2312
 
2060
2313
  // src/sha256.ts
2061
- import { createHash as createHash3 } from "node:crypto";
2314
+ import { createHash as createHash4 } from "node:crypto";
2062
2315
  function sha256Hex(bytes) {
2063
- return createHash3("sha256").update(bytes).digest("hex");
2316
+ return createHash4("sha256").update(bytes).digest("hex");
2064
2317
  }
2065
2318
  var init_sha256 = __esm({
2066
2319
  "src/sha256.ts"() {
@@ -2093,10 +2346,16 @@ function validateAcceptedCollectorReceipt(value) {
2093
2346
  materials: records(safeGet(group, "materials")),
2094
2347
  findings: records(safeGet(group, "findings"))
2095
2348
  }));
2349
+ const unfinishedRaw = safeGet(value, "unfinishedReasons");
2350
+ const unfinishedReasons = strings(unfinishedRaw);
2351
+ const submissionProjection = projectSubmissionProjection(safeGet(value, "submissionProjection"));
2352
+ const prStateRaw = safeGet(value, "prState");
2353
+ const prState = typeof prStateRaw === "string" ? prStateRaw : void 0;
2096
2354
  return {
2097
2355
  host: safeGet(value, "host"),
2098
2356
  repository: safeGet(value, "repository"),
2099
2357
  prNumber: safeGet(value, "prNumber"),
2358
+ ...prState === void 0 ? {} : { prState },
2100
2359
  manifestDigest: safeGet(value, "manifestDigest"),
2101
2360
  activationTime: safeGet(value, "activationTime"),
2102
2361
  deadlineTime: safeGet(value, "deadlineTime"),
@@ -2104,6 +2363,8 @@ function validateAcceptedCollectorReceipt(value) {
2104
2363
  finalSnapshotId: safeGet(value, "finalSnapshotId"),
2105
2364
  targetHead: safeGet(value, "targetHead"),
2106
2365
  groups,
2366
+ ...unfinishedReasons.length > 0 ? { unfinishedReasons } : {},
2367
+ ...submissionProjection === void 0 ? {} : { submissionProjection },
2107
2368
  requestAttempts: records(safeGet(value, "requestAttempts")),
2108
2369
  snapshots: records(safeGet(value, "snapshots")).map((snapshot) => ({
2109
2370
  snapshotId: safeGet(snapshot, "snapshotId"),
@@ -2123,6 +2384,30 @@ function validateAcceptedCollectorReceipt(value) {
2123
2384
  evidenceRecords: records(safeGet(value, "evidenceRecords")).map((record4) => ({ evidenceId: safeGet(record4, "evidenceId"), kind: safeGet(record4, "kind"), versionId: safeGet(record4, "versionId"), contentDigest: safeGet(record4, "contentDigest"), firstObservedAt: safeGet(record4, "firstObservedAt"), githubId: safeGet(record4, "githubId"), authorLogin: safeGet(record4, "authorLogin"), htmlUrl: safeGet(record4, "htmlUrl"), authoritativeTime: safeGet(record4, "authoritativeTime") }))
2124
2385
  };
2125
2386
  }
2387
+ function projectSubmissionProjection(raw) {
2388
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
2389
+ const p = raw;
2390
+ const out = {};
2391
+ if (p["findingsSource"] === "absent" || p["findingsSource"] === "array" || p["findingsSource"] === "unreadable") {
2392
+ out.findingsSource = p["findingsSource"];
2393
+ }
2394
+ if (typeof p["findingsProjectedCount"] === "number") {
2395
+ out.findingsProjectedCount = p["findingsProjectedCount"];
2396
+ }
2397
+ if (typeof p["findingsUnprojected"] === "boolean") {
2398
+ out.findingsUnprojected = p["findingsUnprojected"];
2399
+ }
2400
+ if (p["unfinishedReasonsSource"] === "absent" || p["unfinishedReasonsSource"] === "array" || p["unfinishedReasonsSource"] === "unreadable") {
2401
+ out.unfinishedReasonsSource = p["unfinishedReasonsSource"];
2402
+ }
2403
+ if (typeof p["unfinishedReasonsProjectedCount"] === "number") {
2404
+ out.unfinishedReasonsProjectedCount = p["unfinishedReasonsProjectedCount"];
2405
+ }
2406
+ if (typeof p["unfinishedReasonsUnprojected"] === "boolean") {
2407
+ out.unfinishedReasonsUnprojected = p["unfinishedReasonsUnprojected"];
2408
+ }
2409
+ return Object.keys(out).length > 0 ? out : void 0;
2410
+ }
2126
2411
  var COLLECTOR_OUTPUT_TOOL, COLLECTOR_ACCEPTED_TEXT;
2127
2412
  var init_collector_output = __esm({
2128
2413
  "src/package-contracts/collector-output.ts"() {
@@ -2431,6 +2716,24 @@ function isCorrectableSubmissionError(error) {
2431
2716
  function isCorrectableExecuteError(error) {
2432
2717
  return isCorrectableSubmissionError(error) || error instanceof GatekeeperDecisionError || error instanceof ParentQueueReaskError || error instanceof WorkerCommitReminderError || error instanceof WorkerPrefixReminderError || error instanceof WorkerUnfinishedReasonReminderError;
2433
2718
  }
2719
+ function projectCorrectableExecuteRejection(error) {
2720
+ const diagnostic = error instanceof Error ? error.message : String(error);
2721
+ if (error instanceof GatekeeperDecisionError) {
2722
+ return { diagnostic, details: { ...error.result } };
2723
+ }
2724
+ if (error instanceof WorkerCommitReminderError || error instanceof WorkerPrefixReminderError || error instanceof WorkerUnfinishedReasonReminderError) {
2725
+ return { diagnostic, details: { code: error.code } };
2726
+ }
2727
+ if (typeof error.code === "string") {
2728
+ return { diagnostic, details: { code: error.code } };
2729
+ }
2730
+ return {
2731
+ diagnostic,
2732
+ details: {
2733
+ code: error instanceof Error && error.name ? error.name : "correctable-submission-error"
2734
+ }
2735
+ };
2736
+ }
2434
2737
  var correctableSubmissionErrorBrand, CorrectableSubmissionError;
2435
2738
  var init_submission_correctable_error = __esm({
2436
2739
  "src/submission-correctable-error.ts"() {
@@ -2609,26 +2912,27 @@ function failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, to
2609
2912
  toolCallId
2610
2913
  );
2611
2914
  }
2612
- var INFRASTRUCTURE_FAILURE_DECLARATION_KEY, INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY, infrastructureFailureDeclarationSchema;
2915
+ var INFRASTRUCTURE_FAILURE_DECLARATION_KEY, INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY, infrastructureFailureNested, infrastructureFailureDeclarationSchema;
2613
2916
  var init_terminating_infrastructure = __esm({
2614
2917
  "src/package-contracts/terminating-infrastructure.ts"() {
2615
2918
  "use strict";
2616
2919
  INFRASTRUCTURE_FAILURE_DECLARATION_KEY = "infrastructureFailure";
2617
2920
  INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY = "diagnostic";
2921
+ infrastructureFailureNested = Type4.Object(
2922
+ {
2923
+ [INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]: Type4.Unknown({
2924
+ description: "\u975E\u7A7A\u57FA\u7840\u8BBE\u65BD\u5931\u8D25\u8BCA\u65AD\u5B57\u7B26\u4E32\u3002\u65E0\u5931\u8D25\u65F6\u5FC5\u987B\u7701\u7565\u6574\u4E2A infrastructureFailure\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
2925
+ })
2926
+ },
2927
+ {
2928
+ additionalProperties: true,
2929
+ description: "\u57FA\u7840\u8BBE\u65BD\u771F\u5B9E\u5931\u8D25\u58F0\u660E\uFF08\u5982\u9700\uFF09\u3002\u89C4\u8303\u5F62\uFF1A{ diagnostic: \u975E\u7A7A\u8BCA\u65AD\u5B57\u7B26\u4E32 }\uFF1B\u65E0\u5931\u8D25\u65F6\u5FC5\u987B\u7701\u7565\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
2930
+ }
2931
+ );
2932
+ infrastructureFailureNested.required = [];
2618
2933
  infrastructureFailureDeclarationSchema = Type4.Object(
2619
2934
  {
2620
- [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: Type4.Object(
2621
- {
2622
- [INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]: Type4.String({
2623
- minLength: 1,
2624
- description: "\u975E\u7A7A\u57FA\u7840\u8BBE\u65BD\u5931\u8D25\u8BCA\u65AD"
2625
- })
2626
- },
2627
- {
2628
- additionalProperties: true,
2629
- description: "\u57FA\u7840\u8BBE\u65BD\u5931\u8D25\u58F0\u660E"
2630
- }
2631
- )
2935
+ [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureNested
2632
2936
  },
2633
2937
  { additionalProperties: true }
2634
2938
  );
@@ -3011,21 +3315,21 @@ var init_exact_utf8 = __esm({
3011
3315
 
3012
3316
  // src/merger-contracts.ts
3013
3317
  import { Type as Type9 } from "typebox";
3014
- function fail(message = "Merger input violates its exact contract") {
3318
+ function fail2(message = "Merger input violates its exact contract") {
3015
3319
  throw new MergerInputContractError(message);
3016
3320
  }
3017
3321
  function canonicalPath(path) {
3018
3322
  return typeof path === "string" && path.length > 0 && !path.startsWith("/") && !path.includes("\0") && path.split("/").every((part) => part !== "" && part !== "." && part !== "..");
3019
3323
  }
3020
3324
  function validatePathSet(value, label) {
3021
- if (!Array.isArray(value) || value.length === 0 || !value.every(canonicalPath)) fail(`Merger ${label} must be a non-empty canonical path set`);
3325
+ if (!Array.isArray(value) || value.length === 0 || !value.every(canonicalPath)) fail2(`Merger ${label} must be a non-empty canonical path set`);
3022
3326
  return value;
3023
3327
  }
3024
3328
  function validateMaterial(value, label) {
3025
- if (!record(value) || typeof value.bytesBase64 !== "string" || typeof value.sha256 !== "string") fail(`Merger ${label} material is malformed`);
3329
+ if (!record2(value) || typeof value.bytesBase64 !== "string" || typeof value.sha256 !== "string") fail2(`Merger ${label} material is malformed`);
3026
3330
  const bytes = Buffer.from(value.bytesBase64, "base64");
3027
3331
  exactUtf8(bytes, `Merger ${label} material`);
3028
- if (sha256Hex(bytes) !== value.sha256) fail(`Merger ${label} material digest mismatch`);
3332
+ if (sha256Hex(bytes) !== value.sha256) fail2(`Merger ${label} material digest mismatch`);
3029
3333
  }
3030
3334
  function deepFreeze(value) {
3031
3335
  if (value && typeof value === "object") {
@@ -3035,26 +3339,26 @@ function deepFreeze(value) {
3035
3339
  return value;
3036
3340
  }
3037
3341
  function validateMergerInput(value) {
3038
- if (!record(value) || blank(value.attemptId) || !isFullGitObjectId(value.targetObjectId) || !isFullGitObjectId(value.sourceObjectId) || value.targetObjectId.length !== value.sourceObjectId.length) fail("Merger input has invalid identity or object ID");
3039
- if (!record(value.materials)) fail();
3342
+ if (!record2(value) || blank(value.attemptId) || !isFullGitObjectId(value.targetObjectId) || !isFullGitObjectId(value.sourceObjectId) || value.targetObjectId.length !== value.sourceObjectId.length) fail2("Merger input has invalid identity or object ID");
3343
+ if (!record2(value.materials)) fail2();
3040
3344
  for (const key of ["task", "authority", "targetIntent", "sourceIntent"]) validateMaterial(value.materials[key], key);
3041
3345
  const conflicts = validatePathSet(value.expectedConflictPaths, "expected conflict paths");
3042
3346
  const scope = validatePathSet(value.resolutionScope, "resolution scope");
3043
- if (!conflicts.every((path) => scope.includes(path))) fail("Merger resolution scope must contain the complete conflict set");
3044
- if (!Array.isArray(value.authorizedChecks)) fail("Merger authorized checks are malformed");
3347
+ if (!conflicts.every((path) => scope.includes(path))) fail2("Merger resolution scope must contain the complete conflict set");
3348
+ if (!Array.isArray(value.authorizedChecks)) fail2("Merger authorized checks are malformed");
3045
3349
  for (const check of value.authorizedChecks) {
3046
- if (!record(check) || !Array.isArray(check.argv) || check.argv.length === 0 || check.argv.some(blank)) fail("Merger authorized check is malformed");
3350
+ if (!record2(check) || !Array.isArray(check.argv) || check.argv.length === 0 || check.argv.some(blank)) fail2("Merger authorized check is malformed");
3047
3351
  }
3048
3352
  return deepFreeze(structuredClone(value));
3049
3353
  }
3050
3354
  function validateMergerOutput(value, expectedAttemptId) {
3051
- if (!record(value) || expectedAttemptId !== void 0 && value.attemptId !== expectedAttemptId) throw new Error("\u5408\u5E76\u56DE\u6267 attempt \u4E0D\u5339\u914D");
3355
+ if (!record2(value) || expectedAttemptId !== void 0 && value.attemptId !== expectedAttemptId) throw new Error("\u5408\u5E76\u56DE\u6267 attempt \u4E0D\u5339\u914D");
3052
3356
  const status = typeof value.status === "string" ? value.status : void 0;
3053
3357
  if (status === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value);
3054
3358
  if (status === "escalate") return structuredClone(value);
3055
3359
  throw new Error("\u5408\u5E76\u56DE\u6267\u65E0\u5DF2\u8BC6\u522B\u7684\u6267\u884C\u5224\u522B");
3056
3360
  }
3057
- var oidPattern, materialSchema, checkSchema, mergerInputSchema, mergerOutputVariants, mergerOutputSchema, MERGER_OUTPUT_TOOL_NAME, MERGER_ACCEPTED_TEXT, record, blank, MergerInputContractError;
3361
+ var oidPattern, materialSchema, checkSchema, mergerInputSchema, mergerOutputVariants, mergerOutputSchema, MERGER_OUTPUT_TOOL_NAME, MERGER_ACCEPTED_TEXT, record2, blank, MergerInputContractError;
3058
3362
  var init_merger_contracts = __esm({
3059
3363
  "src/merger-contracts.ts"() {
3060
3364
  "use strict";
@@ -3085,7 +3389,7 @@ var init_merger_contracts = __esm({
3085
3389
  );
3086
3390
  MERGER_OUTPUT_TOOL_NAME = "ak_merger_output";
3087
3391
  MERGER_ACCEPTED_TEXT = "\u5408\u5E76\u56DE\u6267\u5DF2\u63A5\u53D7";
3088
- record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3392
+ record2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3089
3393
  blank = (v) => typeof v !== "string" || v.trim().length === 0;
3090
3394
  MergerInputContractError = class extends Error {
3091
3395
  constructor(message = "Merger input violates its exact contract") {
@@ -3309,27 +3613,27 @@ function causeMessage(cause) {
3309
3613
  return String(cause);
3310
3614
  }
3311
3615
  }
3312
- function fail2(cause) {
3616
+ function fail3(cause) {
3313
3617
  throw new FixerPacketValidationError(cause);
3314
3618
  }
3315
3619
  function parseFailure(value) {
3316
- if (!Array.isArray(value)) fail2(new Error("Fixer prerequisites must be a JSON array"));
3620
+ if (!Array.isArray(value)) fail3(new Error("Fixer prerequisites must be a JSON array"));
3317
3621
  for (const entry of value) {
3318
3622
  if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
3319
- fail2(new Error("Fixer prerequisite entry must be an object with id and requirement fields"));
3623
+ fail3(new Error("Fixer prerequisite entry must be an object with id and requirement fields"));
3320
3624
  }
3321
3625
  const keys = Object.keys(entry);
3322
3626
  if (keys.length !== 2 || !keys.includes("id") || !keys.includes("requirement")) {
3323
- fail2(new Error("Fixer prerequisite entry fields must be exactly id and requirement"));
3627
+ fail3(new Error("Fixer prerequisite entry fields must be exactly id and requirement"));
3324
3628
  }
3325
3629
  if (typeof entry.id !== "string" || !new RegExp(FIXER_PREREQUISITE_ID_PATTERN).test(entry.id)) {
3326
- fail2(new Error(`Fixer prerequisite id violates pattern ${FIXER_PREREQUISITE_ID_PATTERN}`));
3630
+ fail3(new Error(`Fixer prerequisite id violates pattern ${FIXER_PREREQUISITE_ID_PATTERN}`));
3327
3631
  }
3328
3632
  if (typeof entry.requirement !== "string" || !/\S/.test(entry.requirement)) {
3329
- fail2(new Error("Fixer prerequisite requirement must be nonblank"));
3633
+ fail3(new Error("Fixer prerequisite requirement must be nonblank"));
3330
3634
  }
3331
3635
  }
3332
- fail2(new Error("Fixer prerequisites violate the attachment schema"));
3636
+ fail3(new Error("Fixer prerequisites violate the attachment schema"));
3333
3637
  }
3334
3638
  function validateFixerPrerequisites(value) {
3335
3639
  if (!Value.Check(fixerPrerequisitesSchema, value)) parseFailure(value);
@@ -3337,7 +3641,7 @@ function validateFixerPrerequisites(value) {
3337
3641
  const ids = /* @__PURE__ */ new Set();
3338
3642
  const prerequisites = entries.map((entry) => {
3339
3643
  if (ids.has(entry.id)) {
3340
- fail2(new Error(`Fixer prerequisites contain duplicate id: ${entry.id}`));
3644
+ fail3(new Error(`Fixer prerequisites contain duplicate id: ${entry.id}`));
3341
3645
  }
3342
3646
  ids.add(entry.id);
3343
3647
  return Object.freeze({ id: entry.id, requirement: entry.requirement });
@@ -3349,7 +3653,7 @@ function parseFixerPrerequisites(source) {
3349
3653
  try {
3350
3654
  decoded = JSON.parse(source);
3351
3655
  } catch (error) {
3352
- fail2(error);
3656
+ fail3(error);
3353
3657
  }
3354
3658
  return validateFixerPrerequisites(decoded);
3355
3659
  }
@@ -3634,9 +3938,9 @@ var init_terminating_tools = __esm({
3634
3938
  });
3635
3939
 
3636
3940
  // src/doctor-evidence.ts
3637
- import { readdir, readFile as readFile5, realpath as realpath3, stat } from "node:fs/promises";
3941
+ import { readdir, readFile as readFile6, realpath as realpath3, stat } from "node:fs/promises";
3638
3942
  import { dirname as dirname6, relative as relative2, resolve as resolve7, sep as sep2 } from "node:path";
3639
- function record2(value) {
3943
+ function record3(value) {
3640
3944
  return typeof value === "object" && value !== null && !Array.isArray(value);
3641
3945
  }
3642
3946
  async function discoverCaseFiles(root) {
@@ -3685,7 +3989,7 @@ function deriveSession(content, id) {
3685
3989
  for (const line2 of content.split("\n")) if (line2.trim()) {
3686
3990
  try {
3687
3991
  const row = JSON.parse(line2);
3688
- if (!record2(row)) {
3992
+ if (!record3(row)) {
3689
3993
  degradationReasons.push(`non-object session row in ${id}`);
3690
3994
  break;
3691
3995
  }
@@ -3704,16 +4008,16 @@ function deriveSession(content, id) {
3704
4008
  let accepted, observedCommit, turns = 0, calls = 0, tokens = 0;
3705
4009
  const statuses = [], commits = [];
3706
4010
  for (const row of rows) {
3707
- const message = record2(row.message) ? row.message : void 0;
4011
+ const message = record3(row.message) ? row.message : void 0;
3708
4012
  if (message?.role === "assistant") {
3709
- for (const part of Array.isArray(message.content) ? message.content : []) if (record2(part) && part.type === "toolCall") calls++;
4013
+ for (const part of Array.isArray(message.content) ? message.content : []) if (record3(part) && part.type === "toolCall") calls++;
3710
4014
  if (typeof message.responseId === "string") {
3711
4015
  turns++;
3712
- const usage = record2(message.usage) ? message.usage : void 0;
4016
+ const usage = record3(message.usage) ? message.usage : void 0;
3713
4017
  if (usage && typeof usage.output === "number") tokens += usage.output;
3714
4018
  }
3715
4019
  }
3716
- if (message?.role === "toolResult" && message.isError !== true && typeof message.toolName === "string" && isTerminatingToolName(message.toolName) && record2(message.details)) {
4020
+ if (message?.role === "toolResult" && message.isError !== true && typeof message.toolName === "string" && isTerminatingToolName(message.toolName) && record3(message.details)) {
3717
4021
  let details;
3718
4022
  try {
3719
4023
  details = validateAcceptedDetails(message.toolName, message.details);
@@ -3753,7 +4057,7 @@ async function loadDoctorCase(runsPath) {
3753
4057
  const turns = { count: 0, sources: [] }, calls = { count: 0, sources: [] }, tokens = { count: 0, sources: [] };
3754
4058
  for (const path of await discoverCaseFiles(root)) {
3755
4059
  const id = relative2(root, path).split(sep2).join("/");
3756
- const bytes = await readFile5(path);
4060
+ const bytes = await readFile6(path);
3757
4061
  const content = bytes.toString("utf8");
3758
4062
  const kind = id.endsWith(".jsonl") ? "session" : "stderr";
3759
4063
  evidence.push({ id, kind, byteLength: bytes.byteLength, contentLength: content.length, sha256: sha256Hex(bytes), content });
@@ -3781,83 +4085,197 @@ var init_doctor_evidence = __esm({
3781
4085
  }
3782
4086
  });
3783
4087
 
3784
- // src/collector-config.ts
3785
- import { createHash as createHash4 } from "node:crypto";
3786
- import { readFile as readFile6 } from "node:fs/promises";
3787
- function fail3(message, cause) {
3788
- throw new Error(message, cause === void 0 ? void 0 : { cause });
4088
+ // src/public-cli/cli-errors.ts
4089
+ var CliUsageError;
4090
+ var init_cli_errors = __esm({
4091
+ "src/public-cli/cli-errors.ts"() {
4092
+ "use strict";
4093
+ CliUsageError = class extends Error {
4094
+ code = "AK_ROLE_USAGE";
4095
+ constructor(message, options) {
4096
+ super(
4097
+ message,
4098
+ options?.cause === void 0 ? void 0 : { cause: options.cause }
4099
+ );
4100
+ this.name = "CliUsageError";
4101
+ }
4102
+ };
4103
+ }
4104
+ });
4105
+
4106
+ // src/public-cli/github-remote.ts
4107
+ function ownerFromGitHubRemoteUrl(remoteUrl) {
4108
+ const ownerRepo = ownerRepoFromGitHubRemoteUrl(remoteUrl);
4109
+ if (ownerRepo === void 0) return void 0;
4110
+ return ownerRepo.split("/")[0].toLowerCase();
3789
4111
  }
3790
- function conservativeAscii(input) {
3791
- for (let i = 0; i < input.length; i += 1) {
3792
- const code = input.charCodeAt(i);
3793
- if (code <= 31 || code === 127 || code > 127) return false;
4112
+ function ownerRepoFromGitHubRemoteUrl(remoteUrl) {
4113
+ const trimmed = remoteUrl.trim();
4114
+ const scp = /^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i.exec(trimmed);
4115
+ if (scp) {
4116
+ return `${scp[1]}/${stripGitSuffix(scp[2])}`;
3794
4117
  }
3795
- return true;
4118
+ const ssh = /^ssh:\/\/git@github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec(
4119
+ trimmed
4120
+ );
4121
+ if (ssh) {
4122
+ return `${ssh[1]}/${stripGitSuffix(ssh[2])}`;
4123
+ }
4124
+ let parsed;
4125
+ try {
4126
+ parsed = new URL(trimmed);
4127
+ } catch {
4128
+ return void 0;
4129
+ }
4130
+ if (!/^github\.com$/i.test(parsed.hostname)) return void 0;
4131
+ if (parsed.search !== "" || parsed.hash !== "") return void 0;
4132
+ const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
4133
+ if (parts.length !== 2) return void 0;
4134
+ return `${parts[0]}/${stripGitSuffix(parts[1])}`;
3796
4135
  }
3797
- function parseCollectorRepository(raw) {
3798
- if (typeof raw !== "string" || raw.trim() !== raw || raw.length === 0) fail3("Collector repository must be a string owner/repo");
3799
- if (!conservativeAscii(raw) || raw.includes("://") || /[?#@%\\ ]/.test(raw)) fail3("Collector repository rejects URL syntax and non-identity bytes");
3800
- const parts = raw.split("/");
3801
- if (parts.length !== 2) fail3("Collector repository must contain exactly one '/' separating owner and repo");
3802
- const [ownerDisplay, repoDisplay] = parts;
3803
- if (!COLLECTOR_OWNER_PATTERN.test(ownerDisplay) || !COLLECTOR_REPO_PATTERN.test(repoDisplay)) fail3("Collector repository does not match the conservative owner/repo grammar");
3804
- const owner = ownerDisplay.toLowerCase();
3805
- const repo = repoDisplay.toLowerCase();
3806
- return { display: raw, canonical: `${owner}/${repo}`, owner, repo };
4136
+ function stripGitSuffix(name) {
4137
+ return name.toLowerCase().endsWith(".git") ? name.slice(0, -4) : name;
3807
4138
  }
3808
- function parseCollectorPrNumber(raw) {
3809
- if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw)) fail3("Collector pull request number must be a positive safe integer string");
3810
- if (typeof raw !== "string" && typeof raw !== "number") fail3("Collector pull request number is required");
3811
- const value = Number(raw);
3812
- if (!Number.isSafeInteger(value) || value < 1) fail3("Collector pull request number must be a positive safe integer");
3813
- return value;
4139
+ var init_github_remote = __esm({
4140
+ "src/public-cli/github-remote.ts"() {
4141
+ "use strict";
4142
+ }
4143
+ });
4144
+
4145
+ // src/collector-target.ts
4146
+ import { execFileSync as execFileSync2 } from "node:child_process";
4147
+ function ambiguousTarget(detail, cause) {
4148
+ throw new CliUsageError(
4149
+ `collector target is ambiguous: ${detail}; pass an explicit --pr`,
4150
+ cause === void 0 ? void 0 : { cause }
4151
+ );
3814
4152
  }
3815
- function record3(value) {
3816
- return typeof value === "object" && value !== null && !Array.isArray(value);
4153
+ function gitFailure(detail, cause) {
4154
+ throw new Error(`collector git failed: ${detail}`, {
4155
+ cause: cause instanceof Error ? cause : new Error(String(cause))
4156
+ });
3817
4157
  }
3818
- function canonicalManifest(requests) {
3819
- return `${JSON.stringify({ requests: requests.map((request) => ({ id: request.id, body: request.requestBody })) })}
3820
- `;
4158
+ function gitText(projectRoot, args) {
4159
+ return execFileSync2("git", [...args], {
4160
+ cwd: projectRoot,
4161
+ encoding: "utf8",
4162
+ stdio: ["ignore", "pipe", "pipe"]
4163
+ }).trim();
3821
4164
  }
3822
- function emptyCollectorManifest() {
3823
- const canonicalJson2 = canonicalManifest([]);
3824
- return { requests: [], canonicalJson: canonicalJson2, digest: createHash4("sha256").update(canonicalJson2).digest("hex") };
4165
+ function isGitConfigMissing(error) {
4166
+ if (typeof error !== "object" || error === null) return false;
4167
+ const status = error.status;
4168
+ return status === 1;
3825
4169
  }
3826
- async function loadCollectorManifest(path) {
3827
- let bytes;
4170
+ function readCurrentBranch(projectRoot) {
3828
4171
  try {
3829
- bytes = await readFile6(path);
4172
+ return gitText(projectRoot, ["rev-parse", "--abbrev-ref", "HEAD"]);
3830
4173
  } catch (error) {
3831
- fail3(`Collector request manifest is unreadable at ${path}`, error);
4174
+ gitFailure("cannot read current git branch", error);
3832
4175
  }
3833
- let parsed;
4176
+ }
4177
+ function readHeadSha(projectRoot) {
3834
4178
  try {
3835
- parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
4179
+ return gitText(projectRoot, ["rev-parse", "HEAD"]);
3836
4180
  } catch (error) {
3837
- fail3("Collector request manifest must be UTF-8 JSON", error);
4181
+ gitFailure("cannot read current HEAD", error);
3838
4182
  }
3839
- if (!record3(parsed)) fail3("Collector request manifest must be an object");
3840
- const rawRequests = parsed.requests ?? [];
3841
- if (!Array.isArray(rawRequests)) fail3("Collector request manifest requests must be an array");
3842
- const requests = [];
3843
- const ids = /* @__PURE__ */ new Set();
3844
- for (const [index, item] of rawRequests.entries()) {
3845
- if (!record3(item) || typeof item.id !== "string" || item.id.length === 0 || typeof item.body !== "string" || item.body.trim() === "") fail3(`Collector request manifest requests[${index}] is invalid`);
3846
- if (ids.has(item.id)) fail3(`Collector request manifest has duplicate request id "${item.id}"`);
3847
- ids.add(item.id);
3848
- requests.push({ id: item.id, requestBody: item.body });
4183
+ }
4184
+ function headRefFromMerge(merge) {
4185
+ const trimmed = merge.trim();
4186
+ if (trimmed.length === 0) return void 0;
4187
+ if (trimmed.startsWith("refs/heads/")) {
4188
+ const ref = trimmed.slice("refs/heads/".length);
4189
+ return ref.length > 0 ? ref : void 0;
3849
4190
  }
3850
- const canonicalJson2 = canonicalManifest(requests);
3851
- return { requests, canonicalJson: canonicalJson2, digest: createHash4("sha256").update(canonicalJson2).digest("hex"), sourcePath: path };
4191
+ if (trimmed.startsWith("refs/")) return void 0;
4192
+ return trimmed;
3852
4193
  }
3853
- var COLLECTOR_HOST, COLLECTOR_OWNER_PATTERN, COLLECTOR_REPO_PATTERN, COLLECTOR_FIXED_KICKOFF;
3854
- var init_collector_config = __esm({
3855
- "src/collector-config.ts"() {
4194
+ function readUpstreamHeadBinding(projectRoot, branch) {
4195
+ let remote;
4196
+ try {
4197
+ remote = gitText(projectRoot, ["config", "--get", `branch.${branch}.remote`]);
4198
+ } catch (error) {
4199
+ if (isGitConfigMissing(error)) remote = void 0;
4200
+ else gitFailure(`cannot read branch.${branch}.remote`, error);
4201
+ }
4202
+ if (remote === void 0 || remote.length === 0) return void 0;
4203
+ let merge;
4204
+ try {
4205
+ merge = gitText(projectRoot, ["config", "--get", `branch.${branch}.merge`]);
4206
+ } catch (error) {
4207
+ if (isGitConfigMissing(error)) merge = void 0;
4208
+ else gitFailure(`cannot read branch.${branch}.merge`, error);
4209
+ }
4210
+ if (merge === void 0 || merge.length === 0) return void 0;
4211
+ const headRef = headRefFromMerge(merge);
4212
+ if (headRef === void 0) return void 0;
4213
+ let remoteUrl;
4214
+ try {
4215
+ remoteUrl = gitText(projectRoot, ["remote", "get-url", remote]);
4216
+ } catch (error) {
4217
+ gitFailure(`cannot read remote URL for ${remote}`, error);
4218
+ }
4219
+ const headOwner = ownerFromGitHubRemoteUrl(remoteUrl);
4220
+ if (headOwner === void 0) return void 0;
4221
+ return { headOwner, headRef };
4222
+ }
4223
+ async function resolveCollectorTarget(input) {
4224
+ if (input.explicitPrNumber !== void 0) {
4225
+ return { kind: "bound", prNumber: input.explicitPrNumber };
4226
+ }
4227
+ const branch = readCurrentBranch(input.projectRoot);
4228
+ const detached = branch.length === 0 || branch === "HEAD";
4229
+ const runner = createGhApiRunner();
4230
+ const { owner, repo } = input.repository;
4231
+ const numbers = [];
4232
+ if (!detached) {
4233
+ const headSha = readHeadSha(input.projectRoot);
4234
+ const upstream = readUpstreamHeadBinding(input.projectRoot, branch);
4235
+ if (upstream !== void 0) {
4236
+ numbers.push(
4237
+ ...await listPullRequestNumbersByHead(runner, {
4238
+ owner,
4239
+ repo,
4240
+ headOwner: upstream.headOwner,
4241
+ headRef: upstream.headRef
4242
+ })
4243
+ );
4244
+ }
4245
+ numbers.push(
4246
+ ...await listPullRequestNumbersByCommit(runner, {
4247
+ owner,
4248
+ repo,
4249
+ commitSha: headSha
4250
+ })
4251
+ );
4252
+ } else {
4253
+ const headSha = readHeadSha(input.projectRoot);
4254
+ numbers.push(
4255
+ ...await listPullRequestNumbersByCommit(runner, {
4256
+ owner,
4257
+ repo,
4258
+ commitSha: headSha
4259
+ })
4260
+ );
4261
+ }
4262
+ const unique = [...new Set(numbers)];
4263
+ if (unique.length === 1) {
4264
+ return { kind: "bound", prNumber: unique[0] };
4265
+ }
4266
+ if (unique.length > 1) {
4267
+ ambiguousTarget(
4268
+ `multiple PRs associated with context: ${unique.join(", ")}`
4269
+ );
4270
+ }
4271
+ return { kind: "unbound" };
4272
+ }
4273
+ var init_collector_target = __esm({
4274
+ "src/collector-target.ts"() {
3856
4275
  "use strict";
3857
- COLLECTOR_HOST = "github.com";
3858
- COLLECTOR_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
3859
- COLLECTOR_REPO_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
3860
- COLLECTOR_FIXED_KICKOFF = "\u91C7\u96C6\u76EE\u6807\u5DF2\u53D7\u7406\uFF0C\u672C\u5C40\u5F00\u59CB\u3002";
4276
+ init_collector_github();
4277
+ init_cli_errors();
4278
+ init_github_remote();
3861
4279
  }
3862
4280
  });
3863
4281
 
@@ -3961,24 +4379,6 @@ var init_uuidv7 = __esm({
3961
4379
  }
3962
4380
  });
3963
4381
 
3964
- // src/public-cli/cli-errors.ts
3965
- var CliUsageError;
3966
- var init_cli_errors = __esm({
3967
- "src/public-cli/cli-errors.ts"() {
3968
- "use strict";
3969
- CliUsageError = class extends Error {
3970
- code = "AK_ROLE_USAGE";
3971
- constructor(message, options) {
3972
- super(
3973
- message,
3974
- options?.cause === void 0 ? void 0 : { cause: options.cause }
3975
- );
3976
- this.name = "CliUsageError";
3977
- }
3978
- };
3979
- }
3980
- });
3981
-
3982
4382
  // src/typed-provider-http.ts
3983
4383
  import { readFile as readFile7, unlink, writeFile as writeFile2 } from "node:fs/promises";
3984
4384
  import { join as join9 } from "node:path";
@@ -5652,12 +6052,13 @@ var init_option_definitions = __esm({
5652
6052
  canonical: "--pr",
5653
6053
  aliases: [],
5654
6054
  valueMetavar: "number",
5655
- required: true,
6055
+ // #676 D1: optional when context uniquely determines the PR; ambiguous → require explicit.
6056
+ required: false,
5656
6057
  repeatable: false,
5657
6058
  form: "option",
5658
6059
  description: {
5659
- en: "Required positive GitHub pull request number.",
5660
- zh: "\u5FC5\u586B\uFF1B\u6B63\u6574\u6570 GitHub PR \u53F7\u3002"
6060
+ en: "Positive GitHub pull request number. Optional when unique branch/HEAD association binds the PR, or when the Collector role can decide the target from task materials via bind-target; multi-candidate git context or a role that cannot decide requires an explicit value.",
6061
+ zh: "\u6B63\u6574\u6570 GitHub PR \u53F7\u3002\u5206\u652F/HEAD \u552F\u4E00\u5173\u8054\u53EF\u7ED1\u5B9A\u65F6\u53EF\u7701\u7565\uFF1B\u4EA6\u53EF\u7531\u901A\u8FDB\u53F8\u4ECE\u4EFB\u52A1\u6750\u6599\u7ECF bind-target \u5224\u5B9A\u3002git \u591A\u5019\u9009\u6216\u89D2\u8272\u65E0\u6CD5\u5224\u5B9A\u65F6\u5FC5\u987B\u663E\u5F0F\u63D0\u4F9B\u3002"
5661
6062
  }
5662
6063
  },
5663
6064
  {
@@ -5975,9 +6376,10 @@ var init_option_definitions = __esm({
5975
6376
  collector: {
5976
6377
  command: "collector",
5977
6378
  summary: "Collect GitHub PR review evidence.",
5978
- usage: ["ak-role collector --pr <number> [options] [instruction]"],
6379
+ usage: ["ak-role collector [--pr <number>] [options] [instruction]"],
5979
6380
  examples: [
5980
6381
  "ak-role collector --pr 42 --repo owner/repository",
6382
+ 'ak-role collector --repo owner/repository "Collect findings for #42"',
5981
6383
  "ak-role collector --pr 42 --request-manifest ./requests.json"
5982
6384
  ]
5983
6385
  },
@@ -6194,7 +6596,7 @@ __export(invocation_exports, {
6194
6596
  resolveDoctorCaseRunsPath: () => resolveDoctorCaseRunsPath,
6195
6597
  resolveGitHubRemoteRepository: () => resolveGitHubRemoteRepository
6196
6598
  });
6197
- import { execFileSync as execFileSync2 } from "node:child_process";
6599
+ import { execFileSync as execFileSync3 } from "node:child_process";
6198
6600
  import { existsSync as existsSync4 } from "node:fs";
6199
6601
  import {
6200
6602
  lstat as lstat3,
@@ -7140,7 +7542,7 @@ function parseCollectorArgv(args) {
7140
7542
  }
7141
7543
  options.assertRequired();
7142
7544
  return {
7143
- prNumber,
7545
+ ...prNumber === void 0 ? {} : { prNumber },
7144
7546
  instruction: positional.join(" "),
7145
7547
  attachmentPaths,
7146
7548
  ...project === void 0 ? {} : { project },
@@ -7151,16 +7553,21 @@ function parseCollectorArgv(args) {
7151
7553
  function resolveGitHubRemoteRepository(projectRoot) {
7152
7554
  let remoteUrl;
7153
7555
  try {
7154
- remoteUrl = execFileSync2("git", ["remote", "get-url", "origin"], {
7556
+ remoteUrl = execFileSync3("git", ["remote", "get-url", "origin"], {
7155
7557
  cwd: projectRoot,
7156
7558
  encoding: "utf8",
7157
7559
  stdio: ["ignore", "pipe", "pipe"]
7158
7560
  }).trim();
7159
7561
  } catch (error) {
7160
- throw new CliUsageError(
7161
- "collector requires a github.com origin remote or an explicit --repo owner/repo",
7162
- { cause: error }
7163
- );
7562
+ if (isGitRemoteMissing(error)) {
7563
+ throw new CliUsageError(
7564
+ "collector requires a github.com origin remote or an explicit --repo owner/repo",
7565
+ { cause: error }
7566
+ );
7567
+ }
7568
+ throw new Error("collector git failed: cannot read origin remote URL", {
7569
+ cause: error instanceof Error ? error : new Error(String(error))
7570
+ });
7164
7571
  }
7165
7572
  if (remoteUrl.length === 0) {
7166
7573
  throw new CliUsageError(
@@ -7180,43 +7587,23 @@ function resolveGitHubRemoteRepository(projectRoot) {
7180
7587
  throw new CliUsageError(detail, { cause: error });
7181
7588
  }
7182
7589
  }
7183
- function ownerRepoFromGitHubRemoteUrl(remoteUrl) {
7184
- const trimmed = remoteUrl.trim();
7185
- const scp = /^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i.exec(trimmed);
7186
- if (scp) {
7187
- return `${scp[1]}/${stripGitSuffix(scp[2])}`;
7188
- }
7189
- const ssh = /^ssh:\/\/git@github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec(
7190
- trimmed
7191
- );
7192
- if (ssh) {
7193
- return `${ssh[1]}/${stripGitSuffix(ssh[2])}`;
7194
- }
7195
- let parsed;
7196
- try {
7197
- parsed = new URL(trimmed);
7198
- } catch {
7199
- return void 0;
7200
- }
7201
- if (!/^github\.com$/i.test(parsed.hostname)) return void 0;
7202
- if (parsed.search !== "" || parsed.hash !== "") return void 0;
7203
- const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
7204
- if (parts.length !== 2) return void 0;
7205
- return `${parts[0]}/${stripGitSuffix(parts[1])}`;
7206
- }
7207
- function stripGitSuffix(name) {
7208
- return name.toLowerCase().endsWith(".git") ? name.slice(0, -4) : name;
7590
+ function isGitRemoteMissing(error) {
7591
+ if (typeof error !== "object" || error === null) return false;
7592
+ const status = error.status;
7593
+ return status === 2;
7209
7594
  }
7210
7595
  async function admitCollectorInvocation(options) {
7211
7596
  if (options.project !== void 0) {
7212
7597
  requireOptionPath("--project", options.project);
7213
7598
  }
7214
- let prNumber;
7215
- try {
7216
- prNumber = parseCollectorPrNumber(options.prNumber);
7217
- } catch (error) {
7218
- const detail = error instanceof Error ? error.message : String(error);
7219
- throw new CliUsageError(detail, { cause: error });
7599
+ let explicitPrNumber;
7600
+ if (options.prNumber !== void 0) {
7601
+ try {
7602
+ explicitPrNumber = parseCollectorPrNumber(options.prNumber);
7603
+ } catch (error) {
7604
+ const detail = error instanceof Error ? error.message : String(error);
7605
+ throw new CliUsageError(detail, { cause: error });
7606
+ }
7220
7607
  }
7221
7608
  const projectRoot = resolve9(options.project ?? options.cwd);
7222
7609
  let repository;
@@ -7259,13 +7646,19 @@ async function admitCollectorInvocation(options) {
7259
7646
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
7260
7647
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
7261
7648
  const attachments = await freezeAttachments(options.attachmentPaths ?? [], attachmentsDirectory);
7649
+ const instruction = options.instruction ?? "";
7650
+ const instructionEmpty = instruction.trim() === "";
7651
+ const target = await resolveCollectorTarget({
7652
+ projectRoot,
7653
+ repository,
7654
+ ...explicitPrNumber === void 0 ? {} : { explicitPrNumber }
7655
+ });
7656
+ const prNumber = target.kind === "bound" ? target.prNumber : void 0;
7262
7657
  let requestManifestPath;
7263
7658
  if (manifestCanonicalJson !== void 0) {
7264
7659
  requestManifestPath = join13(runDirectory, "request-manifest.json");
7265
7660
  await writeFile4(requestManifestPath, manifestCanonicalJson, "utf8");
7266
7661
  }
7267
- const instruction = options.instruction ?? "";
7268
- const instructionEmpty = instruction.trim() === "";
7269
7662
  const admitted = {
7270
7663
  role: "collector",
7271
7664
  runId,
@@ -7275,7 +7668,7 @@ async function admitCollectorInvocation(options) {
7275
7668
  principal,
7276
7669
  instruction,
7277
7670
  instructionEmpty,
7278
- prNumber,
7671
+ ...prNumber === void 0 ? {} : { prNumber },
7279
7672
  repository: repository.canonical,
7280
7673
  repositoryDisplay: repository.display,
7281
7674
  ...requestManifestPath === void 0 ? {} : { requestManifestPath },
@@ -7305,14 +7698,14 @@ async function admitCollectorInvocation(options) {
7305
7698
  runDirectory,
7306
7699
  principal,
7307
7700
  admittedRequestPath,
7308
- prNumber,
7701
+ ...prNumber === void 0 ? {} : { prNumber },
7309
7702
  repository,
7310
7703
  ...requestManifestPath === void 0 ? {} : { requestManifestPath },
7311
7704
  manifestDigest
7312
7705
  };
7313
7706
  }
7314
- function buildCollectorTransportPrompt(_admitted, engineMaterial) {
7315
- return appendEngineSessionMaterial([COLLECTOR_FIXED_KICKOFF], engineMaterial).join("\n");
7707
+ function buildCollectorTransportPrompt(admitted, engineMaterial) {
7708
+ return buildInstructionTransportPrompt(admitted, engineMaterial);
7316
7709
  }
7317
7710
  function parseDoctorIssueNumber(raw) {
7318
7711
  const trimmed = raw.trim();
@@ -8332,6 +8725,8 @@ var init_invocation = __esm({
8332
8725
  init_run_ticket_number();
8333
8726
  init_doctor_evidence();
8334
8727
  init_collector_config();
8728
+ init_collector_target();
8729
+ init_github_remote();
8335
8730
  init_fixer_packet();
8336
8731
  init_merger_git_state();
8337
8732
  init_merger_contracts();
@@ -10924,19 +11319,19 @@ function branchNamesAtPinnedHead(pin) {
10924
11319
  }
10925
11320
  return Object.freeze([...names]);
10926
11321
  }
10927
- async function gitText(root, args) {
11322
+ async function gitText2(root, args) {
10928
11323
  const { stdout } = await execGit(["-C", root, ...args], { encoding: "utf8" });
10929
11324
  return stdout.trim();
10930
11325
  }
10931
11326
  async function createReviewerPinnedGitReader(root = process.cwd()) {
10932
- const discoveredRoot = await gitText(root, ["rev-parse", "--show-toplevel"]);
11327
+ const discoveredRoot = await gitText2(root, ["rev-parse", "--show-toplevel"]);
10933
11328
  const repositoryRoot = await realpath6(discoveredRoot);
10934
- const objectFormat = await gitText(repositoryRoot, ["rev-parse", "--show-object-format"]);
11329
+ const objectFormat = await gitText2(repositoryRoot, ["rev-parse", "--show-object-format"]);
10935
11330
  if (objectFormat !== "sha1" && objectFormat !== "sha256") throw new Error("Unsupported Git object format");
10936
11331
  const oidWidth = objectFormat === "sha1" ? 40 : 64;
10937
- const targetHead = await gitText(repositoryRoot, ["rev-parse", "HEAD^{commit}"]);
10938
- const reachableCommitIds = Object.freeze((await gitText(repositoryRoot, ["rev-list", targetHead])).split("\n").filter(Boolean));
10939
- const refs = parseReviewerRefSnapshot(await gitText(repositoryRoot, reviewerRefSnapshotArgs()));
11332
+ const targetHead = await gitText2(repositoryRoot, ["rev-parse", "HEAD^{commit}"]);
11333
+ const reachableCommitIds = Object.freeze((await gitText2(repositoryRoot, ["rev-list", targetHead])).split("\n").filter(Boolean));
11334
+ const refs = parseReviewerRefSnapshot(await gitText2(repositoryRoot, reviewerRefSnapshotArgs()));
10940
11335
  const pin = immutableReviewerPin({ repositoryRoot, objectFormat, targetHead, refs });
10941
11336
  const invalid = (code, diagnostic, cause) => {
10942
11337
  throw new ReviewerCorrectablePreflightError(code, diagnostic, cause === void 0 ? void 0 : { cause });
@@ -10955,9 +11350,9 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10955
11350
  return Object.freeze({
10956
11351
  pin,
10957
11352
  async snapshot() {
10958
- const liveObjectFormat = await gitText(repositoryRoot, ["rev-parse", "--show-object-format"]);
11353
+ const liveObjectFormat = await gitText2(repositoryRoot, ["rev-parse", "--show-object-format"]);
10959
11354
  if (liveObjectFormat !== "sha1" && liveObjectFormat !== "sha256") throw new Error("Unsupported Git object format");
10960
- return immutableReviewerPin({ repositoryRoot, objectFormat: liveObjectFormat, targetHead: await gitText(repositoryRoot, ["rev-parse", "HEAD^{commit}"]), refs: parseReviewerRefSnapshot(await gitText(repositoryRoot, reviewerRefSnapshotArgs())) });
11355
+ return immutableReviewerPin({ repositoryRoot, objectFormat: liveObjectFormat, targetHead: await gitText2(repositoryRoot, ["rev-parse", "HEAD^{commit}"]), refs: parseReviewerRefSnapshot(await gitText2(repositoryRoot, reviewerRefSnapshotArgs())) });
10961
11356
  },
10962
11357
  async resolve(base) {
10963
11358
  if (!/^[A-Za-z0-9._/~^+-]+$/.test(base) || base.startsWith("-") || base.includes("..") || base.includes("@{")) {
@@ -10967,7 +11362,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10967
11362
  const headExpression = /^HEAD((?:~[0-9]+|\^[0-9]+)*)$/.exec(base);
10968
11363
  if (headExpression) {
10969
11364
  try {
10970
- commit = await gitText(repositoryRoot, ["rev-parse", "--verify", `${targetHead}${headExpression[1]}^{commit}`]);
11365
+ commit = await gitText2(repositoryRoot, ["rev-parse", "--verify", `${targetHead}${headExpression[1]}^{commit}`]);
10971
11366
  } catch (error) {
10972
11367
  if (exitCode(error) === 128) {
10973
11368
  const repository = await repositoryIsAvailable(repositoryRoot);
@@ -10983,7 +11378,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10983
11378
  } else commit = symbolic(base);
10984
11379
  if (commit === void 0) invalid("base-invalid", "base revision must name an existing pinned ref or reachable commit");
10985
11380
  try {
10986
- commit = await gitText(repositoryRoot, ["rev-parse", "--verify", `${commit}^{commit}`]);
11381
+ commit = await gitText2(repositoryRoot, ["rev-parse", "--verify", `${commit}^{commit}`]);
10987
11382
  } catch (error) {
10988
11383
  if (exitCode(error) === 128) {
10989
11384
  const repository = await repositoryIsAvailable(repositoryRoot);
@@ -10992,7 +11387,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10992
11387
  throw error;
10993
11388
  }
10994
11389
  try {
10995
- await gitText(repositoryRoot, ["merge-base", "--is-ancestor", commit, targetHead]);
11390
+ await gitText2(repositoryRoot, ["merge-base", "--is-ancestor", commit, targetHead]);
10996
11391
  } catch (error) {
10997
11392
  if (exitCode(error) === 1) invalid("base-invalid", "base revision must be an ancestor of the pinned target", error);
10998
11393
  throw error;
@@ -11002,7 +11397,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11002
11397
  async range(base) {
11003
11398
  let mergeBase;
11004
11399
  try {
11005
- mergeBase = await gitText(repositoryRoot, ["merge-base", base, targetHead]);
11400
+ mergeBase = await gitText2(repositoryRoot, ["merge-base", base, targetHead]);
11006
11401
  } catch (error) {
11007
11402
  if (exitCode(error) === 1) {
11008
11403
  invalid("range-invalid", "review range requires a common ancestor for base and pinned target", error);
@@ -11013,7 +11408,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11013
11408
  const diffCommand = `git diff ${mergeBase}...${targetHead}`;
11014
11409
  const [{ stdout: diff }, commitsText] = await Promise.all([
11015
11410
  execGit(["-C", repositoryRoot, "diff", `${mergeBase}...${targetHead}`], { encoding: "buffer", maxBuffer: 64 * 1024 * 1024 }),
11016
- gitText(repositoryRoot, ["rev-list", "--reverse", `${mergeBase}..${targetHead}`])
11411
+ gitText2(repositoryRoot, ["rev-list", "--reverse", `${mergeBase}..${targetHead}`])
11017
11412
  ]);
11018
11413
  if (diff.length === 0) invalid("range-invalid", "review range must contain a non-empty diff between base and pinned target");
11019
11414
  return Object.freeze({ base: mergeBase, target: targetHead, diffCommand, diffSha256: sha256Hex(Uint8Array.from(diff)), commits: Object.freeze(commitsText ? commitsText.split("\n") : []) });
@@ -11029,7 +11424,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11029
11424
  },
11030
11425
  async listSpecCandidatePaths() {
11031
11426
  const roots = ["docs", "specs", ".scratch"];
11032
- const text = await gitText(repositoryRoot, [
11427
+ const text = await gitText2(repositoryRoot, [
11033
11428
  "ls-tree",
11034
11429
  "-r",
11035
11430
  "--name-only",
@@ -11042,7 +11437,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11042
11437
  async originRepository() {
11043
11438
  let remoteUrl;
11044
11439
  try {
11045
- remoteUrl = await gitText(repositoryRoot, ["remote", "get-url", "origin"]);
11440
+ remoteUrl = await gitText2(repositoryRoot, ["remote", "get-url", "origin"]);
11046
11441
  } catch (error) {
11047
11442
  if (isConfirmedMissingOriginRemote(error)) return void 0;
11048
11443
  throw error;
@@ -11050,7 +11445,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11050
11445
  return parseGitHubOriginRemote(remoteUrl);
11051
11446
  },
11052
11447
  async commitMessagesNewestFirst(base) {
11053
- const text = await gitText(repositoryRoot, [
11448
+ const text = await gitText2(repositoryRoot, [
11054
11449
  "log",
11055
11450
  "--format=%s",
11056
11451
  `${base}..${targetHead}`
@@ -11827,21 +12222,214 @@ function assignWindowRelations(records2, activationTime, deadlineTime) {
11827
12222
  }
11828
12223
  }
11829
12224
  }
11830
- var COLLECTOR_ELIGIBILITY_MS;
11831
- var init_collector_evidence = __esm({
11832
- "src/collector-evidence.ts"() {
12225
+ var COLLECTOR_ELIGIBILITY_MS;
12226
+ var init_collector_evidence = __esm({
12227
+ "src/collector-evidence.ts"() {
12228
+ "use strict";
12229
+ COLLECTOR_ELIGIBILITY_MS = 15 * 60 * 1e3;
12230
+ }
12231
+ });
12232
+
12233
+ // src/collector-identity.ts
12234
+ function identityKey(identity) {
12235
+ if (identity === null) return "unassigned";
12236
+ return String(identity.userId);
12237
+ }
12238
+ function mergeMachineIdentity(current, observed) {
12239
+ if (current === null) return observed;
12240
+ if (observed === null) return current;
12241
+ if (current.appId === void 0 && observed.appId !== void 0) return observed;
12242
+ if (current.appId !== void 0 && observed.appId === void 0) return current;
12243
+ return observed.userType < current.userType ? observed : current;
12244
+ }
12245
+ function headRelationFor(record4, targetHead) {
12246
+ return record4.commitOid === void 0 || record4.commitOid === null ? "unbound" : record4.commitOid === targetHead ? "current" : "prior";
12247
+ }
12248
+ function extractCollectorEvidenceIdentityGroups(records2, targetHead) {
12249
+ const groups = /* @__PURE__ */ new Map();
12250
+ for (const record4 of records2) {
12251
+ if (record4.kind !== "review" && record4.kind !== "issue_comment" && record4.kind !== "review_comment" && record4.kind !== "reaction") continue;
12252
+ if (record4.githubId === void 0) continue;
12253
+ const identity = record4.machineIdentity ?? null;
12254
+ const kind = record4.kind;
12255
+ const source = {
12256
+ kind,
12257
+ id: record4.githubId,
12258
+ evidenceId: record4.evidenceId,
12259
+ headRelation: headRelationFor(record4, targetHead)
12260
+ };
12261
+ const key = identityKey(identity);
12262
+ let group = groups.get(key);
12263
+ if (group === void 0) {
12264
+ group = {
12265
+ identity,
12266
+ ...record4.authorLogin === void 0 ? {} : { displayLogin: record4.authorLogin },
12267
+ attendance: true,
12268
+ findings: [],
12269
+ materials: []
12270
+ };
12271
+ groups.set(key, group);
12272
+ } else {
12273
+ group.identity = mergeMachineIdentity(group.identity, identity);
12274
+ }
12275
+ group.materials.push(source);
12276
+ }
12277
+ return [...groups.values()];
12278
+ }
12279
+ function candidateRecord(candidate) {
12280
+ if (candidate === void 0 || candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
12281
+ return void 0;
12282
+ }
12283
+ return candidate;
12284
+ }
12285
+ function hasNonCanonicalOwnKeys(record4) {
12286
+ for (const key of Object.keys(record4)) {
12287
+ if (!COLLECTOR_OUTPUT_CANONICAL_KEYS.has(key)) return true;
12288
+ }
12289
+ return false;
12290
+ }
12291
+ function enrichCollectorFindings(input) {
12292
+ if (input.candidate !== void 0 && input.candidate !== null && candidateRecord(input.candidate) === void 0) {
12293
+ return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
12294
+ }
12295
+ const record4 = candidateRecord(input.candidate);
12296
+ if (record4 === void 0) {
12297
+ return { findingsSource: "absent", findingsProjectedCount: 0, findingsUnprojected: false };
12298
+ }
12299
+ if (!Object.hasOwn(record4, "findings")) {
12300
+ if (hasNonCanonicalOwnKeys(record4)) {
12301
+ return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
12302
+ }
12303
+ return { findingsSource: "absent", findingsProjectedCount: 0, findingsUnprojected: false };
12304
+ }
12305
+ const rawFindings = record4["findings"];
12306
+ if (!Array.isArray(rawFindings)) {
12307
+ return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
12308
+ }
12309
+ const byEvidenceId = new Map(input.records.map((evidence) => [evidence.evidenceId, evidence]));
12310
+ let projected = 0;
12311
+ let unprojected = false;
12312
+ for (const raw of rawFindings) {
12313
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
12314
+ unprojected = true;
12315
+ continue;
12316
+ }
12317
+ const item = raw;
12318
+ const evidenceId = item.evidenceId;
12319
+ if (typeof evidenceId !== "string" || evidenceId.length === 0) {
12320
+ unprojected = true;
12321
+ continue;
12322
+ }
12323
+ const evidence = byEvidenceId.get(evidenceId);
12324
+ if (evidence === void 0) {
12325
+ throw new CollectorUnknownEvidenceError(evidenceId);
12326
+ }
12327
+ if (evidence.kind !== "review" && evidence.kind !== "issue_comment" && evidence.kind !== "review_comment") {
12328
+ throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u6307\u5411\u4E0D\u53EF\u627F finding \u7684\u8BC1\u636E\u79CD\u7C7B ${evidence.kind}`);
12329
+ }
12330
+ if (evidence.githubId === void 0) {
12331
+ throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u8BC1\u636E ${evidenceId} \u7F3A\u5C11 GitHub id`);
12332
+ }
12333
+ const identity = evidence.machineIdentity ?? null;
12334
+ const group = input.groups.find((candidateGroup) => identityKey(candidateGroup.identity) === identityKey(identity));
12335
+ if (group === void 0) {
12336
+ throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u8BC1\u636E ${evidenceId} \u65E0\u5F52\u5C5E\u8EAB\u4EFD\u7EC4`);
12337
+ }
12338
+ const category = item.category;
12339
+ const summary = item.summary;
12340
+ if (Object.hasOwn(item, "category") && typeof category !== "string") unprojected = true;
12341
+ if (Object.hasOwn(item, "summary") && typeof summary !== "string") unprojected = true;
12342
+ group.findings.push({
12343
+ identity,
12344
+ source: {
12345
+ kind: evidence.kind,
12346
+ id: evidence.githubId,
12347
+ evidenceId: evidence.evidenceId,
12348
+ headRelation: headRelationFor(evidence, input.targetHead)
12349
+ },
12350
+ ...typeof category === "string" ? { category } : {},
12351
+ ...typeof summary === "string" ? { summary } : {},
12352
+ pointer: {
12353
+ repository: input.repository,
12354
+ prNumber: input.prNumber,
12355
+ commentId: evidence.githubId,
12356
+ ...evidence.htmlUrl === void 0 ? {} : { htmlUrl: evidence.htmlUrl },
12357
+ ...evidence.authorLogin === void 0 ? {} : { authorLogin: evidence.authorLogin },
12358
+ kind: evidence.kind,
12359
+ authoritativeTime: evidence.authoritativeTime ?? null,
12360
+ ...evidence.commitOid === void 0 ? {} : { commitOid: evidence.commitOid }
12361
+ }
12362
+ });
12363
+ projected += 1;
12364
+ }
12365
+ return {
12366
+ findingsSource: "array",
12367
+ findingsProjectedCount: projected,
12368
+ findingsUnprojected: unprojected
12369
+ };
12370
+ }
12371
+ function extractCollectorUnfinishedReasons(candidate) {
12372
+ if (candidate !== void 0 && candidate !== null && candidateRecord(candidate) === void 0) {
12373
+ return { reasons: void 0, source: "unreadable", unprojected: true };
12374
+ }
12375
+ const record4 = candidateRecord(candidate);
12376
+ if (record4 === void 0) {
12377
+ return { reasons: void 0, source: "absent", unprojected: false };
12378
+ }
12379
+ if (!Object.hasOwn(record4, "unfinishedReasons")) {
12380
+ return { reasons: void 0, source: "absent", unprojected: false };
12381
+ }
12382
+ const raw = record4["unfinishedReasons"];
12383
+ if (!Array.isArray(raw)) {
12384
+ return { reasons: void 0, source: "unreadable", unprojected: true };
12385
+ }
12386
+ const reasons = raw.filter((item) => typeof item === "string");
12387
+ const unprojected = reasons.length !== raw.length;
12388
+ return {
12389
+ reasons: reasons.length > 0 ? reasons : void 0,
12390
+ source: "array",
12391
+ unprojected
12392
+ };
12393
+ }
12394
+ var CollectorUnknownEvidenceError, CollectorFindingsValidationError, CollectorNonOpenRequestError, COLLECTOR_OUTPUT_CANONICAL_KEYS;
12395
+ var init_collector_identity = __esm({
12396
+ "src/collector-identity.ts"() {
11833
12397
  "use strict";
11834
- COLLECTOR_ELIGIBILITY_MS = 15 * 60 * 1e3;
12398
+ init_submission_correctable_error();
12399
+ CollectorUnknownEvidenceError = class extends CorrectableSubmissionError {
12400
+ constructor(evidenceId) {
12401
+ super(`\u672A\u5728\u672C\u5C40\u5DF2\u89C2\u6D4B\u6750\u6599\u4E2D\u627E\u5230 evidenceId ${evidenceId}\uFF1B\u8BF7\u7528 observe \u8FD4\u56DE\u7684\u6307\u9488\u91CD\u8BD5\u3002`);
12402
+ this.name = "CollectorUnknownEvidenceError";
12403
+ }
12404
+ };
12405
+ CollectorFindingsValidationError = class extends CorrectableSubmissionError {
12406
+ constructor(message) {
12407
+ super(message);
12408
+ this.name = "CollectorFindingsValidationError";
12409
+ }
12410
+ };
12411
+ CollectorNonOpenRequestError = class extends CorrectableSubmissionError {
12412
+ constructor(prState) {
12413
+ super(`\u901A\u8FDB\u53F8\u8BF7\u6C42\u8981\u6C42 OPEN \u72B6\u6001\u7684 PR \u5FEB\u7167\uFF1B\u5F53\u524D\u4E3A ${prState}\uFF0C\u4E0D\u518D\u89E6\u53D1\u65B0\u8BC4\u5BA1\uFF0C\u8BF7\u76F4\u63A5\u4EA4\u56DE\u5DF2\u6709\u6750\u6599`);
12414
+ this.name = "CollectorNonOpenRequestError";
12415
+ }
12416
+ };
12417
+ COLLECTOR_OUTPUT_CANONICAL_KEYS = /* @__PURE__ */ new Set([
12418
+ "findings",
12419
+ "unfinishedReasons",
12420
+ "infrastructureFailure"
12421
+ ]);
11835
12422
  }
11836
12423
  });
11837
12424
 
11838
12425
  // src/collector-tool-schemas.ts
11839
12426
  import { Type as Type13 } from "typebox";
11840
- var collectorObserveArgsSchema, collectorRequestArgsSchema, collectorReadArgsSchema, collectorWaitArgsSchema, collectorFindingArgsSchema, collectorOutputBaseSchema, collectorOutputArgsSchema;
12427
+ var collectorObserveArgsSchema, collectorRequestArgsSchema, collectorReadArgsSchema, collectorWaitArgsSchema, collectorBindTargetArgsSchema, collectorFindingItemDeclaration, collectorOutputBaseSchema, collectorOutputArgsSchema;
11841
12428
  var init_collector_tool_schemas = __esm({
11842
12429
  "src/collector-tool-schemas.ts"() {
11843
12430
  "use strict";
11844
12431
  init_collector_evidence();
12432
+ init_open_tool_schema();
11845
12433
  init_terminating_infrastructure();
11846
12434
  collectorObserveArgsSchema = Type13.Object({}, { additionalProperties: false });
11847
12435
  collectorRequestArgsSchema = Type13.Object({
@@ -11854,19 +12442,51 @@ var init_collector_tool_schemas = __esm({
11854
12442
  collectorWaitArgsSchema = Type13.Object({
11855
12443
  durationMs: Type13.Integer({ minimum: 1, maximum: COLLECTOR_ELIGIBILITY_MS, description: "\u7B49\u5F85\u6BEB\u79D2\uFF1B\u5355\u6B21\u4E0A\u9650\u4E94\u5206\u949F\u4E14\u4E0D\u8D85\u5269\u4F59\u8D44\u683C" })
11856
12444
  }, { additionalProperties: false });
11857
- collectorFindingArgsSchema = Type13.Object({
11858
- evidenceId: Type13.String({ minLength: 1, description: "observe \u8FD4\u56DE\u7684\u6750\u6599\u8BC1\u636E id\uFF08evidenceId\uFF09" }),
11859
- category: Type13.Optional(Type13.String({ minLength: 1, maxLength: 200, description: "\u8BE5 finding \u7684\u7B80\u77ED\u5F52\u7C7B\u6807\u7B7E\uFF1B\u9700\u8981\u5934\u90E8\u4E4B\u5916\u7684\u6B63\u6587\u65F6\u5148\u7528 ak_collector_read \u5F00\u5377\u518D\u5224\u8BFB\uFF0C\u4E0D\u5F97\u8A8A\u5199\u8BC4\u8BBA\u6B63\u6587" }))
11860
- }, { additionalProperties: false });
11861
- collectorOutputBaseSchema = Type13.Object({
11862
- findings: Type13.Optional(Type13.Array(collectorFindingArgsSchema, {
11863
- description: "\u672C\u6B21\u6536\u96C6\u5230\u7684\u9010\u6761 findings\uFF1B\u96F6 finding \u7684\u6A21\u677F\u901A\u77E5\u4E0D\u5F97\u8FDB\u5165\u3002\u6B63\u5E38\u5B8C\u5DE5\u65E0 finding \u65F6\u7701\u7565\u3002"
12445
+ collectorBindTargetArgsSchema = Type13.Object({
12446
+ prNumber: Type13.Optional(Type13.Unknown({
12447
+ description: "\u89D2\u8272\u5224\u5B9A\u7684\u672C\u4ED3 PR \u53F7\uFF08\u6B63\u6574\u6570\uFF09\u3002\u4E0E issueNumber \u4E8C\u9009\u4E00\u6216\u540C\u6307\u552F\u4E00\u76EE\u6807\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
12448
+ })),
12449
+ issueNumber: Type13.Optional(Type13.Unknown({
12450
+ description: "\u89D2\u8272\u5224\u5B9A\u7684\u672C\u4ED3 issue \u53F7\uFF08\u6B63\u6574\u6570\uFF09\uFF1Bruntime \u7ECF\u7EBF\u4E0A\u5173\u8054\u89E3\u6790\u552F\u4E00 PR\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
11864
12451
  }))
11865
- }, { additionalProperties: true, description: "\u6B63\u5E38\u5B8C\u5DE5\u63D0\u4EA4\u7A7A\u5BF9\u8C61 {}\uFF1B\u4EC5\u5728\u57FA\u7840\u8BBE\u65BD\u771F\u5B9E\u5931\u8D25\u65F6\u624D\u586B\u5199 infrastructureFailure\uFF0C\u65E0\u5931\u8D25\u65F6\u5FC5\u987B\u7701\u7565\u8BE5\u5B57\u6BB5\u3002" });
12452
+ }, { additionalProperties: true });
12453
+ collectorFindingItemDeclaration = (() => {
12454
+ const item = Type13.Object(
12455
+ {
12456
+ evidenceId: Type13.Unknown({
12457
+ description: "observe \u8FD4\u56DE\u7684\u6750\u6599\u6307\u9488\uFF08\u5FC5\u586B\u8BED\u4E49\uFF09"
12458
+ }),
12459
+ category: Type13.Unknown({
12460
+ description: "\u7B80\u77ED\u5F52\u7C7B\u6807\u7B7E\uFF0C\u4E0D\u662F\u6458\u8981"
12461
+ }),
12462
+ summary: Type13.Unknown({
12463
+ description: "\u54EA\u4E2A bot\u3001\u4EC0\u4E48\u95EE\u9898\u7684\u6458\u8981\uFF1B\u4E0D\u8A8A\u6284\u6B63\u6587"
12464
+ })
12465
+ },
12466
+ {
12467
+ additionalProperties: true,
12468
+ description: "\u5355\u6761 finding \u6307\u9488\uFF1AevidenceId + \u53EF\u9009 category/summary\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
12469
+ }
12470
+ );
12471
+ item.required = [];
12472
+ return item;
12473
+ })();
12474
+ collectorOutputBaseSchema = openToolObject(
12475
+ Type13.Object({
12476
+ // No root type:array — host must not shape-reject non-array findings (#676 C).
12477
+ // Nested item declarations ride `items` for registration preservation (ADR 0057).
12478
+ findings: Type13.Unsafe({
12479
+ description: "\u672C\u6B21\u6536\u96C6\u5230\u7684\u9010\u6761 findings\uFF08\u6307\u9488\u6570\u7EC4\u4E3A\u89C4\u8303\u5F62\uFF09\u3002\u96F6 finding \u7684\u6A21\u677F\u901A\u77E5\u4E0D\u5F97\u8FDB\u5165\uFF1B\u6B63\u5E38\u5B8C\u5DE5\u65E0 finding \u65F6\u7701\u7565\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002",
12480
+ items: collectorFindingItemDeclaration
12481
+ }),
12482
+ unfinishedReasons: Type13.Unknown({
12483
+ description: "\u672A\u5B8C\u6210\u539F\u56E0\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u989D\u5EA6/\u6545\u969C/\u7B49\u5F85\u5C4A\u6EE1\u7B49\u73B0\u573A\u4F9D\u636E\uFF09\uFF1B\u4E0D\u5F97\u628A\u672A\u5B8C\u6210\u8868\u8FF0\u4E3A\u65E0\u95EE\u9898\u3002\u65E0\u53EF\u62A5\u544A\u65F6\u7701\u7565\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
12484
+ })
12485
+ })
12486
+ );
11866
12487
  collectorOutputArgsSchema = withInfrastructureFailureDeclaration(
11867
12488
  collectorOutputBaseSchema
11868
12489
  );
11869
- collectorOutputArgsSchema.required = [];
11870
12490
  }
11871
12491
  });
11872
12492
 
@@ -11942,10 +12562,18 @@ function createCollectorLedger(config, options) {
11942
12562
  return Math.max(0, deadlineMono - monoNowOrThrow(clock2));
11943
12563
  };
11944
12564
  const prIdentity = (pr) => `${pr.state}|${pr.headOid}|${pr.updatedAt ?? ""}`;
12565
+ const requireBoundPr = () => {
12566
+ if (config.prNumber === void 0) {
12567
+ throw new Error(
12568
+ "Collector PR target is unbound; call ak_collector_bind_target with the role-decided issue/PR or pass --pr"
12569
+ );
12570
+ }
12571
+ return config.prNumber;
12572
+ };
11945
12573
  const fetchObserveSurfaces = async (transport, observedAt, signal) => {
11946
12574
  const owner = config.repository.owner;
11947
12575
  const repo = config.repository.repo;
11948
- const prNumber = config.prNumber;
12576
+ const prNumber = requireBoundPr();
11949
12577
  const signalOpt = signal === void 0 ? {} : { signal };
11950
12578
  const user = await transport.getAuthenticatedUser(signalOpt);
11951
12579
  const prInitial = await transport.getPullRequest({
@@ -12214,6 +12842,25 @@ function createCollectorLedger(config, options) {
12214
12842
  assertNotFatal();
12215
12843
  outputCandidate = true;
12216
12844
  },
12845
+ bindTarget(prNumber) {
12846
+ assertNotFatal();
12847
+ if (outputCandidate || pendingOutputCallId !== void 0) {
12848
+ throw new Error("\u901A\u8FDB\u53F8\u5DF2\u4EA7\u51FA\u8F93\u51FA\u5019\u9009\uFF0C\u672C\u5C40\u4E0D\u518D\u53D7\u7406\u76EE\u6807\u7ED1\u5B9A");
12849
+ }
12850
+ if (!Number.isSafeInteger(prNumber) || prNumber < 1) {
12851
+ throw new Error("Collector bind target requires a positive safe-integer PR number");
12852
+ }
12853
+ if (config.prNumber !== void 0 && config.prNumber !== prNumber) {
12854
+ throw new Error(
12855
+ `Collector target already bound to PR ${config.prNumber}; cannot rebind to ${prNumber}`
12856
+ );
12857
+ }
12858
+ config.prNumber = prNumber;
12859
+ appendJournal("ak-collector-target-bound", {
12860
+ prNumber,
12861
+ repository: config.repository.canonical
12862
+ });
12863
+ },
12217
12864
  beginOperational(toolName, toolCallId) {
12218
12865
  assertNotFatal();
12219
12866
  if (toolName !== COLLECTOR_OUTPUT_TOOL && (outputCandidate || pendingOutputCallId !== void 0)) {
@@ -12360,7 +13007,7 @@ function createCollectorLedger(config, options) {
12360
13007
  completedMono,
12361
13008
  host: "github.com",
12362
13009
  repository: config.repository.canonical,
12363
- prNumber: config.prNumber,
13010
+ prNumber: requireBoundPr(),
12364
13011
  prState: pr.state,
12365
13012
  headOid: pr.headOid,
12366
13013
  complete: true,
@@ -12399,10 +13046,6 @@ function createCollectorLedger(config, options) {
12399
13046
  if (activationTime === void 0 || deadlineTime === void 0) {
12400
13047
  throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u9700\u8981\u6FC0\u6D3B");
12401
13048
  }
12402
- if (pastCutoff(clock2)) {
12403
- finalObservationRequired = true;
12404
- throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u4E0D\u5728\u8D44\u683C\u622A\u6B62\u524D");
12405
- }
12406
13049
  if (ledger.unresolvedTransportFailure) {
12407
13050
  throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u65F6\u5B58\u5728\u672A\u6062\u590D\u7684\u4F20\u8F93\u5931\u8D25");
12408
13051
  }
@@ -12418,7 +13061,11 @@ function createCollectorLedger(config, options) {
12418
13061
  throw new Error("\u901A\u8FDB\u53F8\u8BF7\u6C42\u8981\u6C42\u6700\u65B0\u5B8C\u6574\u5FEB\u7167");
12419
13062
  }
12420
13063
  if (snapshot.prState !== "OPEN") {
12421
- throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u8981\u6C42 OPEN \u72B6\u6001\u7684 PR \u5FEB\u7167");
13064
+ throw new CollectorNonOpenRequestError(snapshot.prState);
13065
+ }
13066
+ if (pastCutoff(clock2)) {
13067
+ finalObservationRequired = true;
13068
+ throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u4E0D\u5728\u8D44\u683C\u622A\u6B62\u524D");
12422
13069
  }
12423
13070
  const { body, marker } = buildCollectorRequestBody({
12424
13071
  configuredBody: request.requestBody,
@@ -12435,9 +13082,10 @@ function createCollectorLedger(config, options) {
12435
13082
  `\u901A\u8FDB\u53F8\u5728\u6B64 HEAD \u5DF2\u6709\u540C marker \u7684\u5DF2\u8BA4\u8BC1\u8BF7\u6C42 "${input.requestId}"`
12436
13083
  );
12437
13084
  }
13085
+ const boundPr = requireBoundPr();
12438
13086
  const attemptKey = [
12439
13087
  config.repository.canonical,
12440
- String(config.prNumber),
13088
+ String(boundPr),
12441
13089
  snapshot.headOid,
12442
13090
  request.id
12443
13091
  ].join("|");
@@ -12466,7 +13114,7 @@ function createCollectorLedger(config, options) {
12466
13114
  const result = await transport.createIssueComment({
12467
13115
  owner: config.repository.owner,
12468
13116
  repo: config.repository.repo,
12469
- prNumber: config.prNumber,
13117
+ prNumber: boundPr,
12470
13118
  body,
12471
13119
  ...signal === void 0 ? {} : { signal }
12472
13120
  });
@@ -12632,19 +13280,22 @@ function buildObserveModelView(input) {
12632
13280
  }))
12633
13281
  };
12634
13282
  }
12635
- var COLLECTOR_OBSERVE_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, COLLECTOR_OPERATIONAL_TOOLS, COLLECTOR_OBSERVE_BODY_HEAD_BYTES, COLLECTOR_ACTIVATION_ENTRY_TYPE, COLLECTOR_SNAPSHOT_ENTRY_TYPE, COLLECTOR_REQUEST_ENTRY_TYPE, COLLECTOR_WAIT_ENTRY_TYPE;
13283
+ var COLLECTOR_OBSERVE_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, COLLECTOR_BIND_TARGET_TOOL, COLLECTOR_OPERATIONAL_TOOLS, COLLECTOR_OBSERVE_BODY_HEAD_BYTES, COLLECTOR_ACTIVATION_ENTRY_TYPE, COLLECTOR_SNAPSHOT_ENTRY_TYPE, COLLECTOR_REQUEST_ENTRY_TYPE, COLLECTOR_WAIT_ENTRY_TYPE;
12636
13284
  var init_collector_ledger = __esm({
12637
13285
  "src/collector-ledger.ts"() {
12638
13286
  "use strict";
12639
13287
  init_collector_evidence();
12640
13288
  init_collector_github();
13289
+ init_collector_identity();
12641
13290
  init_collector_tool_schemas();
12642
13291
  init_collector_output();
12643
13292
  COLLECTOR_OBSERVE_TOOL = "ak_collector_observe";
12644
13293
  COLLECTOR_READ_TOOL = "ak_collector_read";
12645
13294
  COLLECTOR_REQUEST_TOOL = "ak_collector_request";
12646
13295
  COLLECTOR_WAIT_TOOL = "ak_collector_wait";
13296
+ COLLECTOR_BIND_TARGET_TOOL = "ak_collector_bind_target";
12647
13297
  COLLECTOR_OPERATIONAL_TOOLS = [
13298
+ COLLECTOR_BIND_TARGET_TOOL,
12648
13299
  COLLECTOR_OBSERVE_TOOL,
12649
13300
  COLLECTOR_READ_TOOL,
12650
13301
  COLLECTOR_REQUEST_TOOL,
@@ -13397,8 +14048,27 @@ function formatFailureStderrDiagnostic(failure2) {
13397
14048
  const oneLine2 = selected.split(/\r?\n/).map((line2) => line2.trim()).find((line2) => line2.length > 0) ?? "failure";
13398
14049
  return formatCliDiagnostic(boundConciseDiagnostic(oneLine2));
13399
14050
  }
14051
+ function formatErrorCauseDetail(cause) {
14052
+ if (cause instanceof Error) return cause.message;
14053
+ if (typeof cause === "object" && cause !== null) {
14054
+ try {
14055
+ return JSON.stringify(cause);
14056
+ } catch {
14057
+ return String(cause);
14058
+ }
14059
+ }
14060
+ return String(cause);
14061
+ }
13400
14062
  function presentStructuralRejection(error, io) {
13401
- io.stderr(formatCliDiagnostic(error.message));
14063
+ let message = error.message;
14064
+ const cause = error.cause;
14065
+ if (cause !== void 0) {
14066
+ const detail = formatErrorCauseDetail(cause);
14067
+ if (detail.trim().length > 0) {
14068
+ message = `${message}; cause: ${detail}`;
14069
+ }
14070
+ }
14071
+ io.stderr(formatCliDiagnostic(message));
13402
14072
  }
13403
14073
  async function inspectJudgeSession(sessionFile) {
13404
14074
  try {
@@ -14086,6 +14756,22 @@ function toolResultText(message) {
14086
14756
  return "";
14087
14757
  }).join("").trim();
14088
14758
  }
14759
+ function extractCollectorTargetBindRejection(entries) {
14760
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
14761
+ const entry = entries[i];
14762
+ if (entry?.type !== "message") continue;
14763
+ const message = entry.message;
14764
+ if (message?.role !== "toolResult") continue;
14765
+ if (message.toolName !== COLLECTOR_BIND_TARGET_TOOL) continue;
14766
+ if (message.isError !== true) return void 0;
14767
+ const diagnostic = toolResultText(message);
14768
+ if (diagnostic.length === 0) return void 0;
14769
+ const details = message.details;
14770
+ const code = isRecord16(details) && typeof details.code === "string" && details.code.trim() !== "" ? details.code : void 0;
14771
+ return code === void 0 ? { diagnostic } : { diagnostic, code };
14772
+ }
14773
+ return void 0;
14774
+ }
14089
14775
  function boundErroredToolCandidate(entries, resultIndex, message, toolName) {
14090
14776
  if (message.toolName !== toolName || message.isError !== true) return void 0;
14091
14777
  const bound = boundRoleToolCallForResult(entries, resultIndex, message, toolName);
@@ -14891,7 +15577,18 @@ async function settleFailureTerminalResult(admitted, failure2, authority, option
14891
15577
  try {
14892
15578
  const facts = parseNoReceiptLifecycleFacts(raw);
14893
15579
  if (facts.runPointer === admitted.runDirectory && facts.attemptPointer === `current:${admitted.runDirectory}`) {
14894
- const decisiveFacts2 = facts;
15580
+ let decisiveFacts2 = facts;
15581
+ if (admitted.role === "collector") {
15582
+ const bindRejection = extractCollectorTargetBindRejection(entries.slice(attemptStart));
15583
+ if (bindRejection !== void 0) {
15584
+ decisiveFacts2 = {
15585
+ ...facts,
15586
+ targetBindRejected: true,
15587
+ targetBindDiagnostic: bindRejection.diagnostic,
15588
+ ...bindRejection.code === void 0 ? {} : { targetBindCode: bindRejection.code }
15589
+ };
15590
+ }
15591
+ }
14895
15592
  return withOptionalGateProjection(
14896
15593
  {
14897
15594
  roleOutcome: { kind: "no_receipt", role: admitted.role, status: "no-accepted-receipt", ...facts, decisiveFacts: decisiveFacts2 },
@@ -14972,6 +15669,14 @@ function presentFailureTerminal(terminal, io) {
14972
15669
  cause: terminal.roleOutcome.cause,
14973
15670
  diagnostic: terminal.roleOutcome.diagnostic
14974
15671
  }));
15672
+ return;
15673
+ }
15674
+ const bindDiagnostic = terminal.roleOutcome.decisiveFacts.targetBindDiagnostic;
15675
+ if (typeof bindDiagnostic === "string" && bindDiagnostic.trim() !== "") {
15676
+ io.stderr(formatFailureStderrDiagnostic({
15677
+ cause: "output",
15678
+ diagnostic: bindDiagnostic
15679
+ }));
14975
15680
  }
14976
15681
  }
14977
15682
  function defaultNavigatorGraceSleep() {
@@ -19432,132 +20137,6 @@ var init_tool_execution_observation = __esm({
19432
20137
  }
19433
20138
  });
19434
20139
 
19435
- // src/collector-identity.ts
19436
- function identityKey(identity) {
19437
- if (identity === null) return "unassigned";
19438
- return String(identity.userId);
19439
- }
19440
- function mergeMachineIdentity(current, observed) {
19441
- if (current === null) return observed;
19442
- if (observed === null) return current;
19443
- if (current.appId === void 0 && observed.appId !== void 0) return observed;
19444
- if (current.appId !== void 0 && observed.appId === void 0) return current;
19445
- return observed.userType < current.userType ? observed : current;
19446
- }
19447
- function headRelationFor(record4, targetHead) {
19448
- return record4.commitOid === void 0 || record4.commitOid === null ? "unbound" : record4.commitOid === targetHead ? "current" : "prior";
19449
- }
19450
- function extractCollectorEvidenceIdentityGroups(records2, targetHead) {
19451
- const groups = /* @__PURE__ */ new Map();
19452
- for (const record4 of records2) {
19453
- if (record4.kind !== "review" && record4.kind !== "issue_comment" && record4.kind !== "review_comment" && record4.kind !== "reaction") continue;
19454
- if (record4.githubId === void 0) continue;
19455
- const identity = record4.machineIdentity ?? null;
19456
- const kind = record4.kind;
19457
- const source = {
19458
- kind,
19459
- id: record4.githubId,
19460
- evidenceId: record4.evidenceId,
19461
- headRelation: headRelationFor(record4, targetHead)
19462
- };
19463
- const key = identityKey(identity);
19464
- let group = groups.get(key);
19465
- if (group === void 0) {
19466
- group = {
19467
- identity,
19468
- ...record4.authorLogin === void 0 ? {} : { displayLogin: record4.authorLogin },
19469
- attendance: true,
19470
- findings: [],
19471
- materials: []
19472
- };
19473
- groups.set(key, group);
19474
- } else {
19475
- group.identity = mergeMachineIdentity(group.identity, identity);
19476
- }
19477
- group.materials.push(source);
19478
- }
19479
- return [...groups.values()];
19480
- }
19481
- function enrichCollectorFindings(input) {
19482
- const candidate = input.candidate;
19483
- if (candidate === void 0 || candidate === null) return;
19484
- if (typeof candidate !== "object" || Array.isArray(candidate)) {
19485
- throw new CollectorFindingsValidationError("\u901A\u8FDB\u53F8\u4EA4\u4EF6\u53C2\u6570\u5FC5\u987B\u4E3A\u5BF9\u8C61");
19486
- }
19487
- const rawFindings = candidate.findings;
19488
- if (rawFindings === void 0) return;
19489
- if (!Array.isArray(rawFindings)) {
19490
- throw new CollectorFindingsValidationError("\u901A\u8FDB\u53F8 findings \u5FC5\u987B\u4E3A\u6570\u7EC4");
19491
- }
19492
- const byEvidenceId = new Map(input.records.map((record4) => [record4.evidenceId, record4]));
19493
- for (const raw of rawFindings) {
19494
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
19495
- throw new CollectorFindingsValidationError("\u901A\u8FDB\u53F8 finding \u5FC5\u987B\u4E3A\u5BF9\u8C61");
19496
- }
19497
- const evidenceId = raw.evidenceId;
19498
- if (typeof evidenceId !== "string" || evidenceId.length === 0) {
19499
- throw new CollectorFindingsValidationError("\u901A\u8FDB\u53F8 finding \u7F3A\u5C11\u53EF\u89E3\u6790\u7684 evidenceId \u6307\u9488");
19500
- }
19501
- const record4 = byEvidenceId.get(evidenceId);
19502
- if (record4 === void 0) {
19503
- throw new CollectorUnknownEvidenceError(evidenceId);
19504
- }
19505
- if (record4.kind !== "review" && record4.kind !== "issue_comment" && record4.kind !== "review_comment") {
19506
- throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u6307\u5411\u4E0D\u53EF\u627F finding \u7684\u8BC1\u636E\u79CD\u7C7B ${record4.kind}`);
19507
- }
19508
- if (record4.githubId === void 0) {
19509
- throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u8BC1\u636E ${evidenceId} \u7F3A\u5C11 GitHub id`);
19510
- }
19511
- const category = raw.category;
19512
- if (category !== void 0 && (typeof category !== "string" || category.trim().length === 0)) {
19513
- throw new CollectorFindingsValidationError("\u901A\u8FDB\u53F8 finding category \u5FC5\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
19514
- }
19515
- const identity = record4.machineIdentity ?? null;
19516
- const group = input.groups.find((candidateGroup) => identityKey(candidateGroup.identity) === identityKey(identity));
19517
- if (group === void 0) {
19518
- throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u8BC1\u636E ${evidenceId} \u65E0\u5F52\u5C5E\u8EAB\u4EFD\u7EC4`);
19519
- }
19520
- group.findings.push({
19521
- identity,
19522
- source: {
19523
- kind: record4.kind,
19524
- id: record4.githubId,
19525
- evidenceId: record4.evidenceId,
19526
- headRelation: headRelationFor(record4, input.targetHead)
19527
- },
19528
- ...category === void 0 ? {} : { category: category.trim() },
19529
- pointer: {
19530
- repository: input.repository,
19531
- prNumber: input.prNumber,
19532
- commentId: record4.githubId,
19533
- ...record4.htmlUrl === void 0 ? {} : { htmlUrl: record4.htmlUrl },
19534
- ...record4.authorLogin === void 0 ? {} : { authorLogin: record4.authorLogin },
19535
- kind: record4.kind,
19536
- authoritativeTime: record4.authoritativeTime ?? null
19537
- }
19538
- });
19539
- }
19540
- }
19541
- var CollectorUnknownEvidenceError, CollectorFindingsValidationError;
19542
- var init_collector_identity = __esm({
19543
- "src/collector-identity.ts"() {
19544
- "use strict";
19545
- init_submission_correctable_error();
19546
- CollectorUnknownEvidenceError = class extends CorrectableSubmissionError {
19547
- constructor(evidenceId) {
19548
- super(`\u672A\u5728\u672C\u5C40\u5DF2\u89C2\u6D4B\u6750\u6599\u4E2D\u627E\u5230 evidenceId ${evidenceId}\uFF1B\u8BF7\u7528 observe \u8FD4\u56DE\u7684\u6307\u9488\u91CD\u8BD5\u3002`);
19549
- this.name = "CollectorUnknownEvidenceError";
19550
- }
19551
- };
19552
- CollectorFindingsValidationError = class extends CorrectableSubmissionError {
19553
- constructor(message) {
19554
- super(message);
19555
- this.name = "CollectorFindingsValidationError";
19556
- }
19557
- };
19558
- }
19559
- });
19560
-
19561
20140
  // src/collector-receipt.ts
19562
20141
  function fail4(message) {
19563
20142
  throw new Error(message);
@@ -19567,13 +20146,15 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19567
20146
  if (ledger.unresolvedTransportFailure) fail4("Collector cannot output while a transport failure is unrecovered");
19568
20147
  if (ledger.latestCompleteSnapshotId === void 0) fail4("Collector output requires a complete final snapshot");
19569
20148
  if (ledger.activationTime === void 0 || ledger.deadlineTime === void 0) fail4("Collector output requires activation timeline");
20149
+ if (ledger.config.prNumber === void 0) {
20150
+ fail4("Collector output requires a bound PR target; call ak_collector_bind_target first or pass --pr");
20151
+ }
19570
20152
  if (clock !== void 0) ledger.assertOutputObservationLaw(clock);
19571
20153
  else if (ledger.observedGeneration !== ledger.mutationGeneration || ledger.finalObservationRequired && !ledger.finalObservationCompleted) {
19572
20154
  fail4("Collector output requires a complete observe after the latest request/wait mutation");
19573
20155
  }
19574
20156
  const finalSnapshot = ledger.getSnapshot(ledger.latestCompleteSnapshotId);
19575
20157
  if (finalSnapshot === void 0 || !finalSnapshot.complete) fail4("Collector final snapshot is incomplete");
19576
- if (finalSnapshot.prState !== "OPEN") fail4("Collector final snapshot PR state is not OPEN");
19577
20158
  const evidenceRecords = [...ledger.allEvidence()];
19578
20159
  const snapshots = [...ledger.allSnapshots()];
19579
20160
  const evidenceIndex = new Map(evidenceRecords.map((record4) => [record4.evidenceId, record4]));
@@ -19586,7 +20167,7 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19586
20167
  for (const id of snapshot.evidenceIds) if (!evidenceIndex.has(id)) fail4(`Collector snapshot ref "${id}" does not resolve`);
19587
20168
  }
19588
20169
  const groups = extractCollectorEvidenceIdentityGroups(evidenceRecords, finalSnapshot.headOid);
19589
- enrichCollectorFindings({
20170
+ const findingsProjection = enrichCollectorFindings({
19590
20171
  candidate: candidateRaw,
19591
20172
  records: evidenceRecords,
19592
20173
  groups,
@@ -19595,7 +20176,6 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19595
20176
  prNumber: ledger.config.prNumber
19596
20177
  });
19597
20178
  for (const group of groups) {
19598
- if (group.attendance !== true) fail4("Collector group lacks attendance");
19599
20179
  for (const material of group.materials) {
19600
20180
  if (material.evidenceId === void 0 || !evidenceIndex.has(material.evidenceId)) fail4("Collector material lacks a receipt-local evidence ref");
19601
20181
  }
@@ -19603,10 +20183,20 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19603
20183
  if (finding2.source.evidenceId === void 0 || !evidenceIndex.has(finding2.source.evidenceId)) fail4("Collector finding lacks a receipt-local evidence ref");
19604
20184
  }
19605
20185
  }
20186
+ const unfinished = extractCollectorUnfinishedReasons(candidateRaw);
20187
+ const submissionProjection = {
20188
+ findingsSource: findingsProjection.findingsSource,
20189
+ findingsProjectedCount: findingsProjection.findingsProjectedCount,
20190
+ findingsUnprojected: findingsProjection.findingsUnprojected,
20191
+ unfinishedReasonsSource: unfinished.source,
20192
+ unfinishedReasonsProjectedCount: unfinished.reasons?.length ?? 0,
20193
+ unfinishedReasonsUnprojected: unfinished.unprojected
20194
+ };
19606
20195
  return {
19607
20196
  host: COLLECTOR_HOST,
19608
20197
  repository: ledger.config.repository.canonical,
19609
20198
  prNumber: ledger.config.prNumber,
20199
+ prState: finalSnapshot.prState,
19610
20200
  manifestDigest: ledger.config.manifest.digest,
19611
20201
  activationTime: ledger.activationTime.toISOString(),
19612
20202
  deadlineTime: ledger.deadlineTime.toISOString(),
@@ -19614,6 +20204,8 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19614
20204
  finalSnapshotId: finalSnapshot.snapshotId,
19615
20205
  targetHead: finalSnapshot.headOid,
19616
20206
  groups,
20207
+ ...unfinished.reasons === void 0 ? {} : { unfinishedReasons: unfinished.reasons },
20208
+ submissionProjection,
19617
20209
  requestAttempts: [...ledger.requestAttempts()],
19618
20210
  snapshots,
19619
20211
  evidenceRecords: evidenceRecords.map(toReceiptEvidenceRecord)
@@ -19643,120 +20235,76 @@ var init_collector_receipt = __esm({
19643
20235
 
19644
20236
  // src/collector-role.ts
19645
20237
  function buildMethodContext(activation) {
20238
+ const pr = activation.ledger.config.prNumber;
19646
20239
  return [
19647
20240
  "<collector_method>",
19648
20241
  `host: github.com`,
19649
20242
  `repository: ${activation.repository.canonical}`,
19650
- `prNumber: ${activation.prNumber}`,
20243
+ `prNumber: ${pr === void 0 ? "unbound \u2014 call ak_collector_bind_target with the role-decided issue/PR before observe" : String(pr)}`,
19651
20244
  `requests: ${JSON.stringify(activation.manifest.requests.map((request) => ({ id: request.id })))}`,
19652
20245
  "</collector_method>"
19653
20246
  ].join("\n");
19654
20247
  }
20248
+ function parsePositiveTicket(raw, label) {
20249
+ if (raw === void 0 || raw === null) return void 0;
20250
+ if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) return raw;
20251
+ if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim())) return Number(raw.trim());
20252
+ throw new CollectorTargetBindError(`ak_collector_bind_target ${label} must be a positive safe integer`);
20253
+ }
19655
20254
  function createCollectorRoleRuntime(pi, dependencies, hostActions) {
19656
- let activation;
19657
- let inputCount = 0;
19658
- let lifecycleRegistered = false;
19659
20255
  let toolsRegistered = false;
19660
- let firstDispatchDone = false;
19661
- pi.registerFlag("ak-collector-repo", {
19662
- description: "GitHub owner/repo target for Collector (github.com only; conservative ASCII grammar). Collector forbids every Skill, including command-only Skills.",
19663
- type: "string"
19664
- });
19665
- pi.registerFlag("ak-collector-pr", {
19666
- 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",
19667
- type: "string"
19668
- });
19669
- pi.registerFlag("ak-collector-request-manifest", {
19670
- 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",
19671
- type: "string"
19672
- });
19673
- const ensureLifecycle = () => {
19674
- if (lifecycleRegistered) return;
19675
- lifecycleRegistered = true;
19676
- pi.on("input", (event, ctx) => {
19677
- if (activation === void 0) {
19678
- return { action: "continue" };
19679
- }
19680
- if (inputCount >= 1) {
19681
- activation.ledger.latchFatal("\u901A\u8FDB\u53F8\u5DF2\u62D2\u7EDD\u540E\u7EED\u8F93\u5165");
19682
- if (process.exitCode === void 0 || process.exitCode === 0) {
19683
- process.exitCode = 1;
19684
- }
19685
- console.error("Collector rejected later input");
19686
- return { action: "handled" };
19687
- }
19688
- inputCount += 1;
19689
- return {
19690
- action: "transform",
19691
- text: COLLECTOR_FIXED_KICKOFF,
19692
- images: []
19693
- };
19694
- });
19695
- pi.on("before_agent_start", (event, ctx) => {
19696
- if (activation === void 0) return;
19697
- const options = event.systemPromptOptions;
19698
- if (options.skills && options.skills.length > 0) {
19699
- hostActions.failInfrastructure(
19700
- activation.ledger.latchFatal(
19701
- "\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230\u7CFB\u7EDF\u63D0\u793A\u4E2D\u7684\u73AF\u5883 skills"
19702
- ),
19703
- ctx
19704
- );
19705
- }
19706
- if (options.contextFiles && options.contextFiles.length > 0) {
19707
- hostActions.failInfrastructure(
19708
- activation.ledger.latchFatal(
19709
- "\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230\u7CFB\u7EDF\u63D0\u793A\u4E2D\u7684\u73AF\u5883 context files"
19710
- ),
19711
- ctx
19712
- );
19713
- }
19714
- if (typeof options.appendSystemPrompt === "string" && options.appendSystemPrompt.trim().length > 0) {
19715
- hostActions.failInfrastructure(
19716
- activation.ledger.latchFatal(
19717
- "\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230 appendSystemPrompt \u6F02\u79FB"
19718
- ),
19719
- ctx
19720
- );
19721
- }
19722
- if (event.prompt !== COLLECTOR_FIXED_KICKOFF) {
19723
- hostActions.failInfrastructure(
19724
- activation.ledger.latchFatal(
19725
- "\u901A\u8FDB\u53F8\u9996\u6761\u63D0\u793A\u4E0D\u662F\u56FA\u5B9A\u5F00\u573A\u4EE4"
19726
- ),
19727
- ctx
19728
- );
20256
+ return {
20257
+ async activate(ctx) {
20258
+ const soul = (await dependencies.loadSoul()).trim();
20259
+ if (soul.length === 0) throw new Error("Collector soul is empty");
20260
+ const repoFlag = pi.getFlag("ak-collector-repo");
20261
+ const prFlag = pi.getFlag("ak-collector-pr");
20262
+ const requestManifestFlag = pi.getFlag("ak-collector-request-manifest");
20263
+ if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
20264
+ throw new Error("Collector requires --ak-collector-repo");
19729
20265
  }
19730
- if (!firstDispatchDone) {
19731
- firstDispatchDone = true;
19732
- activation.ledger.recordActivation(activation.clock);
20266
+ const repository = parseCollectorRepository(repoFlag);
20267
+ let prNumber;
20268
+ if (typeof prFlag === "string" && prFlag.trim().length > 0) {
20269
+ prNumber = parseCollectorPrNumber(prFlag);
20270
+ } else if (typeof prFlag === "number") {
20271
+ prNumber = parseCollectorPrNumber(prFlag);
19733
20272
  }
20273
+ const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0 ? await loadCollectorManifest(requestManifestFlag) : emptyCollectorManifest();
20274
+ const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
20275
+ const transport = dependencies.createTransport();
20276
+ const ledger = dependencies.createLedger(
20277
+ { repository, prNumber, manifest },
20278
+ clock,
20279
+ ctx
20280
+ );
19734
20281
  return {
19735
- systemPrompt: [
19736
- event.systemPrompt,
19737
- "",
19738
- "<collector_soul>",
19739
- activation.soul,
19740
- "</collector_soul>",
19741
- "",
19742
- buildMethodContext(activation)
19743
- ].join("\n")
20282
+ soul,
20283
+ repository,
20284
+ manifest,
20285
+ ledger,
20286
+ transport,
20287
+ clock
19744
20288
  };
19745
- });
19746
- pi.on("tool_call", (event) => {
19747
- if (activation === void 0) return;
20289
+ },
20290
+ assembleMaterials(activation, baseSystemPrompt) {
20291
+ return [
20292
+ baseSystemPrompt,
20293
+ "",
20294
+ "<collector_soul>",
20295
+ activation.soul,
20296
+ "</collector_soul>",
20297
+ "",
20298
+ buildMethodContext(activation)
20299
+ ].join("\n");
20300
+ },
20301
+ onToolCall(activation, event) {
19748
20302
  if (activation.ledger.fatal) {
19749
20303
  return {
19750
20304
  block: true,
19751
20305
  reason: activation.ledger.fatalReason ?? "\u901A\u8FDB\u53F8\u81F4\u547D\u72B6\u6001"
19752
20306
  };
19753
20307
  }
19754
- if (!COLLECTOR_REQUIRED_TOOLS.includes(event.toolName)) {
19755
- return {
19756
- block: true,
19757
- reason: `\u901A\u8FDB\u53F8\u7981\u7528\u5DE5\u5177 ${event.toolName}`
19758
- };
19759
- }
19760
20308
  if (event.toolName === COLLECTOR_OUTPUT_TOOL) {
19761
20309
  activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, event.toolCallId);
19762
20310
  }
@@ -19767,304 +20315,255 @@ function createCollectorRoleRuntime(pi, dependencies, hostActions) {
19767
20315
  };
19768
20316
  }
19769
20317
  return void 0;
19770
- });
19771
- pi.on("tool_result", (event) => {
19772
- if (activation === void 0) return;
20318
+ },
20319
+ onToolResult(activation, event) {
19773
20320
  activation.ledger.completeOperational(event.toolCallId);
19774
- });
19775
- pi.on("session_shutdown", () => {
19776
- if (activation === void 0) return;
19777
- if (activation.ledger.fatal) {
19778
- if (process.exitCode === void 0 || process.exitCode === 0) {
19779
- process.exitCode = 1;
19780
- }
19781
- }
19782
- });
19783
- };
19784
- const registerTools = () => {
19785
- if (toolsRegistered) return;
19786
- toolsRegistered = true;
19787
- pi.registerTool({
19788
- name: COLLECTOR_OBSERVE_TOOL,
19789
- label: "\u901A\u8FDB\u53F8\u89C2\u5BDF",
19790
- description: "\u6293\u53D6\u914D\u7F6E\u76EE\u6807\u7684\u5B8C\u6574 GitHub PR \u8BC1\u636E\uFF0C\u5B58\u4E0D\u53EF\u53D8\u5FEB\u7167\u5165\u5377\u3002\u6B63\u6587\u5728\u4E0A\u4E0B\u6587\u4E2D\u53EA\u7ED9\u5934\u90E8\u6458\u5F55\u52A0\u6307\u9488\uFF1B\u9700\u8981\u5934\u90E8\u4E4B\u5916\u7684\u6B63\u6587\u65F6\uFF0C\u7528 ak_collector_read \u6309 evidenceId \u5F00\u5377\uFF1Bfindings \u7684\u62C6\u5206\u4E0E\u5F52\u7C7B\u7531\u4F60\u5728\u4EA4\u4EF6\u65F6\u5B8C\u6210\u3002",
19791
- promptSnippet: "\u6293\u53D6\u914D\u7F6E\u76EE\u6807 PR \u8BC1\u636E",
19792
- parameters: observeSchema,
19793
- async execute(toolCallId, _params, signal, _onUpdate, ctx) {
19794
- if (activation === void 0) {
19795
- throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
19796
- }
19797
- try {
19798
- activation.ledger.beginOperational(COLLECTOR_OBSERVE_TOOL, toolCallId);
19799
- const { snapshot, contextView } = await activation.ledger.observe(
19800
- activation.transport,
19801
- activation.clock,
19802
- signal
19803
- );
19804
- activation.ledger.completeOperational(toolCallId);
19805
- if (snapshot.prState !== "OPEN") {
20321
+ },
20322
+ registerBusinessTools(getActivation) {
20323
+ if (toolsRegistered) return;
20324
+ toolsRegistered = true;
20325
+ pi.registerTool({
20326
+ name: COLLECTOR_BIND_TARGET_TOOL,
20327
+ label: "\u901A\u8FDB\u53F8\u8BA4\u7968\u7ED1\u5B9A",
20328
+ description: "\u89D2\u8272\u5224\u5B9A\u4EFB\u52A1\u6750\u6599\u540E\u7ED1\u5B9A\u672C\u4ED3\u552F\u4E00 PR \u76EE\u6807\u3002\u53EF\u63D0\u4EA4 prNumber \u6216 issueNumber\uFF08\u7EBF\u4E0A\u5173\u8054\u552F\u4E00 PR\uFF09\uFF1B\u663E\u5F0F --pr \u5DF2\u7ED1\u5B9A\u65F6\u65E0\u9700\u518D\u8C03\u3002\u591A\u4E49\u6216\u65E0\u6CD5\u786E\u5B9A\u65F6\u4F1A\u6B63\u786E\u9A73\u56DE\uFF0C\u8981\u6C42\u8C03\u7528\u65B9\u660E\u786E --pr\u3002",
20329
+ promptSnippet: "\u7ED1\u5B9A\u89D2\u8272\u5224\u5B9A\u7684 issue/PR \u76EE\u6807",
20330
+ parameters: bindSchema,
20331
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
20332
+ const activation = getActivation();
20333
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20334
+ try {
20335
+ activation.ledger.beginOperational(COLLECTOR_BIND_TARGET_TOOL, toolCallId);
20336
+ const prNumber = parsePositiveTicket(params.prNumber, "prNumber");
20337
+ const issueNumber = parsePositiveTicket(params.issueNumber, "issueNumber");
20338
+ if (prNumber === void 0 && issueNumber === void 0) {
20339
+ throw new CollectorTargetBindError(
20340
+ "ak_collector_bind_target requires role-decided prNumber and/or issueNumber"
20341
+ );
20342
+ }
20343
+ let bound = prNumber;
20344
+ if (issueNumber !== void 0) {
20345
+ const associated = await listPullRequestNumbersByTicket(createGhApiRunner(), {
20346
+ owner: activation.repository.owner,
20347
+ repo: activation.repository.repo,
20348
+ ticketNumber: issueNumber
20349
+ });
20350
+ if (associated.length === 0) {
20351
+ throw new CollectorTargetBindError(
20352
+ `no PR associated with issue #${issueNumber} in ${activation.repository.canonical}; pass an explicit --pr or a different issueNumber`
20353
+ );
20354
+ }
20355
+ if (associated.length > 1) {
20356
+ throw new CollectorTargetBindError(
20357
+ `multiple PRs associated with issue #${issueNumber}: ${associated.join(", ")}; pass an explicit prNumber or --pr`
20358
+ );
20359
+ }
20360
+ const fromIssue = associated[0];
20361
+ if (prNumber !== void 0 && prNumber !== fromIssue) {
20362
+ throw new CollectorTargetBindError(
20363
+ `prNumber ${prNumber} conflicts with issue #${issueNumber} association PR ${fromIssue}`
20364
+ );
20365
+ }
20366
+ bound = fromIssue;
20367
+ }
20368
+ activation.ledger.bindTarget(bound);
20369
+ activation.ledger.completeOperational(toolCallId);
20370
+ return {
20371
+ content: [{
20372
+ type: "text",
20373
+ text: `\u76EE\u6807\u5DF2\u7ED1\u5B9A\uFF1A${activation.repository.canonical}#${bound}`
20374
+ }],
20375
+ details: {
20376
+ repository: activation.repository.canonical,
20377
+ prNumber: bound,
20378
+ ...issueNumber === void 0 ? {} : { issueNumber }
20379
+ }
20380
+ };
20381
+ } catch (error) {
20382
+ if (isCorrectableExecuteError(error)) throw error;
20383
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20384
+ } finally {
20385
+ try {
20386
+ activation.ledger.completeOperational(toolCallId);
20387
+ } catch {
20388
+ }
19806
20389
  }
19807
- return {
19808
- content: [{
19809
- type: "text",
19810
- // #641 chain①: model context carries bounded body heads + pointers only.
19811
- text: JSON.stringify(contextView)
19812
- }],
19813
- // #641 the bounded projection is the only provider-visible face on every host
19814
- // (Grok/ACP relays tool details as MCP structuredContent); full bodies stay
19815
- // in the ledger volume and enter context only by explicit ak_collector_read.
19816
- details: contextView
19817
- };
19818
- } catch (error) {
19819
- hostActions.failInfrastructure(error, ctx, toolCallId);
19820
20390
  }
19821
- }
19822
- });
19823
- pi.registerTool({
19824
- name: COLLECTOR_READ_TOOL,
19825
- label: "\u901A\u8FDB\u53F8\u5F00\u5377",
19826
- description: "\u6309 evidenceId \u5F00\u5377\u8BFB\u53D6\u4E00\u6761\u5DF2\u89C2\u6D4B\u6750\u6599\u7684\u5168\u91CF\u6B63\u6587\u4E0E\u6307\u9488\uFF1B\u53EA\u5728\u89C2\u5BDF\u5934\u90E8\u6458\u5F55\u4E0D\u8DB3\u4EE5\u5224\u8BFB\u65F6\u8C03\u7528\u3002",
19827
- promptSnippet: "\u6309\u6307\u9488\u5F00\u5377\u8BFB\u6750\u6599",
19828
- parameters: readSchema,
19829
- async execute(toolCallId, params, _signal, _onUpdate, ctx) {
19830
- if (activation === void 0) {
19831
- throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
19832
- }
19833
- try {
19834
- activation.ledger.beginOperational(COLLECTOR_READ_TOOL, toolCallId);
19835
- const record4 = activation.ledger.getEvidence(params.evidenceId);
19836
- if (record4 === void 0 || record4.kind !== "review" && record4.kind !== "issue_comment" && record4.kind !== "review_comment" && record4.kind !== "reaction" || typeof record4.body !== "string") {
19837
- throw new CollectorUnknownEvidenceError(params.evidenceId);
20391
+ });
20392
+ pi.registerTool({
20393
+ name: COLLECTOR_OBSERVE_TOOL,
20394
+ label: "\u901A\u8FDB\u53F8\u89C2\u5BDF",
20395
+ description: "\u6293\u53D6\u914D\u7F6E\u76EE\u6807\u7684\u5B8C\u6574 GitHub PR \u8BC1\u636E\uFF0C\u5B58\u4E0D\u53EF\u53D8\u5FEB\u7167\u5165\u5377\u3002\u6B63\u6587\u5728\u4E0A\u4E0B\u6587\u4E2D\u53EA\u7ED9\u5934\u90E8\u6458\u5F55\u52A0\u6307\u9488\uFF1B\u9700\u8981\u5934\u90E8\u4E4B\u5916\u7684\u6B63\u6587\u65F6\uFF0C\u7528 ak_collector_read \u6309 evidenceId \u5F00\u5377\uFF1Bfindings \u7684\u62C6\u5206\u4E0E\u5F52\u7C7B\u7531\u4F60\u5728\u4EA4\u4EF6\u65F6\u5B8C\u6210\u3002\u76EE\u6807\u672A\u7ED1\u5B9A\u524D\u987B\u5148 ak_collector_bind_target\u3002",
20396
+ promptSnippet: "\u6293\u53D6\u914D\u7F6E\u76EE\u6807 PR \u8BC1\u636E",
20397
+ parameters: observeSchema,
20398
+ async execute(toolCallId, _params, signal, _onUpdate, ctx) {
20399
+ const activation = getActivation();
20400
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20401
+ try {
20402
+ activation.ledger.beginOperational(COLLECTOR_OBSERVE_TOOL, toolCallId);
20403
+ const { snapshot, contextView } = await activation.ledger.observe(
20404
+ activation.transport,
20405
+ activation.clock,
20406
+ signal
20407
+ );
20408
+ activation.ledger.completeOperational(toolCallId);
20409
+ return {
20410
+ content: [{
20411
+ type: "text",
20412
+ text: JSON.stringify(contextView)
20413
+ }],
20414
+ details: contextView
20415
+ };
20416
+ } catch (error) {
20417
+ if (isCorrectableExecuteError(error)) throw error;
20418
+ hostActions.failInfrastructure(error, ctx, toolCallId);
19838
20419
  }
19839
- const material = projectEvidenceEntryView(record4);
19840
- activation.ledger.completeOperational(toolCallId);
19841
- return {
19842
- content: [{
19843
- type: "text",
19844
- // #641 chain①: full bodies enter provider context only by explicit pointer.
19845
- text: JSON.stringify(material)
19846
- }],
19847
- details: material
19848
- };
19849
- } catch (error) {
19850
- if (isCorrectableExecuteError(error)) throw error;
19851
- hostActions.failInfrastructure(error, ctx, toolCallId);
19852
- }
19853
- }
19854
- });
19855
- pi.registerTool({
19856
- name: COLLECTOR_REQUEST_TOOL,
19857
- label: "\u901A\u8FDB\u53F8\u8BF7\u6C42",
19858
- description: "\u6309\u914D\u7F6E\u8BF7\u6C42\u4F53\u4E0E\u5173\u8054\u6807\u8BB0\uFF0C\u5728\u6240\u5F15\u6700\u65B0\u5FEB\u7167 HEAD \u53D1\u4E00\u6B21\u8BF7\u6C42\u3002",
19859
- promptSnippet: "\u6309\u914D\u7F6E\u53D1\u4E00\u6B21\u8BF7\u6C42",
19860
- parameters: requestSchema,
19861
- async execute(toolCallId, params, signal, _onUpdate, ctx) {
19862
- if (activation === void 0) {
19863
- throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
19864
- }
19865
- try {
19866
- activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
19867
- const details = await activation.ledger.request(
19868
- params,
19869
- activation.transport,
19870
- activation.clock,
19871
- signal
19872
- );
19873
- activation.ledger.completeOperational(toolCallId);
19874
- return {
19875
- content: [{
19876
- type: "text",
19877
- text: `\u8BF7\u6C42\u5C1D\u8BD5\u5DF2\u8BB0\u5F55\uFF1Arequest ${params.requestId}`
19878
- }],
19879
- details
19880
- };
19881
- } catch (error) {
19882
- hostActions.failInfrastructure(error, ctx, toolCallId);
19883
- }
19884
- }
19885
- });
19886
- pi.registerTool({
19887
- name: COLLECTOR_WAIT_TOOL,
19888
- label: "\u901A\u8FDB\u53F8\u7B49\u5F85",
19889
- description: "\u518D\u89C2\u5BDF\u524D\u7B49\u5F85\uFF1B\u5355\u6B21\u4E0A\u9650\u4E94\u5206\u949F\u4E14\u4E0D\u8D85\u5269\u4F59\u8D44\u683C\u3002",
19890
- promptSnippet: "\u8D44\u683C\u622A\u6B62\u524D\u7B49\u5F85",
19891
- parameters: waitSchema,
19892
- async execute(toolCallId, params, signal, _onUpdate, ctx) {
19893
- if (activation === void 0) {
19894
- throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
19895
- }
19896
- try {
19897
- activation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
19898
- const details = await activation.ledger.wait(
19899
- params,
19900
- activation.clock,
19901
- signal
19902
- );
19903
- activation.ledger.completeOperational(toolCallId);
19904
- return {
19905
- content: [{
19906
- type: "text",
19907
- text: `\u5DF2\u7B49\u5F85 ${String(details.effectiveMs)}ms`
19908
- }],
19909
- details
19910
- };
19911
- } catch (error) {
19912
- hostActions.failInfrastructure(error, ctx, toolCallId);
19913
- }
19914
- }
19915
- });
19916
- pi.registerTool({
19917
- name: COLLECTOR_OUTPUT_TOOL,
19918
- label: "\u901A\u8FDB\u53F8\u8F93\u51FA",
19919
- description: "\u89C2\u5BDF\u5B8C\u6210\u540E\u63D0\u4EA4\uFF1B\u56DE\u6267\u7531 runtime \u7EC4\u88C5\u3002\u6B63\u5E38\u5B8C\u5DE5\u63D0\u4EA4\u7A7A\u5BF9\u8C61 {}\uFF08\u5982\u9700\u62A5 finding\uFF0C\u586B findings \u6307\u9488\u6570\u7EC4\uFF09\uFF1B\u4EC5\u5728\u57FA\u7840\u8BBE\u65BD\u771F\u5B9E\u5931\u8D25\u65F6\u624D\u53EF\u586B infrastructureFailure\uFF0C\u65E0\u5931\u8D25\u65F6\u5FC5\u987B\u7701\u7565\u8BE5\u5B57\u6BB5\u3002",
19920
- promptSnippet: "\u63D0\u4EA4\u901A\u8FDB\u53F8\u56DE\u6267",
19921
- // #641 chain②: the seat owns the normal-completion decision. The probe
19922
- // runs the exact receipt assembly (validation only, no accept side
19923
- // effects); success ⇒ machine-verified normal completion ⇒ bounce.
19924
- bounceInfrastructureDeclaration(params) {
19925
- if (activation === void 0) return void 0;
19926
- try {
19927
- buildCollectorReceipt(activation.ledger, params, activation.clock);
19928
- } catch {
19929
- return void 0;
19930
- }
19931
- return new CollectorNormalCompletionDeclarationError();
19932
- },
19933
- parameters: outputSchema,
19934
- async execute(toolCallId, params, _signal, _onUpdate, ctx) {
19935
- if (activation === void 0) {
19936
- throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
19937
20420
  }
19938
- try {
19939
- activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, toolCallId);
19940
- const receipt = buildCollectorReceipt(
19941
- activation.ledger,
19942
- params,
19943
- activation.clock
19944
- );
19945
- activation.ledger.recordOutputCandidate();
19946
- const acceptedDetails = receipt;
19947
- return {
19948
- content: [{
19949
- type: "text",
19950
- text: COLLECTOR_ACCEPTED_TEXT
19951
- }],
19952
- details: acceptedDetails,
19953
- terminate: true
19954
- };
19955
- } catch (error) {
19956
- if (error instanceof Error && error.collectorFatal === true) {
20421
+ });
20422
+ pi.registerTool({
20423
+ name: COLLECTOR_READ_TOOL,
20424
+ label: "\u901A\u8FDB\u53F8\u5F00\u5377",
20425
+ description: "\u6309 evidenceId \u5F00\u5377\u8BFB\u53D6\u4E00\u6761\u5DF2\u89C2\u6D4B\u6750\u6599\u7684\u5168\u91CF\u6B63\u6587\u4E0E\u6307\u9488\uFF1B\u53EA\u5728\u89C2\u5BDF\u5934\u90E8\u6458\u5F55\u4E0D\u8DB3\u4EE5\u5224\u8BFB\u65F6\u8C03\u7528\u3002",
20426
+ promptSnippet: "\u6309\u6307\u9488\u5F00\u5377\u8BFB\u6750\u6599",
20427
+ parameters: readSchema,
20428
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
20429
+ const activation = getActivation();
20430
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20431
+ try {
20432
+ activation.ledger.beginOperational(COLLECTOR_READ_TOOL, toolCallId);
20433
+ const record4 = activation.ledger.getEvidence(params.evidenceId);
20434
+ if (record4 === void 0 || record4.kind !== "review" && record4.kind !== "issue_comment" && record4.kind !== "review_comment" && record4.kind !== "reaction" || typeof record4.body !== "string") {
20435
+ throw new CollectorUnknownEvidenceError(params.evidenceId);
20436
+ }
20437
+ const material = projectEvidenceEntryView(record4);
20438
+ activation.ledger.completeOperational(toolCallId);
20439
+ return {
20440
+ content: [{
20441
+ type: "text",
20442
+ text: JSON.stringify(material)
20443
+ }],
20444
+ details: material
20445
+ };
20446
+ } catch (error) {
20447
+ if (isCorrectableExecuteError(error)) throw error;
19957
20448
  hostActions.failInfrastructure(error, ctx, toolCallId);
19958
20449
  }
19959
- throw error;
19960
- } finally {
19961
- activation.ledger.completeOperational(toolCallId);
19962
20450
  }
19963
- }
19964
- });
19965
- };
19966
- return {
19967
- async activate(ctx, event) {
19968
- activation = void 0;
19969
- ensureLifecycle();
19970
- if (ctx.mode !== "print" && ctx.mode !== "json") {
19971
- throw new Error(
19972
- `Collector supports only print or json mode (got ${ctx.mode})`
19973
- );
19974
- }
19975
- if (event.reason === "fork" || event.reason === "reload") {
19976
- throw new Error(
19977
- `Collector does not support session_start reason ${event.reason}`
19978
- );
19979
- }
19980
- const soul = (await dependencies.loadSoul()).trim();
19981
- if (soul.length === 0) throw new Error("Collector soul is empty");
19982
- const repoFlag = pi.getFlag("ak-collector-repo");
19983
- const prFlag = pi.getFlag("ak-collector-pr");
19984
- const requestManifestFlag = pi.getFlag("ak-collector-request-manifest");
19985
- if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
19986
- throw new Error("Collector requires --ak-collector-repo");
19987
- }
19988
- if (typeof prFlag !== "string" && typeof prFlag !== "number") {
19989
- throw new Error("Collector requires --ak-collector-pr");
19990
- }
19991
- const repository = parseCollectorRepository(repoFlag);
19992
- const prNumber = parseCollectorPrNumber(prFlag);
19993
- const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0 ? await loadCollectorManifest(requestManifestFlag) : emptyCollectorManifest();
19994
- const commands = pi.getCommands?.() ?? [];
19995
- const ambientCommands = commands.filter((command) => {
19996
- const name = command.name.toLowerCase();
19997
- return name.includes("skill") || name.includes("prompt") || name.startsWith("template");
19998
20451
  });
19999
- if (ambientCommands.length > 0) {
20000
- throw new Error(
20001
- `Collector detected ambient instruction commands: ${ambientCommands.map((c) => c.name).join(", ")}`
20002
- );
20003
- }
20004
- const preExisting = pi.getAllTools();
20005
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
20006
- const prior = preExisting.filter((tool) => tool.name === required);
20007
- if (prior.length > 0) {
20008
- throw new Error(`Collector required tool name collision: ${required}`);
20009
- }
20010
- }
20011
- registerTools();
20012
- const allTools = pi.getAllTools();
20013
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
20014
- const matches = allTools.filter((tool2) => tool2.name === required);
20015
- if (matches.length === 0) {
20016
- throw new Error(`Collector required tool missing: ${required}`);
20017
- }
20018
- if (matches.length > 1) {
20019
- throw new Error(`Collector required tool name collision: ${required}`);
20020
- }
20021
- const tool = matches[0];
20022
- if (dependencies.packageExtensionPath !== void 0 && tool.sourceInfo?.path !== void 0 && tool.sourceInfo.path !== dependencies.packageExtensionPath && !tool.sourceInfo.path.includes("role-runtime")) {
20023
- throw new Error(
20024
- `Collector required tool ${required} is overridden by ${tool.sourceInfo.path}`
20025
- );
20452
+ pi.registerTool({
20453
+ name: COLLECTOR_REQUEST_TOOL,
20454
+ label: "\u901A\u8FDB\u53F8\u8BF7\u6C42",
20455
+ description: "\u6309\u914D\u7F6E\u8BF7\u6C42\u4F53\u4E0E\u5173\u8054\u6807\u8BB0\uFF0C\u5728\u6240\u5F15\u6700\u65B0\u5FEB\u7167 HEAD \u53D1\u4E00\u6B21\u8BF7\u6C42\u3002",
20456
+ promptSnippet: "\u6309\u914D\u7F6E\u53D1\u4E00\u6B21\u8BF7\u6C42",
20457
+ parameters: requestSchema,
20458
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
20459
+ const activation = getActivation();
20460
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20461
+ try {
20462
+ activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
20463
+ const details = await activation.ledger.request(
20464
+ params,
20465
+ activation.transport,
20466
+ activation.clock,
20467
+ signal
20468
+ );
20469
+ activation.ledger.completeOperational(toolCallId);
20470
+ return {
20471
+ content: [{
20472
+ type: "text",
20473
+ text: `\u8BF7\u6C42\u5C1D\u8BD5\u5DF2\u8BB0\u5F55\uFF1Arequest ${params.requestId}`
20474
+ }],
20475
+ details
20476
+ };
20477
+ } catch (error) {
20478
+ if (isCorrectableExecuteError(error)) throw error;
20479
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20480
+ }
20026
20481
  }
20027
- }
20028
- pi.setActiveTools([...COLLECTOR_REQUIRED_TOOLS]);
20029
- const active = new Set(pi.getActiveTools());
20030
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
20031
- if (!active.has(required)) {
20032
- throw new Error(`Collector failed to activate required tool ${required}`);
20482
+ });
20483
+ pi.registerTool({
20484
+ name: COLLECTOR_WAIT_TOOL,
20485
+ label: "\u901A\u8FDB\u53F8\u7B49\u5F85",
20486
+ description: "\u518D\u89C2\u5BDF\u524D\u7B49\u5F85\uFF1B\u5355\u6B21\u4E0A\u9650\u4E94\u5206\u949F\u4E14\u4E0D\u8D85\u5269\u4F59\u8D44\u683C\u3002",
20487
+ promptSnippet: "\u8D44\u683C\u622A\u6B62\u524D\u7B49\u5F85",
20488
+ parameters: waitSchema,
20489
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
20490
+ const activation = getActivation();
20491
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20492
+ try {
20493
+ activation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
20494
+ const details = await activation.ledger.wait(
20495
+ params,
20496
+ activation.clock,
20497
+ signal
20498
+ );
20499
+ activation.ledger.completeOperational(toolCallId);
20500
+ return {
20501
+ content: [{
20502
+ type: "text",
20503
+ text: `\u5DF2\u7B49\u5F85 ${String(details.effectiveMs)}ms`
20504
+ }],
20505
+ details
20506
+ };
20507
+ } catch (error) {
20508
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20509
+ }
20033
20510
  }
20034
- }
20035
- for (const name of active) {
20036
- if (!COLLECTOR_REQUIRED_TOOLS.includes(name)) {
20037
- throw new Error(`Collector active tool surface includes unexpected ${name}`);
20511
+ });
20512
+ pi.registerTool({
20513
+ name: COLLECTOR_OUTPUT_TOOL,
20514
+ label: "\u901A\u8FDB\u53F8\u8F93\u51FA",
20515
+ description: "\u89C2\u5BDF\u5B8C\u6210\u540E\u63D0\u4EA4\uFF1B\u56DE\u6267\u7531 runtime \u7EC4\u88C5\u3002\u6B63\u5E38\u5B8C\u5DE5\u63D0\u4EA4\u7A7A\u5BF9\u8C61 {}\uFF08\u5982\u9700\u62A5 finding\uFF0C\u586B findings \u6307\u9488\u6570\u7EC4\uFF09\uFF1B\u4EC5\u5728\u57FA\u7840\u8BBE\u65BD\u771F\u5B9E\u5931\u8D25\u65F6\u624D\u53EF\u586B infrastructureFailure\uFF0C\u65E0\u5931\u8D25\u65F6\u5FC5\u987B\u7701\u7565\u8BE5\u5B57\u6BB5\u3002",
20516
+ promptSnippet: "\u63D0\u4EA4\u901A\u8FDB\u53F8\u56DE\u6267",
20517
+ bounceInfrastructureDeclaration(params) {
20518
+ const activation = getActivation();
20519
+ if (activation === void 0) return void 0;
20520
+ try {
20521
+ buildCollectorReceipt(activation.ledger, params, activation.clock);
20522
+ } catch {
20523
+ return void 0;
20524
+ }
20525
+ return new CollectorNormalCompletionDeclarationError();
20526
+ },
20527
+ parameters: outputSchema,
20528
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
20529
+ const activation = getActivation();
20530
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20531
+ try {
20532
+ activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, toolCallId);
20533
+ const receipt = buildCollectorReceipt(
20534
+ activation.ledger,
20535
+ params,
20536
+ activation.clock
20537
+ );
20538
+ activation.ledger.recordOutputCandidate();
20539
+ return {
20540
+ content: [{
20541
+ type: "text",
20542
+ text: COLLECTOR_ACCEPTED_TEXT
20543
+ }],
20544
+ details: receipt,
20545
+ terminate: true
20546
+ };
20547
+ } catch (error) {
20548
+ if (error instanceof Error && error.collectorFatal === true) {
20549
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20550
+ }
20551
+ throw error;
20552
+ } finally {
20553
+ activation.ledger.completeOperational(toolCallId);
20554
+ }
20038
20555
  }
20039
- }
20040
- const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
20041
- const transport = dependencies.createTransport();
20042
- const ledger = dependencies.createLedger(
20043
- { repository, prNumber, manifest },
20044
- clock,
20045
- ctx
20046
- );
20047
- if (ledger.activationRecorded) {
20048
- firstDispatchDone = true;
20049
- }
20050
- activation = {
20051
- soul,
20052
- repository,
20053
- prNumber,
20054
- manifest,
20055
- ledger,
20056
- transport,
20057
- clock
20058
- };
20556
+ });
20059
20557
  }
20060
20558
  };
20061
20559
  }
20062
- var CollectorNormalCompletionDeclarationError, COLLECTOR_REQUIRED_TOOLS, observeSchema, readSchema, requestSchema, waitSchema, outputSchema;
20560
+ var CollectorNormalCompletionDeclarationError, CollectorTargetBindError, COLLECTOR_REQUIRED_TOOLS, COLLECTOR_TRANSPORT_FLAGS, observeSchema, readSchema, requestSchema, waitSchema, bindSchema, outputSchema;
20063
20561
  var init_collector_role = __esm({
20064
20562
  "src/collector-role.ts"() {
20065
20563
  "use strict";
20066
20564
  init_collector_config();
20067
20565
  init_collector_evidence();
20566
+ init_collector_github();
20068
20567
  init_collector_ledger();
20069
20568
  init_collector_receipt();
20070
20569
  init_collector_tool_schemas();
@@ -20072,24 +20571,54 @@ var init_collector_role = __esm({
20072
20571
  init_submission_correctable_error();
20073
20572
  init_collector_identity();
20074
20573
  init_collector_identity();
20075
- init_collector_config();
20076
20574
  CollectorNormalCompletionDeclarationError = class extends CorrectableSubmissionError {
20077
20575
  constructor() {
20078
20576
  super("runtime \u5DF2\u6309\u673A\u5668\u72B6\u6001\u6838\u9A8C\u672C\u5C40\u4E3A\u6B63\u5E38\u5B8C\u5DE5\uFF08\u56DE\u6267\u53EF\u5408\u6CD5\u7EC4\u88C5\uFF09\uFF1A\u6B63\u5E38\u5B8C\u5DE5\u7684\u4EA4\u4EF6\u4E0D\u5F97\u586B infrastructureFailure\uFF0C\u8BF7\u7701\u7565\u8BE5\u5B57\u6BB5\u540E\u91CD\u65B0\u63D0\u4EA4\u3002");
20079
20577
  this.name = "CollectorNormalCompletionDeclarationError";
20080
20578
  }
20081
20579
  };
20580
+ CollectorTargetBindError = class extends CorrectableSubmissionError {
20581
+ constructor(message) {
20582
+ super(message);
20583
+ this.name = "CollectorTargetBindError";
20584
+ }
20585
+ };
20082
20586
  COLLECTOR_REQUIRED_TOOLS = [
20587
+ COLLECTOR_BIND_TARGET_TOOL,
20083
20588
  COLLECTOR_OBSERVE_TOOL,
20084
20589
  COLLECTOR_READ_TOOL,
20085
20590
  COLLECTOR_REQUEST_TOOL,
20086
20591
  COLLECTOR_WAIT_TOOL,
20087
20592
  COLLECTOR_OUTPUT_TOOL
20088
20593
  ];
20594
+ COLLECTOR_TRANSPORT_FLAGS = Object.freeze([
20595
+ Object.freeze({
20596
+ name: "ak-collector-repo",
20597
+ definition: Object.freeze({
20598
+ description: "GitHub owner/repo target for Collector (github.com only; conservative ASCII grammar).",
20599
+ type: "string"
20600
+ })
20601
+ }),
20602
+ Object.freeze({
20603
+ name: "ak-collector-pr",
20604
+ definition: Object.freeze({
20605
+ description: "Optional positive safe-integer pull request number for Collector. Omit when the role will bind from task materials.",
20606
+ type: "string"
20607
+ })
20608
+ }),
20609
+ Object.freeze({
20610
+ name: "ak-collector-request-manifest",
20611
+ definition: Object.freeze({
20612
+ description: "Path to the Collector v1 request manifest JSON file.",
20613
+ type: "string"
20614
+ })
20615
+ })
20616
+ ]);
20089
20617
  observeSchema = collectorObserveArgsSchema;
20090
20618
  readSchema = collectorReadArgsSchema;
20091
20619
  requestSchema = collectorRequestArgsSchema;
20092
20620
  waitSchema = collectorWaitArgsSchema;
20621
+ bindSchema = collectorBindTargetArgsSchema;
20093
20622
  outputSchema = collectorOutputArgsSchema;
20094
20623
  }
20095
20624
  });
@@ -21070,11 +21599,11 @@ var init_reviewer_role = __esm({
21070
21599
  });
21071
21600
 
21072
21601
  // src/worker-submission-gates.ts
21073
- import { execFileSync as execFileSync3 } from "node:child_process";
21602
+ import { execFileSync as execFileSync4 } from "node:child_process";
21074
21603
  import { existsSync as existsSync10, lstatSync as lstatSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmdirSync, rmSync } from "node:fs";
21075
21604
  import { resolve as resolve18 } from "node:path";
21076
21605
  function git2(cwd, args) {
21077
- return execFileSync3("git", args, {
21606
+ return execFileSync4("git", args, {
21078
21607
  cwd,
21079
21608
  encoding: "utf8",
21080
21609
  stdio: ["ignore", "pipe", "pipe"],
@@ -21082,7 +21611,7 @@ function git2(cwd, args) {
21082
21611
  }).trim();
21083
21612
  }
21084
21613
  function gitFile(file, args) {
21085
- return execFileSync3("git", ["config", "--file", file, ...args], {
21614
+ return execFileSync4("git", ["config", "--file", file, ...args], {
21086
21615
  encoding: "utf8",
21087
21616
  stdio: ["ignore", "pipe", "pipe"],
21088
21617
  env: { ...process.env, GIT_DIR: void 0, GIT_WORK_TREE: void 0, GIT_COMMON_DIR: void 0 }
@@ -22310,6 +22839,9 @@ function createRoleRuntimeExtension(dependencies) {
22310
22839
  for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
22311
22840
  roleHost.registerFlag(flag.name, flag.definition);
22312
22841
  }
22842
+ for (const flag of COLLECTOR_TRANSPORT_FLAGS) {
22843
+ roleHost.registerFlag(flag.name, flag.definition);
22844
+ }
22313
22845
  let admitted = false;
22314
22846
  let selectedRole;
22315
22847
  let activeReviewerParent;
@@ -22440,10 +22972,41 @@ function createRoleRuntimeExtension(dependencies) {
22440
22972
  })
22441
22973
  };
22442
22974
  }
22975
+ if (role === "collector" && activeCollector !== void 0) {
22976
+ const options = event.systemPromptOptions;
22977
+ if (options.skills && options.skills.length > 0) {
22978
+ failInfrastructure(
22979
+ activeCollector.ledger.latchFatal("\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230\u7CFB\u7EDF\u63D0\u793A\u4E2D\u7684\u73AF\u5883 skills"),
22980
+ ctx
22981
+ );
22982
+ }
22983
+ if (options.contextFiles && options.contextFiles.length > 0) {
22984
+ failInfrastructure(
22985
+ activeCollector.ledger.latchFatal("\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230\u7CFB\u7EDF\u63D0\u793A\u4E2D\u7684\u73AF\u5883 context files"),
22986
+ ctx
22987
+ );
22988
+ }
22989
+ if (typeof options.appendSystemPrompt === "string" && options.appendSystemPrompt.trim().length > 0) {
22990
+ failInfrastructure(
22991
+ activeCollector.ledger.latchFatal("\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230 appendSystemPrompt \u6F02\u79FB"),
22992
+ ctx
22993
+ );
22994
+ }
22995
+ if (!collectorFirstDispatchDone) {
22996
+ collectorFirstDispatchDone = true;
22997
+ activeCollector.ledger.recordActivation(activeCollector.clock);
22998
+ }
22999
+ return {
23000
+ systemPrompt: collectorBusiness.assembleMaterials(activeCollector, event.systemPrompt)
23001
+ };
23002
+ }
22443
23003
  });
22444
23004
  roleHost.on("tool_result", async (event) => {
22445
23005
  const role = selectedRole;
22446
23006
  if (role === void 0) return;
23007
+ if (role === "collector" && activeCollector !== void 0) {
23008
+ collectorBusiness.onToolResult(activeCollector, event);
23009
+ }
22447
23010
  const pendingInfra = pendingInfrastructureFailures.get(event.toolCallId);
22448
23011
  const isRoleInfrastructureFailure = pendingInfra !== void 0;
22449
23012
  if (pendingInfra !== void 0) pendingInfrastructureFailures.delete(event.toolCallId);
@@ -22541,6 +23104,11 @@ function createRoleRuntimeExtension(dependencies) {
22541
23104
  priorFetch = void 0;
22542
23105
  fetchWrapped = false;
22543
23106
  }
23107
+ if (selectedRole === "collector" && activeCollector !== void 0 && activeCollector.ledger.fatal) {
23108
+ if (process.exitCode === void 0 || process.exitCode === 0) {
23109
+ process.exitCode = 1;
23110
+ }
23111
+ }
22544
23112
  const presentation = pendingNavigatorPresentation;
22545
23113
  pendingNavigatorPresentation = void 0;
22546
23114
  if (presentation !== void 0) {
@@ -22742,7 +23310,9 @@ function createRoleRuntimeExtension(dependencies) {
22742
23310
  }
22743
23311
  }
22744
23312
  }, hostActions);
22745
- const collector = createCollectorRoleRuntime(
23313
+ let activeCollector;
23314
+ let collectorFirstDispatchDone = false;
23315
+ const collectorBusiness = createCollectorRoleRuntime(
22746
23316
  roleHost,
22747
23317
  {
22748
23318
  async loadSoul() {
@@ -22771,13 +23341,90 @@ function createRoleRuntimeExtension(dependencies) {
22771
23341
  dossierEntries: context.sessionManager?.getEntries?.() ?? []
22772
23342
  });
22773
23343
  },
22774
- ...dependencies.createCollectorClock === void 0 ? {} : { createClock: dependencies.createCollectorClock },
22775
- ...dependencies.collectorPackageExtensionPath === void 0 ? {} : {
22776
- packageExtensionPath: dependencies.collectorPackageExtensionPath
22777
- }
23344
+ ...dependencies.createCollectorClock === void 0 ? {} : { createClock: dependencies.createCollectorClock }
22778
23345
  },
22779
23346
  hostActions
22780
23347
  );
23348
+ let collectorToolCallRegistered = false;
23349
+ const collector = {
23350
+ async activate(context, event) {
23351
+ activeCollector = void 0;
23352
+ collectorFirstDispatchDone = false;
23353
+ if (context.mode !== "print" && context.mode !== "json") {
23354
+ throw new Error(
23355
+ `Collector supports only print or json mode (got ${context.mode})`
23356
+ );
23357
+ }
23358
+ if (event.reason === "fork" || event.reason === "reload") {
23359
+ throw new Error(
23360
+ `Collector does not support session_start reason ${event.reason}`
23361
+ );
23362
+ }
23363
+ const commands = roleHost.getCommands?.() ?? [];
23364
+ const ambientCommands = commands.filter((command) => {
23365
+ const name = command.name.toLowerCase();
23366
+ return name.includes("skill") || name.includes("prompt") || name.startsWith("template");
23367
+ });
23368
+ if (ambientCommands.length > 0) {
23369
+ throw new Error(
23370
+ `Collector detected ambient instruction commands: ${ambientCommands.map((c) => c.name).join(", ")}`
23371
+ );
23372
+ }
23373
+ const preExisting = roleHost.getAllTools();
23374
+ const alreadyRegistered = COLLECTOR_REQUIRED_TOOLS.every(
23375
+ (required) => preExisting.some((tool) => tool.name === required)
23376
+ );
23377
+ if (!alreadyRegistered) {
23378
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
23379
+ const prior = preExisting.filter((tool) => tool.name === required);
23380
+ if (prior.length > 0) {
23381
+ throw new Error(`Collector required tool name collision: ${required}`);
23382
+ }
23383
+ }
23384
+ }
23385
+ collectorBusiness.registerBusinessTools(() => activeCollector);
23386
+ const allTools = roleHost.getAllTools();
23387
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
23388
+ const matches = allTools.filter((tool) => tool.name === required);
23389
+ if (matches.length === 0) {
23390
+ throw new Error(`Collector required tool missing: ${required}`);
23391
+ }
23392
+ if (matches.length > 1) {
23393
+ throw new Error(`Collector required tool name collision: ${required}`);
23394
+ }
23395
+ }
23396
+ roleHost.setActiveTools([...COLLECTOR_REQUIRED_TOOLS]);
23397
+ const active = new Set(roleHost.getActiveTools());
23398
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
23399
+ if (!active.has(required)) {
23400
+ throw new Error(`Collector failed to activate required tool ${required}`);
23401
+ }
23402
+ }
23403
+ for (const name of active) {
23404
+ if (!COLLECTOR_REQUIRED_TOOLS.includes(name)) {
23405
+ throw new Error(`Collector active tool surface includes unexpected ${name}`);
23406
+ }
23407
+ }
23408
+ if (!collectorToolCallRegistered) {
23409
+ collectorToolCallRegistered = true;
23410
+ roleHost.on("tool_call", (toolEvent) => {
23411
+ if (activeCollector === void 0 || selectedRole !== "collector") return;
23412
+ if (!COLLECTOR_REQUIRED_TOOLS.includes(toolEvent.toolName)) {
23413
+ return {
23414
+ block: true,
23415
+ reason: `\u901A\u8FDB\u53F8\u7981\u7528\u5DE5\u5177 ${toolEvent.toolName}`
23416
+ };
23417
+ }
23418
+ return collectorBusiness.onToolCall(activeCollector, toolEvent);
23419
+ });
23420
+ }
23421
+ const activation = await collectorBusiness.activate(context);
23422
+ if (activation.ledger.activationRecorded) {
23423
+ collectorFirstDispatchDone = true;
23424
+ }
23425
+ activeCollector = activation;
23426
+ }
23427
+ };
22781
23428
  const clock = dependencies.activationClock ?? (() => (/* @__PURE__ */ new Date()).toISOString());
22782
23429
  const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
22783
23430
  const observationFace = createToolExecutionObservationFace({
@@ -25050,7 +25697,6 @@ ${priorNativePaths.join("\n")}` : prepared.prompt;
25050
25697
  }
25051
25698
 
25052
25699
  // src/acp-host/role-envelope.ts
25053
- init_submission_errors();
25054
25700
  init_submission_correctable_error();
25055
25701
  init_navigator_invocation_identity();
25056
25702
  function parseCanonicalSkillInvocation(prompt) {
@@ -25096,7 +25742,7 @@ function projectAcpActivationFlags(request) {
25096
25742
  }
25097
25743
  if (activation.role === "collector") {
25098
25744
  flags.set("ak-collector-repo", activation.repo);
25099
- flags.set("ak-collector-pr", activation.pr);
25745
+ if (activation.pr !== void 0) flags.set("ak-collector-pr", activation.pr);
25100
25746
  if (activation.requestManifestPath !== void 0) flags.set("ak-collector-request-manifest", activation.requestManifestPath);
25101
25747
  }
25102
25748
  return flags;
@@ -25438,17 +26084,9 @@ async function prepareAcpRoleEnvelope(options) {
25438
26084
  let content;
25439
26085
  let details;
25440
26086
  if (isCorrectableExecuteError(error)) {
25441
- const diagnostic = error instanceof Error ? error.message : String(error);
25442
- content = [{ type: "text", text: diagnostic }];
25443
- if (error instanceof GatekeeperDecisionError) {
25444
- details = { ...error.result };
25445
- } else if (error instanceof WorkerCommitReminderError || error instanceof WorkerPrefixReminderError || error instanceof WorkerUnfinishedReasonReminderError) {
25446
- details = { code: error.code };
25447
- } else if (typeof error.code === "string") {
25448
- details = { code: error.code };
25449
- } else {
25450
- details = { code: error instanceof Error && error.name ? error.name : "correctable-submission-error" };
25451
- }
26087
+ const projected2 = projectCorrectableExecuteRejection(error);
26088
+ content = [{ type: "text", text: projected2.diagnostic }];
26089
+ details = projected2.details;
25452
26090
  } else {
25453
26091
  ({ content, details } = declareRoundInfrastructureFailure(error));
25454
26092
  }