@akagilnc/pi-workflow-roles 0.1.3758 → 0.1.3783

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 (63) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +3 -2
  3. package/dist/acp-host/production-host.js +1735 -948
  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/headless-host/description.js +77 -0
  13. package/dist/headless-host/mcp-relay.mjs +119 -0
  14. package/dist/headless-host/production-host.js +26590 -0
  15. package/dist/host-descriptions.js +44 -3
  16. package/dist/package-contracts/collector-output.js +32 -0
  17. package/dist/package-contracts/terminating-infrastructure.js +13 -12
  18. package/dist/pi/role-turn-host.js +1 -2
  19. package/dist/public-cli/github-remote.js +45 -0
  20. package/dist/public-cli/invocation.js +53 -54
  21. package/dist/public-cli/load-production-external-host.js +19 -0
  22. package/dist/public-cli/load-production-headless-host.js +36 -0
  23. package/dist/public-cli/main.js +857 -310
  24. package/dist/public-cli/option-definitions.js +6 -4
  25. package/dist/public-cli/run-lifecycle.js +3 -3
  26. package/dist/public-cli/settlement.js +83 -6
  27. package/dist/public-role-summons.js +40 -5
  28. package/dist/role-runtime.js +137 -7
  29. package/dist/submission-correctable-error.js +24 -0
  30. package/extensions/role-runtime.ts +0 -1
  31. package/package.json +1 -1
  32. package/scripts/build-package.mjs +28 -0
  33. package/src/acp-host/role-envelope.ts +146 -103
  34. package/src/acp-host/role-turn-host.ts +12 -0
  35. package/src/collector-config.ts +0 -1
  36. package/src/collector-github.ts +236 -2
  37. package/src/collector-identity.ts +148 -40
  38. package/src/collector-ledger.ts +48 -10
  39. package/src/collector-receipt.ts +33 -14
  40. package/src/collector-role.ts +376 -450
  41. package/src/collector-target.ts +207 -0
  42. package/src/collector-tool-schemas.ts +62 -15
  43. package/src/headless-host/description.ts +123 -0
  44. package/src/headless-host/production-host.ts +75 -0
  45. package/src/headless-host/role-turn-host.ts +424 -0
  46. package/src/host-contracts.ts +2 -1
  47. package/src/host-descriptions.ts +53 -6
  48. package/src/package-contracts/collector-output.ts +72 -0
  49. package/src/package-contracts/terminating-infrastructure.ts +24 -13
  50. package/src/pi/role-turn-host.ts +1 -2
  51. package/src/public-cli/cli.ts +12 -3
  52. package/src/public-cli/collector-run.ts +3 -2
  53. package/src/public-cli/github-remote.ts +45 -0
  54. package/src/public-cli/invocation.ts +60 -59
  55. package/src/public-cli/load-production-external-host.ts +29 -0
  56. package/src/public-cli/load-production-headless-host.ts +48 -0
  57. package/src/public-cli/main.ts +5 -0
  58. package/src/public-cli/option-definitions.ts +6 -4
  59. package/src/public-cli/run-lifecycle.ts +2 -2
  60. package/src/public-cli/settlement.ts +82 -6
  61. package/src/public-role-summons.ts +62 -9
  62. package/src/role-runtime.ts +166 -13
  63. 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,72 +7553,57 @@ 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(
7167
7574
  "collector requires a github.com origin remote or an explicit --repo owner/repo"
7168
- );
7169
- }
7170
- const ownerRepo = ownerRepoFromGitHubRemoteUrl(remoteUrl);
7171
- if (ownerRepo === void 0) {
7172
- throw new CliUsageError(
7173
- `collector origin remote must be a github.com owner/repo URL, got ${remoteUrl}`
7174
- );
7175
- }
7176
- try {
7177
- return parseCollectorRepository(ownerRepo);
7178
- } catch (error) {
7179
- const detail = error instanceof Error ? error.message : String(error);
7180
- throw new CliUsageError(detail, { cause: error });
7181
- }
7182
- }
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])}`;
7575
+ );
7576
+ }
7577
+ const ownerRepo = ownerRepoFromGitHubRemoteUrl(remoteUrl);
7578
+ if (ownerRepo === void 0) {
7579
+ throw new CliUsageError(
7580
+ `collector origin remote must be a github.com owner/repo URL, got ${remoteUrl}`
7581
+ );
7194
7582
  }
7195
- let parsed;
7196
7583
  try {
7197
- parsed = new URL(trimmed);
7198
- } catch {
7199
- return void 0;
7584
+ return parseCollectorRepository(ownerRepo);
7585
+ } catch (error) {
7586
+ const detail = error instanceof Error ? error.message : String(error);
7587
+ throw new CliUsageError(detail, { cause: error });
7200
7588
  }
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
7589
  }
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();
@@ -8361,24 +8756,35 @@ var init_invocation = __esm({
8361
8756
  var host_descriptions_exports = {};
8362
8757
  __export(host_descriptions_exports, {
8363
8758
  DEFAULT_ROLE_TURN_HOST: () => DEFAULT_ROLE_TURN_HOST,
8759
+ HEADLESS_HOST_DESCRIPTIONS: () => HEADLESS_HOST_DESCRIPTIONS,
8364
8760
  HOST_DESCRIPTIONS: () => HOST_DESCRIPTIONS,
8365
8761
  assertRegisteredHostName: () => assertRegisteredHostName,
8762
+ lookupHeadlessHostDescription: () => lookupHeadlessHostDescription,
8366
8763
  lookupHostDescription: () => lookupHostDescription,
8764
+ lookupHostFamily: () => lookupHostFamily,
8367
8765
  packagedExternalHostNames: () => packagedExternalHostNames
8368
8766
  });
8369
8767
  function lookupHostDescription(host) {
8370
8768
  return Object.hasOwn(HOST_DESCRIPTIONS, host) ? HOST_DESCRIPTIONS[host] : void 0;
8371
8769
  }
8770
+ function lookupHeadlessHostDescription(host) {
8771
+ return Object.hasOwn(HEADLESS_HOST_DESCRIPTIONS, host) ? HEADLESS_HOST_DESCRIPTIONS[host] : void 0;
8772
+ }
8773
+ function lookupHostFamily(host) {
8774
+ if (lookupHostDescription(host) !== void 0) return "acp";
8775
+ if (lookupHeadlessHostDescription(host) !== void 0) return "headless";
8776
+ return void 0;
8777
+ }
8372
8778
  function packagedExternalHostNames() {
8373
- return Object.keys(HOST_DESCRIPTIONS);
8779
+ return [...Object.keys(HOST_DESCRIPTIONS), ...Object.keys(HEADLESS_HOST_DESCRIPTIONS)];
8374
8780
  }
8375
8781
  function assertRegisteredHostName(host) {
8376
- if (host === DEFAULT_ROLE_TURN_HOST || lookupHostDescription(host) !== void 0) {
8782
+ if (host === DEFAULT_ROLE_TURN_HOST || lookupHostFamily(host) !== void 0) {
8377
8783
  return host;
8378
8784
  }
8379
8785
  throw new Error(`unregistered host: ${host}`);
8380
8786
  }
8381
- var PRIVATE_COMPAT_ENV, DEFAULT_ROLE_TURN_HOST, HOST_DESCRIPTIONS;
8787
+ var PRIVATE_COMPAT_ENV, DEFAULT_ROLE_TURN_HOST, HOST_DESCRIPTIONS, HEADLESS_HOST_DESCRIPTIONS;
8382
8788
  var init_host_descriptions = __esm({
8383
8789
  "src/host-descriptions.ts"() {
8384
8790
  "use strict";
@@ -8430,14 +8836,37 @@ var init_host_descriptions = __esm({
8430
8836
  })
8431
8837
  })
8432
8838
  });
8839
+ HEADLESS_HOST_DESCRIPTIONS = Object.freeze({
8840
+ "claude": Object.freeze({
8841
+ binaryFromHome: Object.freeze([".local", "bin", "claude"]),
8842
+ sessionBindingFile: "claude-headless-session.json",
8843
+ fixedArgs: Object.freeze([
8844
+ // One result envelope (not stream-json): typed receipt only; no event-stream copy.
8845
+ "--output-format",
8846
+ "json",
8847
+ "--permission-mode",
8848
+ "bypassPermissions",
8849
+ // Empty sources: no user/project/local operator surface (envelope owns materials).
8850
+ "--setting-sources",
8851
+ "",
8852
+ // With adapter-supplied --mcp-config only (AK relay); drops operator + claude.ai MCP.
8853
+ "--strict-mcp-config"
8854
+ ]),
8855
+ promptFlag: "-p",
8856
+ modelFlag: "--model",
8857
+ effortFlag: "--effort",
8858
+ // File path keeps large role envelopes off ARG_MAX.
8859
+ systemPromptFlag: "--system-prompt-file",
8860
+ jsonSchemaFlag: "--json-schema",
8861
+ mcpConfigFlag: "--mcp-config",
8862
+ sessionIdFlag: "--session-id",
8863
+ resumeFlag: "--resume"
8864
+ })
8865
+ });
8433
8866
  }
8434
8867
  });
8435
8868
 
8436
8869
  // src/public-cli/load-production-acp-host.ts
8437
- var load_production_acp_host_exports = {};
8438
- __export(load_production_acp_host_exports, {
8439
- loadProductionAcpHostFactory: () => loadProductionAcpHostFactory
8440
- });
8441
8870
  import { existsSync as existsSync5 } from "node:fs";
8442
8871
  import { join as join14 } from "node:path";
8443
8872
  import { pathToFileURL } from "node:url";
@@ -8461,6 +8890,54 @@ var init_load_production_acp_host = __esm({
8461
8890
  }
8462
8891
  });
8463
8892
 
8893
+ // src/public-cli/load-production-headless-host.ts
8894
+ import { existsSync as existsSync6 } from "node:fs";
8895
+ import { join as join15 } from "node:path";
8896
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
8897
+ async function loadProductionHeadlessHostFactory(packageRoot, host) {
8898
+ const description = lookupHeadlessHostDescription(host);
8899
+ if (description === void 0) {
8900
+ throw new Error(`unregistered headless host: ${host}`);
8901
+ }
8902
+ const built = join15(packageRoot, "dist/headless-host/production-host.js");
8903
+ const source = join15(packageRoot, "src/headless-host/production-host.ts");
8904
+ const target = existsSync6(built) ? built : source;
8905
+ const href = pathToFileURL2(target).href;
8906
+ const mod = await import(href);
8907
+ const create = mod.createProductionHeadlessRoleTurnHost;
8908
+ return (options) => create({ ...options, description });
8909
+ }
8910
+ var init_load_production_headless_host = __esm({
8911
+ "src/public-cli/load-production-headless-host.ts"() {
8912
+ "use strict";
8913
+ init_host_descriptions();
8914
+ }
8915
+ });
8916
+
8917
+ // src/public-cli/load-production-external-host.ts
8918
+ var load_production_external_host_exports = {};
8919
+ __export(load_production_external_host_exports, {
8920
+ loadProductionExternalHostFactory: () => loadProductionExternalHostFactory
8921
+ });
8922
+ async function loadProductionExternalHostFactory(packageRoot, host) {
8923
+ const family = lookupHostFamily(host);
8924
+ if (family === "acp") {
8925
+ return loadProductionAcpHostFactory(packageRoot, host);
8926
+ }
8927
+ if (family === "headless") {
8928
+ return loadProductionHeadlessHostFactory(packageRoot, host);
8929
+ }
8930
+ throw new Error(`unregistered host: ${host}`);
8931
+ }
8932
+ var init_load_production_external_host = __esm({
8933
+ "src/public-cli/load-production-external-host.ts"() {
8934
+ "use strict";
8935
+ init_host_descriptions();
8936
+ init_load_production_acp_host();
8937
+ init_load_production_headless_host();
8938
+ }
8939
+ });
8940
+
8464
8941
  // src/public-cli/host-providers.ts
8465
8942
  var host_providers_exports = {};
8466
8943
  __export(host_providers_exports, {
@@ -8475,15 +8952,15 @@ __export(host_providers_exports, {
8475
8952
  renderHostProvidersTable: () => renderHostProvidersTable
8476
8953
  });
8477
8954
  import { readFileSync as readFileSync2 } from "node:fs";
8478
- import { join as join15 } from "node:path";
8955
+ import { join as join16 } from "node:path";
8479
8956
  function hostProvidersPath(home) {
8480
8957
  if (typeof home !== "string" || home.trim() === "") {
8481
8958
  throw new Error("home must be explicitly provided");
8482
8959
  }
8483
- return join15(home, ".ak-roles", "host-providers.json");
8960
+ return join16(home, ".ak-roles", "host-providers.json");
8484
8961
  }
8485
8962
  function hermesProviderModelsCachePath(home) {
8486
- return join15(home, ".hermes", "provider_models_cache.json");
8963
+ return join16(home, ".hermes", "provider_models_cache.json");
8487
8964
  }
8488
8965
  function parseHostProvidersTable(value) {
8489
8966
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -9050,7 +9527,7 @@ __export(config_exports, {
9050
9527
  validatePublicCliConfigAxes: () => validatePublicCliConfigAxes
9051
9528
  });
9052
9529
  import { mkdir as mkdir2, readFile as readFile10, writeFile as writeFile5 } from "node:fs/promises";
9053
- import { dirname as dirname8, join as join16 } from "node:path";
9530
+ import { dirname as dirname8, join as join17 } from "node:path";
9054
9531
  function isGateOfficerSeat(value) {
9055
9532
  return GATE_OFFICER_SEATS.includes(value);
9056
9533
  }
@@ -9058,7 +9535,7 @@ function publicCliConfigPath(home) {
9058
9535
  if (typeof home !== "string" || home.trim() === "") {
9059
9536
  throw new Error("home must be explicitly provided");
9060
9537
  }
9061
- return join16(home, ".ak-roles", "public-cli.json");
9538
+ return join17(home, ".ak-roles", "public-cli.json");
9062
9539
  }
9063
9540
  async function loadPublicCliConfig(home) {
9064
9541
  const path = publicCliConfigPath(home);
@@ -9467,7 +9944,7 @@ function credentialProvidersFromAuthData(data) {
9467
9944
  }
9468
9945
  async function loadCredentialProviders(agentDir) {
9469
9946
  try {
9470
- const raw = await readFile10(join16(agentDir, "auth.json"), "utf8");
9947
+ const raw = await readFile10(join17(agentDir, "auth.json"), "utf8");
9471
9948
  return credentialProvidersFromAuthData(JSON.parse(raw));
9472
9949
  } catch (error) {
9473
9950
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -9609,7 +10086,7 @@ var init_ticket_provenance_contracts = __esm({
9609
10086
  // src/ticket-provenance.ts
9610
10087
  import { createHash as createHash5 } from "node:crypto";
9611
10088
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
9612
- import { join as join17 } from "node:path";
10089
+ import { join as join18 } from "node:path";
9613
10090
  function ticketProvenanceSubject(ticketNumber) {
9614
10091
  if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) {
9615
10092
  throw new Error(`ticket-provenance subject requires a positive ticket number, got ${String(ticketNumber)}`);
@@ -9667,7 +10144,7 @@ function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
9667
10144
  return {
9668
10145
  recordFile: path.recordFile,
9669
10146
  volumeDir: path.sessionDir,
9670
- humanViewFile: join17(path.sessionDir, TICKET_PROVENANCE_HUMAN_VIEW)
10147
+ humanViewFile: join18(path.sessionDir, TICKET_PROVENANCE_HUMAN_VIEW)
9671
10148
  };
9672
10149
  }
9673
10150
  async function readTicketProvenance(ticketNumber, cwd, home) {
@@ -9811,7 +10288,7 @@ var init_case_dossier_delivery = __esm({
9811
10288
 
9812
10289
  // src/host-transition-prior-native.ts
9813
10290
  import { access as access2, readdir as readdir3 } from "node:fs/promises";
9814
- import { dirname as dirname9, join as join18 } from "node:path";
10291
+ import { dirname as dirname9, join as join19 } from "node:path";
9815
10292
  function isEnoent2(error) {
9816
10293
  return typeof error === "object" && error !== null && error.code === "ENOENT";
9817
10294
  }
@@ -9836,7 +10313,7 @@ async function listSitianRecordPaths(sessionParent) {
9836
10313
  const recordPaths = [];
9837
10314
  for (const entry of entries) {
9838
10315
  if (!entry.isDirectory()) continue;
9839
- const recordFile = join18(sessionRoot, entry.name, "records.jsonl");
10316
+ const recordFile = join19(sessionRoot, entry.name, "records.jsonl");
9840
10317
  try {
9841
10318
  await access2(recordFile);
9842
10319
  recordPaths.push(recordFile);
@@ -9903,7 +10380,7 @@ var init_public_run_credentials = __esm({
9903
10380
  });
9904
10381
 
9905
10382
  // src/run-terminal-artifacts.ts
9906
- import { basename as basename6, dirname as dirname10, join as join19 } from "node:path";
10383
+ import { basename as basename6, dirname as dirname10, join as join20 } from "node:path";
9907
10384
  function runIdFromRunDirectory(runDirectory) {
9908
10385
  const name = basename6(runDirectory);
9909
10386
  const at = name.lastIndexOf("@");
@@ -10307,7 +10784,7 @@ var init_ledger_session_read = __esm({
10307
10784
 
10308
10785
  // src/analyst-gate-cycles-read.ts
10309
10786
  import { readdir as readdir4 } from "node:fs/promises";
10310
- import { join as join20 } from "node:path";
10787
+ import { join as join21 } from "node:path";
10311
10788
  function isRecord13(value) {
10312
10789
  return typeof value === "object" && value !== null && !Array.isArray(value);
10313
10790
  }
@@ -10569,7 +11046,7 @@ async function readAnalystGateCyclesFromAuditorRoles(auditorRolesDirectory, opti
10569
11046
  throw error;
10570
11047
  }
10571
11048
  for (const name of names) {
10572
- const path = join20(directory, name);
11049
+ const path = join21(directory, name);
10573
11050
  const fromPointer = name.endsWith(".pointer.json");
10574
11051
  const sessionPath = fromPointer ? await resolveOfficerSessionFromPointerFile(path) : path;
10575
11052
  if (sessionPath === void 0) continue;
@@ -10611,14 +11088,14 @@ var init_analyst_gate_cycles_read = __esm({
10611
11088
  });
10612
11089
 
10613
11090
  // src/session-opening-materials.ts
10614
- import { existsSync as existsSync6 } from "node:fs";
11091
+ import { existsSync as existsSync7 } from "node:fs";
10615
11092
  import { readFile as readFile12 } from "node:fs/promises";
10616
- import { dirname as dirname11, join as join21 } from "node:path";
10617
- import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
11093
+ import { dirname as dirname11, join as join22 } from "node:path";
11094
+ import { fileURLToPath, pathToFileURL as pathToFileURL3 } from "node:url";
10618
11095
  function resolvePackageRootDir(moduleUrl = import.meta.url) {
10619
11096
  let dir = dirname11(fileURLToPath(moduleUrl));
10620
11097
  for (let i = 0; i < 8; i += 1) {
10621
- if (existsSync6(join21(dir, "package.json")) && existsSync6(join21(dir, "souls"))) {
11098
+ if (existsSync7(join22(dir, "package.json")) && existsSync7(join22(dir, "souls"))) {
10622
11099
  return dir;
10623
11100
  }
10624
11101
  const parent = dirname11(dir);
@@ -10648,7 +11125,7 @@ var init_session_opening_materials = __esm({
10648
11125
  "src/session-opening-materials.ts"() {
10649
11126
  "use strict";
10650
11127
  init_packaged_role_registry();
10651
- packageRootUrl = pathToFileURL2(resolvePackageRootDir() + "/").href;
11128
+ packageRootUrl = pathToFileURL3(resolvePackageRootDir() + "/").href;
10652
11129
  MAIN_ROLE_SESSION_MATERIALS = {
10653
11130
  ...Object.fromEntries(
10654
11131
  PUBLIC_ROLE_RECORDS.map((record4) => [record4.role, record4.sessionMaterials])
@@ -10924,19 +11401,19 @@ function branchNamesAtPinnedHead(pin) {
10924
11401
  }
10925
11402
  return Object.freeze([...names]);
10926
11403
  }
10927
- async function gitText(root, args) {
11404
+ async function gitText2(root, args) {
10928
11405
  const { stdout } = await execGit(["-C", root, ...args], { encoding: "utf8" });
10929
11406
  return stdout.trim();
10930
11407
  }
10931
11408
  async function createReviewerPinnedGitReader(root = process.cwd()) {
10932
- const discoveredRoot = await gitText(root, ["rev-parse", "--show-toplevel"]);
11409
+ const discoveredRoot = await gitText2(root, ["rev-parse", "--show-toplevel"]);
10933
11410
  const repositoryRoot = await realpath6(discoveredRoot);
10934
- const objectFormat = await gitText(repositoryRoot, ["rev-parse", "--show-object-format"]);
11411
+ const objectFormat = await gitText2(repositoryRoot, ["rev-parse", "--show-object-format"]);
10935
11412
  if (objectFormat !== "sha1" && objectFormat !== "sha256") throw new Error("Unsupported Git object format");
10936
11413
  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()));
11414
+ const targetHead = await gitText2(repositoryRoot, ["rev-parse", "HEAD^{commit}"]);
11415
+ const reachableCommitIds = Object.freeze((await gitText2(repositoryRoot, ["rev-list", targetHead])).split("\n").filter(Boolean));
11416
+ const refs = parseReviewerRefSnapshot(await gitText2(repositoryRoot, reviewerRefSnapshotArgs()));
10940
11417
  const pin = immutableReviewerPin({ repositoryRoot, objectFormat, targetHead, refs });
10941
11418
  const invalid = (code, diagnostic, cause) => {
10942
11419
  throw new ReviewerCorrectablePreflightError(code, diagnostic, cause === void 0 ? void 0 : { cause });
@@ -10955,9 +11432,9 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10955
11432
  return Object.freeze({
10956
11433
  pin,
10957
11434
  async snapshot() {
10958
- const liveObjectFormat = await gitText(repositoryRoot, ["rev-parse", "--show-object-format"]);
11435
+ const liveObjectFormat = await gitText2(repositoryRoot, ["rev-parse", "--show-object-format"]);
10959
11436
  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())) });
11437
+ return immutableReviewerPin({ repositoryRoot, objectFormat: liveObjectFormat, targetHead: await gitText2(repositoryRoot, ["rev-parse", "HEAD^{commit}"]), refs: parseReviewerRefSnapshot(await gitText2(repositoryRoot, reviewerRefSnapshotArgs())) });
10961
11438
  },
10962
11439
  async resolve(base) {
10963
11440
  if (!/^[A-Za-z0-9._/~^+-]+$/.test(base) || base.startsWith("-") || base.includes("..") || base.includes("@{")) {
@@ -10967,7 +11444,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10967
11444
  const headExpression = /^HEAD((?:~[0-9]+|\^[0-9]+)*)$/.exec(base);
10968
11445
  if (headExpression) {
10969
11446
  try {
10970
- commit = await gitText(repositoryRoot, ["rev-parse", "--verify", `${targetHead}${headExpression[1]}^{commit}`]);
11447
+ commit = await gitText2(repositoryRoot, ["rev-parse", "--verify", `${targetHead}${headExpression[1]}^{commit}`]);
10971
11448
  } catch (error) {
10972
11449
  if (exitCode(error) === 128) {
10973
11450
  const repository = await repositoryIsAvailable(repositoryRoot);
@@ -10983,7 +11460,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10983
11460
  } else commit = symbolic(base);
10984
11461
  if (commit === void 0) invalid("base-invalid", "base revision must name an existing pinned ref or reachable commit");
10985
11462
  try {
10986
- commit = await gitText(repositoryRoot, ["rev-parse", "--verify", `${commit}^{commit}`]);
11463
+ commit = await gitText2(repositoryRoot, ["rev-parse", "--verify", `${commit}^{commit}`]);
10987
11464
  } catch (error) {
10988
11465
  if (exitCode(error) === 128) {
10989
11466
  const repository = await repositoryIsAvailable(repositoryRoot);
@@ -10992,7 +11469,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
10992
11469
  throw error;
10993
11470
  }
10994
11471
  try {
10995
- await gitText(repositoryRoot, ["merge-base", "--is-ancestor", commit, targetHead]);
11472
+ await gitText2(repositoryRoot, ["merge-base", "--is-ancestor", commit, targetHead]);
10996
11473
  } catch (error) {
10997
11474
  if (exitCode(error) === 1) invalid("base-invalid", "base revision must be an ancestor of the pinned target", error);
10998
11475
  throw error;
@@ -11002,7 +11479,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11002
11479
  async range(base) {
11003
11480
  let mergeBase;
11004
11481
  try {
11005
- mergeBase = await gitText(repositoryRoot, ["merge-base", base, targetHead]);
11482
+ mergeBase = await gitText2(repositoryRoot, ["merge-base", base, targetHead]);
11006
11483
  } catch (error) {
11007
11484
  if (exitCode(error) === 1) {
11008
11485
  invalid("range-invalid", "review range requires a common ancestor for base and pinned target", error);
@@ -11013,7 +11490,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11013
11490
  const diffCommand = `git diff ${mergeBase}...${targetHead}`;
11014
11491
  const [{ stdout: diff }, commitsText] = await Promise.all([
11015
11492
  execGit(["-C", repositoryRoot, "diff", `${mergeBase}...${targetHead}`], { encoding: "buffer", maxBuffer: 64 * 1024 * 1024 }),
11016
- gitText(repositoryRoot, ["rev-list", "--reverse", `${mergeBase}..${targetHead}`])
11493
+ gitText2(repositoryRoot, ["rev-list", "--reverse", `${mergeBase}..${targetHead}`])
11017
11494
  ]);
11018
11495
  if (diff.length === 0) invalid("range-invalid", "review range must contain a non-empty diff between base and pinned target");
11019
11496
  return Object.freeze({ base: mergeBase, target: targetHead, diffCommand, diffSha256: sha256Hex(Uint8Array.from(diff)), commits: Object.freeze(commitsText ? commitsText.split("\n") : []) });
@@ -11029,7 +11506,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11029
11506
  },
11030
11507
  async listSpecCandidatePaths() {
11031
11508
  const roots = ["docs", "specs", ".scratch"];
11032
- const text = await gitText(repositoryRoot, [
11509
+ const text = await gitText2(repositoryRoot, [
11033
11510
  "ls-tree",
11034
11511
  "-r",
11035
11512
  "--name-only",
@@ -11042,7 +11519,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11042
11519
  async originRepository() {
11043
11520
  let remoteUrl;
11044
11521
  try {
11045
- remoteUrl = await gitText(repositoryRoot, ["remote", "get-url", "origin"]);
11522
+ remoteUrl = await gitText2(repositoryRoot, ["remote", "get-url", "origin"]);
11046
11523
  } catch (error) {
11047
11524
  if (isConfirmedMissingOriginRemote(error)) return void 0;
11048
11525
  throw error;
@@ -11050,7 +11527,7 @@ async function createReviewerPinnedGitReader(root = process.cwd()) {
11050
11527
  return parseGitHubOriginRemote(remoteUrl);
11051
11528
  },
11052
11529
  async commitMessagesNewestFirst(base) {
11053
- const text = await gitText(repositoryRoot, [
11530
+ const text = await gitText2(repositoryRoot, [
11054
11531
  "log",
11055
11532
  "--format=%s",
11056
11533
  `${base}..${targetHead}`
@@ -11490,12 +11967,12 @@ var init_reviewer_dispatch = __esm({
11490
11967
  // src/public-cli/reviewer-dispatch-rejection.ts
11491
11968
  import { writeFileSync as writeFileSync4 } from "node:fs";
11492
11969
  import { readFile as readFile13, unlink as unlink3 } from "node:fs/promises";
11493
- import { join as join22 } from "node:path";
11970
+ import { join as join23 } from "node:path";
11494
11971
  function isReviewerPreflightViolation(value) {
11495
11972
  return typeof value === "string" && REVIEWER_PREFLIGHT_VIOLATIONS.includes(value);
11496
11973
  }
11497
11974
  function reviewerDispatchRejectionPath(runDirectory) {
11498
- return join22(runDirectory, REVIEWER_DISPATCH_REJECTION_FILE);
11975
+ return join23(runDirectory, REVIEWER_DISPATCH_REJECTION_FILE);
11499
11976
  }
11500
11977
  function recordReviewerDispatchRejectionSync(runDirectory, rejection) {
11501
11978
  writeFileSync4(
@@ -11835,13 +12312,206 @@ var init_collector_evidence = __esm({
11835
12312
  }
11836
12313
  });
11837
12314
 
12315
+ // src/collector-identity.ts
12316
+ function identityKey(identity) {
12317
+ if (identity === null) return "unassigned";
12318
+ return String(identity.userId);
12319
+ }
12320
+ function mergeMachineIdentity(current, observed) {
12321
+ if (current === null) return observed;
12322
+ if (observed === null) return current;
12323
+ if (current.appId === void 0 && observed.appId !== void 0) return observed;
12324
+ if (current.appId !== void 0 && observed.appId === void 0) return current;
12325
+ return observed.userType < current.userType ? observed : current;
12326
+ }
12327
+ function headRelationFor(record4, targetHead) {
12328
+ return record4.commitOid === void 0 || record4.commitOid === null ? "unbound" : record4.commitOid === targetHead ? "current" : "prior";
12329
+ }
12330
+ function extractCollectorEvidenceIdentityGroups(records2, targetHead) {
12331
+ const groups = /* @__PURE__ */ new Map();
12332
+ for (const record4 of records2) {
12333
+ if (record4.kind !== "review" && record4.kind !== "issue_comment" && record4.kind !== "review_comment" && record4.kind !== "reaction") continue;
12334
+ if (record4.githubId === void 0) continue;
12335
+ const identity = record4.machineIdentity ?? null;
12336
+ const kind = record4.kind;
12337
+ const source = {
12338
+ kind,
12339
+ id: record4.githubId,
12340
+ evidenceId: record4.evidenceId,
12341
+ headRelation: headRelationFor(record4, targetHead)
12342
+ };
12343
+ const key = identityKey(identity);
12344
+ let group = groups.get(key);
12345
+ if (group === void 0) {
12346
+ group = {
12347
+ identity,
12348
+ ...record4.authorLogin === void 0 ? {} : { displayLogin: record4.authorLogin },
12349
+ attendance: true,
12350
+ findings: [],
12351
+ materials: []
12352
+ };
12353
+ groups.set(key, group);
12354
+ } else {
12355
+ group.identity = mergeMachineIdentity(group.identity, identity);
12356
+ }
12357
+ group.materials.push(source);
12358
+ }
12359
+ return [...groups.values()];
12360
+ }
12361
+ function candidateRecord(candidate) {
12362
+ if (candidate === void 0 || candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
12363
+ return void 0;
12364
+ }
12365
+ return candidate;
12366
+ }
12367
+ function hasNonCanonicalOwnKeys(record4) {
12368
+ for (const key of Object.keys(record4)) {
12369
+ if (!COLLECTOR_OUTPUT_CANONICAL_KEYS.has(key)) return true;
12370
+ }
12371
+ return false;
12372
+ }
12373
+ function enrichCollectorFindings(input) {
12374
+ if (input.candidate !== void 0 && input.candidate !== null && candidateRecord(input.candidate) === void 0) {
12375
+ return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
12376
+ }
12377
+ const record4 = candidateRecord(input.candidate);
12378
+ if (record4 === void 0) {
12379
+ return { findingsSource: "absent", findingsProjectedCount: 0, findingsUnprojected: false };
12380
+ }
12381
+ if (!Object.hasOwn(record4, "findings")) {
12382
+ if (hasNonCanonicalOwnKeys(record4)) {
12383
+ return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
12384
+ }
12385
+ return { findingsSource: "absent", findingsProjectedCount: 0, findingsUnprojected: false };
12386
+ }
12387
+ const rawFindings = record4["findings"];
12388
+ if (!Array.isArray(rawFindings)) {
12389
+ return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
12390
+ }
12391
+ const byEvidenceId = new Map(input.records.map((evidence) => [evidence.evidenceId, evidence]));
12392
+ let projected = 0;
12393
+ let unprojected = false;
12394
+ for (const raw of rawFindings) {
12395
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
12396
+ unprojected = true;
12397
+ continue;
12398
+ }
12399
+ const item = raw;
12400
+ const evidenceId = item.evidenceId;
12401
+ if (typeof evidenceId !== "string" || evidenceId.length === 0) {
12402
+ unprojected = true;
12403
+ continue;
12404
+ }
12405
+ const evidence = byEvidenceId.get(evidenceId);
12406
+ if (evidence === void 0) {
12407
+ throw new CollectorUnknownEvidenceError(evidenceId);
12408
+ }
12409
+ if (evidence.kind !== "review" && evidence.kind !== "issue_comment" && evidence.kind !== "review_comment") {
12410
+ throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u6307\u5411\u4E0D\u53EF\u627F finding \u7684\u8BC1\u636E\u79CD\u7C7B ${evidence.kind}`);
12411
+ }
12412
+ if (evidence.githubId === void 0) {
12413
+ throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u8BC1\u636E ${evidenceId} \u7F3A\u5C11 GitHub id`);
12414
+ }
12415
+ const identity = evidence.machineIdentity ?? null;
12416
+ const group = input.groups.find((candidateGroup) => identityKey(candidateGroup.identity) === identityKey(identity));
12417
+ if (group === void 0) {
12418
+ throw new CollectorFindingsValidationError(`\u901A\u8FDB\u53F8 finding \u6307\u9488\u8BC1\u636E ${evidenceId} \u65E0\u5F52\u5C5E\u8EAB\u4EFD\u7EC4`);
12419
+ }
12420
+ const category = item.category;
12421
+ const summary = item.summary;
12422
+ if (Object.hasOwn(item, "category") && typeof category !== "string") unprojected = true;
12423
+ if (Object.hasOwn(item, "summary") && typeof summary !== "string") unprojected = true;
12424
+ group.findings.push({
12425
+ identity,
12426
+ source: {
12427
+ kind: evidence.kind,
12428
+ id: evidence.githubId,
12429
+ evidenceId: evidence.evidenceId,
12430
+ headRelation: headRelationFor(evidence, input.targetHead)
12431
+ },
12432
+ ...typeof category === "string" ? { category } : {},
12433
+ ...typeof summary === "string" ? { summary } : {},
12434
+ pointer: {
12435
+ repository: input.repository,
12436
+ prNumber: input.prNumber,
12437
+ commentId: evidence.githubId,
12438
+ ...evidence.htmlUrl === void 0 ? {} : { htmlUrl: evidence.htmlUrl },
12439
+ ...evidence.authorLogin === void 0 ? {} : { authorLogin: evidence.authorLogin },
12440
+ kind: evidence.kind,
12441
+ authoritativeTime: evidence.authoritativeTime ?? null,
12442
+ ...evidence.commitOid === void 0 ? {} : { commitOid: evidence.commitOid }
12443
+ }
12444
+ });
12445
+ projected += 1;
12446
+ }
12447
+ return {
12448
+ findingsSource: "array",
12449
+ findingsProjectedCount: projected,
12450
+ findingsUnprojected: unprojected
12451
+ };
12452
+ }
12453
+ function extractCollectorUnfinishedReasons(candidate) {
12454
+ if (candidate !== void 0 && candidate !== null && candidateRecord(candidate) === void 0) {
12455
+ return { reasons: void 0, source: "unreadable", unprojected: true };
12456
+ }
12457
+ const record4 = candidateRecord(candidate);
12458
+ if (record4 === void 0) {
12459
+ return { reasons: void 0, source: "absent", unprojected: false };
12460
+ }
12461
+ if (!Object.hasOwn(record4, "unfinishedReasons")) {
12462
+ return { reasons: void 0, source: "absent", unprojected: false };
12463
+ }
12464
+ const raw = record4["unfinishedReasons"];
12465
+ if (!Array.isArray(raw)) {
12466
+ return { reasons: void 0, source: "unreadable", unprojected: true };
12467
+ }
12468
+ const reasons = raw.filter((item) => typeof item === "string");
12469
+ const unprojected = reasons.length !== raw.length;
12470
+ return {
12471
+ reasons: reasons.length > 0 ? reasons : void 0,
12472
+ source: "array",
12473
+ unprojected
12474
+ };
12475
+ }
12476
+ var CollectorUnknownEvidenceError, CollectorFindingsValidationError, CollectorNonOpenRequestError, COLLECTOR_OUTPUT_CANONICAL_KEYS;
12477
+ var init_collector_identity = __esm({
12478
+ "src/collector-identity.ts"() {
12479
+ "use strict";
12480
+ init_submission_correctable_error();
12481
+ CollectorUnknownEvidenceError = class extends CorrectableSubmissionError {
12482
+ constructor(evidenceId) {
12483
+ 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`);
12484
+ this.name = "CollectorUnknownEvidenceError";
12485
+ }
12486
+ };
12487
+ CollectorFindingsValidationError = class extends CorrectableSubmissionError {
12488
+ constructor(message) {
12489
+ super(message);
12490
+ this.name = "CollectorFindingsValidationError";
12491
+ }
12492
+ };
12493
+ CollectorNonOpenRequestError = class extends CorrectableSubmissionError {
12494
+ constructor(prState) {
12495
+ 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`);
12496
+ this.name = "CollectorNonOpenRequestError";
12497
+ }
12498
+ };
12499
+ COLLECTOR_OUTPUT_CANONICAL_KEYS = /* @__PURE__ */ new Set([
12500
+ "findings",
12501
+ "unfinishedReasons",
12502
+ "infrastructureFailure"
12503
+ ]);
12504
+ }
12505
+ });
12506
+
11838
12507
  // src/collector-tool-schemas.ts
11839
12508
  import { Type as Type13 } from "typebox";
11840
- var collectorObserveArgsSchema, collectorRequestArgsSchema, collectorReadArgsSchema, collectorWaitArgsSchema, collectorFindingArgsSchema, collectorOutputBaseSchema, collectorOutputArgsSchema;
12509
+ var collectorObserveArgsSchema, collectorRequestArgsSchema, collectorReadArgsSchema, collectorWaitArgsSchema, collectorBindTargetArgsSchema, collectorFindingItemDeclaration, collectorOutputBaseSchema, collectorOutputArgsSchema;
11841
12510
  var init_collector_tool_schemas = __esm({
11842
12511
  "src/collector-tool-schemas.ts"() {
11843
12512
  "use strict";
11844
12513
  init_collector_evidence();
12514
+ init_open_tool_schema();
11845
12515
  init_terminating_infrastructure();
11846
12516
  collectorObserveArgsSchema = Type13.Object({}, { additionalProperties: false });
11847
12517
  collectorRequestArgsSchema = Type13.Object({
@@ -11854,19 +12524,51 @@ var init_collector_tool_schemas = __esm({
11854
12524
  collectorWaitArgsSchema = Type13.Object({
11855
12525
  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
12526
  }, { 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"
12527
+ collectorBindTargetArgsSchema = Type13.Object({
12528
+ prNumber: Type13.Optional(Type13.Unknown({
12529
+ 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"
12530
+ })),
12531
+ issueNumber: Type13.Optional(Type13.Unknown({
12532
+ 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
12533
  }))
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" });
12534
+ }, { additionalProperties: true });
12535
+ collectorFindingItemDeclaration = (() => {
12536
+ const item = Type13.Object(
12537
+ {
12538
+ evidenceId: Type13.Unknown({
12539
+ description: "observe \u8FD4\u56DE\u7684\u6750\u6599\u6307\u9488\uFF08\u5FC5\u586B\u8BED\u4E49\uFF09"
12540
+ }),
12541
+ category: Type13.Unknown({
12542
+ description: "\u7B80\u77ED\u5F52\u7C7B\u6807\u7B7E\uFF0C\u4E0D\u662F\u6458\u8981"
12543
+ }),
12544
+ summary: Type13.Unknown({
12545
+ description: "\u54EA\u4E2A bot\u3001\u4EC0\u4E48\u95EE\u9898\u7684\u6458\u8981\uFF1B\u4E0D\u8A8A\u6284\u6B63\u6587"
12546
+ })
12547
+ },
12548
+ {
12549
+ additionalProperties: true,
12550
+ description: "\u5355\u6761 finding \u6307\u9488\uFF1AevidenceId + \u53EF\u9009 category/summary\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
12551
+ }
12552
+ );
12553
+ item.required = [];
12554
+ return item;
12555
+ })();
12556
+ collectorOutputBaseSchema = openToolObject(
12557
+ Type13.Object({
12558
+ // No root type:array — host must not shape-reject non-array findings (#676 C).
12559
+ // Nested item declarations ride `items` for registration preservation (ADR 0057).
12560
+ findings: Type13.Unsafe({
12561
+ 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",
12562
+ items: collectorFindingItemDeclaration
12563
+ }),
12564
+ unfinishedReasons: Type13.Unknown({
12565
+ 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"
12566
+ })
12567
+ })
12568
+ );
11866
12569
  collectorOutputArgsSchema = withInfrastructureFailureDeclaration(
11867
12570
  collectorOutputBaseSchema
11868
12571
  );
11869
- collectorOutputArgsSchema.required = [];
11870
12572
  }
11871
12573
  });
11872
12574
 
@@ -11942,10 +12644,18 @@ function createCollectorLedger(config, options) {
11942
12644
  return Math.max(0, deadlineMono - monoNowOrThrow(clock2));
11943
12645
  };
11944
12646
  const prIdentity = (pr) => `${pr.state}|${pr.headOid}|${pr.updatedAt ?? ""}`;
12647
+ const requireBoundPr = () => {
12648
+ if (config.prNumber === void 0) {
12649
+ throw new Error(
12650
+ "Collector PR target is unbound; call ak_collector_bind_target with the role-decided issue/PR or pass --pr"
12651
+ );
12652
+ }
12653
+ return config.prNumber;
12654
+ };
11945
12655
  const fetchObserveSurfaces = async (transport, observedAt, signal) => {
11946
12656
  const owner = config.repository.owner;
11947
12657
  const repo = config.repository.repo;
11948
- const prNumber = config.prNumber;
12658
+ const prNumber = requireBoundPr();
11949
12659
  const signalOpt = signal === void 0 ? {} : { signal };
11950
12660
  const user = await transport.getAuthenticatedUser(signalOpt);
11951
12661
  const prInitial = await transport.getPullRequest({
@@ -12214,6 +12924,25 @@ function createCollectorLedger(config, options) {
12214
12924
  assertNotFatal();
12215
12925
  outputCandidate = true;
12216
12926
  },
12927
+ bindTarget(prNumber) {
12928
+ assertNotFatal();
12929
+ if (outputCandidate || pendingOutputCallId !== void 0) {
12930
+ throw new Error("\u901A\u8FDB\u53F8\u5DF2\u4EA7\u51FA\u8F93\u51FA\u5019\u9009\uFF0C\u672C\u5C40\u4E0D\u518D\u53D7\u7406\u76EE\u6807\u7ED1\u5B9A");
12931
+ }
12932
+ if (!Number.isSafeInteger(prNumber) || prNumber < 1) {
12933
+ throw new Error("Collector bind target requires a positive safe-integer PR number");
12934
+ }
12935
+ if (config.prNumber !== void 0 && config.prNumber !== prNumber) {
12936
+ throw new Error(
12937
+ `Collector target already bound to PR ${config.prNumber}; cannot rebind to ${prNumber}`
12938
+ );
12939
+ }
12940
+ config.prNumber = prNumber;
12941
+ appendJournal("ak-collector-target-bound", {
12942
+ prNumber,
12943
+ repository: config.repository.canonical
12944
+ });
12945
+ },
12217
12946
  beginOperational(toolName, toolCallId) {
12218
12947
  assertNotFatal();
12219
12948
  if (toolName !== COLLECTOR_OUTPUT_TOOL && (outputCandidate || pendingOutputCallId !== void 0)) {
@@ -12360,7 +13089,7 @@ function createCollectorLedger(config, options) {
12360
13089
  completedMono,
12361
13090
  host: "github.com",
12362
13091
  repository: config.repository.canonical,
12363
- prNumber: config.prNumber,
13092
+ prNumber: requireBoundPr(),
12364
13093
  prState: pr.state,
12365
13094
  headOid: pr.headOid,
12366
13095
  complete: true,
@@ -12399,10 +13128,6 @@ function createCollectorLedger(config, options) {
12399
13128
  if (activationTime === void 0 || deadlineTime === void 0) {
12400
13129
  throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u9700\u8981\u6FC0\u6D3B");
12401
13130
  }
12402
- if (pastCutoff(clock2)) {
12403
- finalObservationRequired = true;
12404
- throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u4E0D\u5728\u8D44\u683C\u622A\u6B62\u524D");
12405
- }
12406
13131
  if (ledger.unresolvedTransportFailure) {
12407
13132
  throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u65F6\u5B58\u5728\u672A\u6062\u590D\u7684\u4F20\u8F93\u5931\u8D25");
12408
13133
  }
@@ -12418,7 +13143,11 @@ function createCollectorLedger(config, options) {
12418
13143
  throw new Error("\u901A\u8FDB\u53F8\u8BF7\u6C42\u8981\u6C42\u6700\u65B0\u5B8C\u6574\u5FEB\u7167");
12419
13144
  }
12420
13145
  if (snapshot.prState !== "OPEN") {
12421
- throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u8981\u6C42 OPEN \u72B6\u6001\u7684 PR \u5FEB\u7167");
13146
+ throw new CollectorNonOpenRequestError(snapshot.prState);
13147
+ }
13148
+ if (pastCutoff(clock2)) {
13149
+ finalObservationRequired = true;
13150
+ throw latchFatal("\u901A\u8FDB\u53F8\u8BF7\u6C42\u4E0D\u5728\u8D44\u683C\u622A\u6B62\u524D");
12422
13151
  }
12423
13152
  const { body, marker } = buildCollectorRequestBody({
12424
13153
  configuredBody: request.requestBody,
@@ -12435,9 +13164,10 @@ function createCollectorLedger(config, options) {
12435
13164
  `\u901A\u8FDB\u53F8\u5728\u6B64 HEAD \u5DF2\u6709\u540C marker \u7684\u5DF2\u8BA4\u8BC1\u8BF7\u6C42 "${input.requestId}"`
12436
13165
  );
12437
13166
  }
13167
+ const boundPr = requireBoundPr();
12438
13168
  const attemptKey = [
12439
13169
  config.repository.canonical,
12440
- String(config.prNumber),
13170
+ String(boundPr),
12441
13171
  snapshot.headOid,
12442
13172
  request.id
12443
13173
  ].join("|");
@@ -12466,7 +13196,7 @@ function createCollectorLedger(config, options) {
12466
13196
  const result = await transport.createIssueComment({
12467
13197
  owner: config.repository.owner,
12468
13198
  repo: config.repository.repo,
12469
- prNumber: config.prNumber,
13199
+ prNumber: boundPr,
12470
13200
  body,
12471
13201
  ...signal === void 0 ? {} : { signal }
12472
13202
  });
@@ -12632,19 +13362,22 @@ function buildObserveModelView(input) {
12632
13362
  }))
12633
13363
  };
12634
13364
  }
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;
13365
+ 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
13366
  var init_collector_ledger = __esm({
12637
13367
  "src/collector-ledger.ts"() {
12638
13368
  "use strict";
12639
13369
  init_collector_evidence();
12640
13370
  init_collector_github();
13371
+ init_collector_identity();
12641
13372
  init_collector_tool_schemas();
12642
13373
  init_collector_output();
12643
13374
  COLLECTOR_OBSERVE_TOOL = "ak_collector_observe";
12644
13375
  COLLECTOR_READ_TOOL = "ak_collector_read";
12645
13376
  COLLECTOR_REQUEST_TOOL = "ak_collector_request";
12646
13377
  COLLECTOR_WAIT_TOOL = "ak_collector_wait";
13378
+ COLLECTOR_BIND_TARGET_TOOL = "ak_collector_bind_target";
12647
13379
  COLLECTOR_OPERATIONAL_TOOLS = [
13380
+ COLLECTOR_BIND_TARGET_TOOL,
12648
13381
  COLLECTOR_OBSERVE_TOOL,
12649
13382
  COLLECTOR_READ_TOOL,
12650
13383
  COLLECTOR_REQUEST_TOOL,
@@ -12661,7 +13394,7 @@ var init_collector_ledger = __esm({
12661
13394
  // src/package-resources/method-skill.ts
12662
13395
  import { createHash as createHash7 } from "node:crypto";
12663
13396
  import { readFile as readFile14, realpath as realpath7 } from "node:fs/promises";
12664
- import { join as join23 } from "node:path";
13397
+ import { join as join24 } from "node:path";
12665
13398
  function gitBlobOid(bytes) {
12666
13399
  const body = typeof bytes === "string" ? Buffer.from(bytes, "utf8") : Buffer.from(bytes);
12667
13400
  const header = Buffer.from(`blob ${body.byteLength}\0`, "utf8");
@@ -12678,10 +13411,10 @@ function packagedMethodSkillRelativeDirectory(name) {
12678
13411
  return `${METHOD_SKILL_RELATIVE_ROOT}/${name}`;
12679
13412
  }
12680
13413
  function resolvePackagedMethodSkillRoot(packageRoot, name) {
12681
- return join23(packageRoot, packagedMethodSkillRelativeDirectory(name));
13414
+ return join24(packageRoot, packagedMethodSkillRelativeDirectory(name));
12682
13415
  }
12683
13416
  function resolvePackagedMethodSkillPath(packageRoot, name) {
12684
- return join23(resolvePackagedMethodSkillRoot(packageRoot, name), "SKILL.md");
13417
+ return join24(resolvePackagedMethodSkillRoot(packageRoot, name), "SKILL.md");
12685
13418
  }
12686
13419
  function isRecord14(value) {
12687
13420
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -12775,8 +13508,8 @@ function parseProvenance(raw, expectedName) {
12775
13508
  }
12776
13509
  async function loadPackagedMethodSkillMaterial(packageRoot, name) {
12777
13510
  const rootDirectory = resolvePackagedMethodSkillRoot(packageRoot, name);
12778
- const skillPathConfigured = join23(rootDirectory, "SKILL.md");
12779
- const provenancePath = join23(rootDirectory, "provenance.json");
13511
+ const skillPathConfigured = join24(rootDirectory, "SKILL.md");
13512
+ const provenancePath = join24(rootDirectory, "provenance.json");
12780
13513
  let provenanceRaw;
12781
13514
  try {
12782
13515
  provenanceRaw = await readFile14(provenancePath, "utf8");
@@ -12793,7 +13526,7 @@ async function loadPackagedMethodSkillMaterial(packageRoot, name) {
12793
13526
  }
12794
13527
  const provenance = parseProvenance(provenanceJson, name);
12795
13528
  for (const [rel, expected] of Object.entries(provenance.files)) {
12796
- const absolute = join23(rootDirectory, rel);
13529
+ const absolute = join24(rootDirectory, rel);
12797
13530
  let bytes;
12798
13531
  try {
12799
13532
  bytes = await readFile14(absolute);
@@ -13309,7 +14042,7 @@ var init_terminal = __esm({
13309
14042
  // src/public-cli/settlement.ts
13310
14043
  import { randomUUID as randomUUID3 } from "node:crypto";
13311
14044
  import { appendFile as appendFile2, readFile as readFile15, readdir as readdir5, writeFile as writeFile6 } from "node:fs/promises";
13312
- import { dirname as dirname12, join as join24 } from "node:path";
14045
+ import { dirname as dirname12, join as join25 } from "node:path";
13313
14046
  function sealedLedgerHome(admitted) {
13314
14047
  return homeFromRunDirectory(admitted.runDirectory);
13315
14048
  }
@@ -13397,8 +14130,27 @@ function formatFailureStderrDiagnostic(failure2) {
13397
14130
  const oneLine2 = selected.split(/\r?\n/).map((line2) => line2.trim()).find((line2) => line2.length > 0) ?? "failure";
13398
14131
  return formatCliDiagnostic(boundConciseDiagnostic(oneLine2));
13399
14132
  }
14133
+ function formatErrorCauseDetail(cause) {
14134
+ if (cause instanceof Error) return cause.message;
14135
+ if (typeof cause === "object" && cause !== null) {
14136
+ try {
14137
+ return JSON.stringify(cause);
14138
+ } catch {
14139
+ return String(cause);
14140
+ }
14141
+ }
14142
+ return String(cause);
14143
+ }
13400
14144
  function presentStructuralRejection(error, io) {
13401
- io.stderr(formatCliDiagnostic(error.message));
14145
+ let message = error.message;
14146
+ const cause = error.cause;
14147
+ if (cause !== void 0) {
14148
+ const detail = formatErrorCauseDetail(cause);
14149
+ if (detail.trim().length > 0) {
14150
+ message = `${message}; cause: ${detail}`;
14151
+ }
14152
+ }
14153
+ io.stderr(formatCliDiagnostic(message));
13402
14154
  }
13403
14155
  async function inspectJudgeSession(sessionFile) {
13404
14156
  try {
@@ -13705,7 +14457,7 @@ async function readSessionProviderStop(sessionFile) {
13705
14457
  }
13706
14458
  }
13707
14459
  async function readBoundEvidenceChildKnownFailure(sessionFile) {
13708
- const childDirectory = join24(dirname12(sessionFile), "evidence-children");
14460
+ const childDirectory = join25(dirname12(sessionFile), "evidence-children");
13709
14461
  let names;
13710
14462
  try {
13711
14463
  names = await readdir5(childDirectory);
@@ -13716,7 +14468,7 @@ async function readBoundEvidenceChildKnownFailure(sessionFile) {
13716
14468
  for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
13717
14469
  let entries;
13718
14470
  try {
13719
- entries = await readBoundSessionEntries(join24(childDirectory, file));
14471
+ entries = await readBoundSessionEntries(join25(childDirectory, file));
13720
14472
  } catch (error) {
13721
14473
  throw sessionReadFailure(error, "failed to read discovered evidence-child session");
13722
14474
  }
@@ -13770,7 +14522,7 @@ async function loadBoundAuditorVolumes(sessionFile) {
13770
14522
  latestParentUserIndex = i;
13771
14523
  break;
13772
14524
  }
13773
- const childDirectories = [join24(dirname12(sessionFile), "auditor-roles")];
14525
+ const childDirectories = [join25(dirname12(sessionFile), "auditor-roles")];
13774
14526
  const valid = [];
13775
14527
  let sawAnyDirectory = false;
13776
14528
  for (const childDirectory of childDirectories) {
@@ -13785,7 +14537,7 @@ async function loadBoundAuditorVolumes(sessionFile) {
13785
14537
  for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
13786
14538
  let entries;
13787
14539
  try {
13788
- entries = await readBoundSessionEntries(join24(childDirectory, file));
14540
+ entries = await readBoundSessionEntries(join25(childDirectory, file));
13789
14541
  } catch (error) {
13790
14542
  throw sessionReadFailure(error, "failed to read discovered auditor session");
13791
14543
  }
@@ -14086,6 +14838,22 @@ function toolResultText(message) {
14086
14838
  return "";
14087
14839
  }).join("").trim();
14088
14840
  }
14841
+ function extractCollectorTargetBindRejection(entries) {
14842
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
14843
+ const entry = entries[i];
14844
+ if (entry?.type !== "message") continue;
14845
+ const message = entry.message;
14846
+ if (message?.role !== "toolResult") continue;
14847
+ if (message.toolName !== COLLECTOR_BIND_TARGET_TOOL) continue;
14848
+ if (message.isError !== true) return void 0;
14849
+ const diagnostic = toolResultText(message);
14850
+ if (diagnostic.length === 0) return void 0;
14851
+ const details = message.details;
14852
+ const code = isRecord16(details) && typeof details.code === "string" && details.code.trim() !== "" ? details.code : void 0;
14853
+ return code === void 0 ? { diagnostic } : { diagnostic, code };
14854
+ }
14855
+ return void 0;
14856
+ }
14089
14857
  function boundErroredToolCandidate(entries, resultIndex, message, toolName) {
14090
14858
  if (message.toolName !== toolName || message.isError !== true) return void 0;
14091
14859
  const bound = boundRoleToolCallForResult(entries, resultIndex, message, toolName);
@@ -14285,8 +15053,8 @@ function projectTerminalGateFact(rounds) {
14285
15053
  };
14286
15054
  }
14287
15055
  async function extractGateFactFromSessionDirectory(sessionDirectory, options = {}) {
14288
- const directories = [join24(sessionDirectory, "auditor-roles")];
14289
- const parentSessionFile = options.parentSessionFile ?? join24(sessionDirectory, "session.jsonl");
15056
+ const directories = [join25(sessionDirectory, "auditor-roles")];
15057
+ const parentSessionFile = options.parentSessionFile ?? join25(sessionDirectory, "session.jsonl");
14290
15058
  const rounds = await readAnalystGateCyclesFromAuditorRoles(directories, {
14291
15059
  parentSessionFile
14292
15060
  });
@@ -14384,8 +15152,8 @@ async function extractNavigatorFactFromAdmittedSession(sessionFile) {
14384
15152
  async function publishJudgeArtifacts(admitted, roleOutcome, coordinates) {
14385
15153
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
14386
15154
  const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
14387
- const reportPath = join24(artifactsDir, "report.json");
14388
- const evidencePath = join24(artifactsDir, "evidence.json");
15155
+ const reportPath = join25(artifactsDir, "report.json");
15156
+ const evidencePath = join25(artifactsDir, "evidence.json");
14389
15157
  await writeFile6(
14390
15158
  reportPath,
14391
15159
  `${JSON.stringify(
@@ -14475,8 +15243,8 @@ async function trySettleJudgeTerminalResult(admitted, authority) {
14475
15243
  async function publishDoctorArtifacts(admitted, roleOutcome, coordinates, options = {}) {
14476
15244
  await appendRunAttemptHistory({ role: admitted.role, runId: admitted.runId, sessionFile: coordinates.sessionFile }, roleOutcome);
14477
15245
  const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
14478
- const reportPath = join24(artifactsDir, "report.json");
14479
- const evidencePath = join24(artifactsDir, "evidence.json");
15246
+ const reportPath = join25(artifactsDir, "report.json");
15247
+ const evidencePath = join25(artifactsDir, "evidence.json");
14480
15248
  await writeFile6(
14481
15249
  reportPath,
14482
15250
  `${JSON.stringify(
@@ -14709,7 +15477,7 @@ function uniqueFailureFallbackDirs(runDirectory, baseDir) {
14709
15477
  return dirs;
14710
15478
  }
14711
15479
  async function resolveFailureArtifactsBase(runDirectory) {
14712
- const artifactsDir = join24(runDirectory, "artifacts");
15480
+ const artifactsDir = join25(runDirectory, "artifacts");
14713
15481
  try {
14714
15482
  await ensureRunArtifactsDir(runDirectory);
14715
15483
  return { baseDir: artifactsDir };
@@ -14725,7 +15493,7 @@ async function writeFailureJsonRetainingCause(preferredCandidates, uniqueFallbac
14725
15493
  const candidates = [
14726
15494
  ...preferredCandidates,
14727
15495
  // One unique name per fallback dir — collisions on fixed names cannot exhaust this.
14728
- ...uniqueFallbackDirs.map((dir) => join24(dir, `${stem}.${randomUUID3()}.json`))
15496
+ ...uniqueFallbackDirs.map((dir) => join25(dir, `${stem}.${randomUUID3()}.json`))
14729
15497
  ];
14730
15498
  for (let i = 0; i < candidates.length; i += 1) {
14731
15499
  const path = candidates[i];
@@ -14770,26 +15538,26 @@ async function publishFailureArtifacts(admitted, failure2, authority) {
14770
15538
  } catch (error) {
14771
15539
  priorIssues.push(publicationAttemptFromError(sessionFile, error));
14772
15540
  }
14773
- const underArtifacts = baseDir === join24(admitted.runDirectory, "artifacts");
15541
+ const underArtifacts = baseDir === join25(admitted.runDirectory, "artifacts");
14774
15542
  const uniqueFallbackDirs = uniqueFailureFallbackDirs(
14775
15543
  admitted.runDirectory,
14776
15544
  baseDir
14777
15545
  );
14778
15546
  const errorCandidates = underArtifacts ? [
14779
- join24(baseDir, "error.json"),
14780
- join24(baseDir, "error.settlement.json"),
14781
- join24(admitted.runDirectory, "error.settlement.json")
15547
+ join25(baseDir, "error.json"),
15548
+ join25(baseDir, "error.settlement.json"),
15549
+ join25(admitted.runDirectory, "error.settlement.json")
14782
15550
  ] : [
14783
- join24(baseDir, "error.settlement.json"),
14784
- join24(baseDir, "error.json")
15551
+ join25(baseDir, "error.settlement.json"),
15552
+ join25(baseDir, "error.json")
14785
15553
  ];
14786
15554
  const evidenceCandidates = underArtifacts ? [
14787
- join24(baseDir, "evidence.json"),
14788
- join24(baseDir, "evidence.settlement.json"),
14789
- join24(admitted.runDirectory, "evidence.settlement.json")
15555
+ join25(baseDir, "evidence.json"),
15556
+ join25(baseDir, "evidence.settlement.json"),
15557
+ join25(admitted.runDirectory, "evidence.settlement.json")
14790
15558
  ] : [
14791
- join24(baseDir, "evidence.settlement.json"),
14792
- join24(baseDir, "evidence.json")
15559
+ join25(baseDir, "evidence.settlement.json"),
15560
+ join25(baseDir, "evidence.json")
14793
15561
  ];
14794
15562
  const errorPayloadBase = {
14795
15563
  kind: "error",
@@ -14891,7 +15659,18 @@ async function settleFailureTerminalResult(admitted, failure2, authority, option
14891
15659
  try {
14892
15660
  const facts = parseNoReceiptLifecycleFacts(raw);
14893
15661
  if (facts.runPointer === admitted.runDirectory && facts.attemptPointer === `current:${admitted.runDirectory}`) {
14894
- const decisiveFacts2 = facts;
15662
+ let decisiveFacts2 = facts;
15663
+ if (admitted.role === "collector") {
15664
+ const bindRejection = extractCollectorTargetBindRejection(entries.slice(attemptStart));
15665
+ if (bindRejection !== void 0) {
15666
+ decisiveFacts2 = {
15667
+ ...facts,
15668
+ targetBindRejected: true,
15669
+ targetBindDiagnostic: bindRejection.diagnostic,
15670
+ ...bindRejection.code === void 0 ? {} : { targetBindCode: bindRejection.code }
15671
+ };
15672
+ }
15673
+ }
14895
15674
  return withOptionalGateProjection(
14896
15675
  {
14897
15676
  roleOutcome: { kind: "no_receipt", role: admitted.role, status: "no-accepted-receipt", ...facts, decisiveFacts: decisiveFacts2 },
@@ -14969,8 +15748,16 @@ function presentFailureTerminal(terminal, io) {
14969
15748
  io.stdout(formatTerminalResult(terminal));
14970
15749
  if (terminal.roleOutcome.kind === "failure") {
14971
15750
  io.stderr(formatFailureStderrDiagnostic({
14972
- cause: terminal.roleOutcome.cause,
14973
- diagnostic: terminal.roleOutcome.diagnostic
15751
+ cause: terminal.roleOutcome.cause,
15752
+ diagnostic: terminal.roleOutcome.diagnostic
15753
+ }));
15754
+ return;
15755
+ }
15756
+ const bindDiagnostic = terminal.roleOutcome.decisiveFacts.targetBindDiagnostic;
15757
+ if (typeof bindDiagnostic === "string" && bindDiagnostic.trim() !== "") {
15758
+ io.stderr(formatFailureStderrDiagnostic({
15759
+ cause: "output",
15760
+ diagnostic: bindDiagnostic
14974
15761
  }));
14975
15762
  }
14976
15763
  }
@@ -15060,7 +15847,7 @@ var init_settlement = __esm({
15060
15847
  import { constants as fsConstants } from "node:fs";
15061
15848
  import { randomUUID as randomUUID4 } from "node:crypto";
15062
15849
  import { lstat as lstat5, mkdir as mkdir4, open as open2 } from "node:fs/promises";
15063
- import { join as join25 } from "node:path";
15850
+ import { join as join26 } from "node:path";
15064
15851
  function presentTerminal(terminal, io) {
15065
15852
  if (terminal.roleOutcome.kind === "failure" || terminal.roleOutcome.kind === "no_receipt") {
15066
15853
  presentFailureTerminal(terminal, io);
@@ -15079,7 +15866,7 @@ async function finalizeExceptionRunBestEffort(runDirectory, io) {
15079
15866
  }
15080
15867
  }
15081
15868
  function runArtifactsDirectory(runDirectory) {
15082
- return join25(runDirectory, "artifacts");
15869
+ return join26(runDirectory, "artifacts");
15083
15870
  }
15084
15871
  async function ensureRealArtifactsDirectory(runDirectory) {
15085
15872
  const runStat = await lstat5(runDirectory);
@@ -15162,7 +15949,7 @@ function jsonSafeReplacer() {
15162
15949
  }
15163
15950
  async function retainDispatchError(admitted, principalAuthority, sessionAppender, attempt, error) {
15164
15951
  const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
15165
- const filePath = join25(
15952
+ const filePath = join26(
15166
15953
  artifactsDir,
15167
15954
  `dispatch-error-attempt-${attempt}-${randomUUID4()}.json`
15168
15955
  );
@@ -15411,7 +16198,7 @@ var init_auto_resume = __esm({
15411
16198
  // src/public-cli/post-admission.ts
15412
16199
  import { randomUUID as randomUUID5 } from "node:crypto";
15413
16200
  import { readFile as readFile16, writeFile as writeFile7 } from "node:fs/promises";
15414
- import { isAbsolute as isAbsolute7, join as join26, resolve as resolve11 } from "node:path";
16201
+ import { isAbsolute as isAbsolute7, join as join27, resolve as resolve11 } from "node:path";
15415
16202
  function appendContinuationSection(continuation, section) {
15416
16203
  const prompt = `${continuation.prompt}
15417
16204
 
@@ -15420,7 +16207,7 @@ ${section}`;
15420
16207
  }
15421
16208
  async function readInvocationHost(runDirectory) {
15422
16209
  try {
15423
- const raw = JSON.parse(await readFile16(join26(runDirectory, "invocation.json"), "utf8"));
16210
+ const raw = JSON.parse(await readFile16(join27(runDirectory, "invocation.json"), "utf8"));
15424
16211
  return typeof raw.host === "string" && raw.host.trim() !== "" ? raw.host : void 0;
15425
16212
  } catch (error) {
15426
16213
  if (error.code === "ENOENT") return void 0;
@@ -15574,7 +16361,7 @@ async function dispatchPostAdmissionTurn(input) {
15574
16361
  }
15575
16362
  try {
15576
16363
  await writeFile7(
15577
- join26(admitted.runDirectory, "stderr.log"),
16364
+ join27(admitted.runDirectory, "stderr.log"),
15578
16365
  result.stderr,
15579
16366
  "utf8"
15580
16367
  );
@@ -15683,7 +16470,7 @@ function resumeTurnRequestProjectionOptions(admitted, request, env, summonsPrepa
15683
16470
  }
15684
16471
  function isAlreadyFrozenSummonsAttachment(runDirectory, attachmentPath) {
15685
16472
  const absolute = isAbsolute7(attachmentPath) ? attachmentPath : resolve11(attachmentPath);
15686
- return pathContainedIn(join26(runDirectory, "attachments"), absolute);
16473
+ return pathContainedIn(join27(runDirectory, "attachments"), absolute);
15687
16474
  }
15688
16475
  async function prepareSummonsResumeMaterials(runDirectory, summons) {
15689
16476
  if (summons === void 0) return void 0;
@@ -16868,8 +17655,8 @@ __export(public_role_summons_exports, {
16868
17655
  summonGateOfficer: () => summonGateOfficer,
16869
17656
  summonPublicRole: () => summonPublicRole
16870
17657
  });
16871
- import { existsSync as existsSync7 } from "node:fs";
16872
- import { join as join27 } from "node:path";
17658
+ import { existsSync as existsSync8 } from "node:fs";
17659
+ import { join as join28 } from "node:path";
16873
17660
  function createCapturingIo() {
16874
17661
  const chunks = [];
16875
17662
  return {
@@ -16892,7 +17679,7 @@ function parentDir(path) {
16892
17679
  function walkPackageRoot(start) {
16893
17680
  let dir = start;
16894
17681
  for (let i = 0; i < 12; i += 1) {
16895
- if (existsSync7(join27(dir, "package.json")) && existsSync7(join27(dir, "souls"))) {
17682
+ if (existsSync8(join28(dir, "package.json")) && existsSync8(join28(dir, "souls"))) {
16896
17683
  return dir;
16897
17684
  }
16898
17685
  const parent = parentDir(dir);
@@ -16968,9 +17755,9 @@ async function createSummonEnv(options) {
16968
17755
  const hostName = options.seat.host ?? "pi";
16969
17756
  let roleTurnHost = piHost;
16970
17757
  if (hostName !== "pi") {
16971
- const { lookupHostDescription: lookupHostDescription2 } = await Promise.resolve().then(() => (init_host_descriptions(), host_descriptions_exports));
16972
- const { loadProductionAcpHostFactory: loadProductionAcpHostFactory2 } = await Promise.resolve().then(() => (init_load_production_acp_host(), load_production_acp_host_exports));
16973
- if (lookupHostDescription2(hostName) === void 0) {
17758
+ const { lookupHostFamily: lookupHostFamily2 } = await Promise.resolve().then(() => (init_host_descriptions(), host_descriptions_exports));
17759
+ const { loadProductionExternalHostFactory: loadProductionExternalHostFactory2 } = await Promise.resolve().then(() => (init_load_production_external_host(), load_production_external_host_exports));
17760
+ if (lookupHostFamily2(hostName) === void 0) {
16974
17761
  throw new Error(
16975
17762
  `public role summons host unregistered: host=${hostName} seat=${options.role}`
16976
17763
  );
@@ -16978,7 +17765,7 @@ async function createSummonEnv(options) {
16978
17765
  let hostPromise;
16979
17766
  roleTurnHost = {
16980
17767
  executeTurn: async (request) => {
16981
- hostPromise ??= loadProductionAcpHostFactory2(options.packageRoot, hostName).then(
17768
+ hostPromise ??= loadProductionExternalHostFactory2(options.packageRoot, hostName).then(
16982
17769
  (create) => create({
16983
17770
  packageRoot: options.packageRoot,
16984
17771
  principalAuthority
@@ -17012,7 +17799,7 @@ async function createSummonEnv(options) {
17012
17799
  async function summonPublicRole(options) {
17013
17800
  const packageRoot = resolveSummonsPackageRoot(options.packageRoot);
17014
17801
  const home = await resolveSummonHome(options);
17015
- const agentDir = options.agentDir ?? process.env.PI_CODING_AGENT_DIR ?? join27(home, ".pi", "agent");
17802
+ const agentDir = options.agentDir ?? process.env.PI_CODING_AGENT_DIR ?? join28(home, ".pi", "agent");
17016
17803
  const {
17017
17804
  loadCredentialProviders: loadCredentialProviders2,
17018
17805
  loadPublicCliConfig: loadPublicCliConfig2,
@@ -17020,7 +17807,8 @@ async function summonPublicRole(options) {
17020
17807
  } = await Promise.resolve().then(() => (init_config(), config_exports));
17021
17808
  const credentials = options.credentials ?? await loadCredentialProviders2(agentDir);
17022
17809
  const config = await loadPublicCliConfig2(home);
17023
- const seat = resolveEffectiveSeat2(config, options.role, credentials);
17810
+ const invocation = options.host === void 0 ? void 0 : { host: options.host };
17811
+ const seat = resolveEffectiveSeat2(config, options.role, credentials, invocation);
17024
17812
  const env = {
17025
17813
  ...await createSummonEnv({
17026
17814
  role: options.role,
@@ -17132,6 +17920,38 @@ async function summonPublicRole(options) {
17132
17920
  ...stderr === void 0 || stderr === "" ? {} : { stderr }
17133
17921
  };
17134
17922
  }
17923
+ async function parentInvocationHost(sourceRunDirectory) {
17924
+ const { readFile: readFile21 } = await import("node:fs/promises");
17925
+ const path = join28(sourceRunDirectory, "invocation.json");
17926
+ let text;
17927
+ try {
17928
+ text = await readFile21(path, "utf8");
17929
+ } catch (error) {
17930
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
17931
+ throw new Error(
17932
+ `parent invocation.json required for gate source run at ${path}: ${code ?? (error instanceof Error ? error.message : String(error))}`,
17933
+ { cause: error }
17934
+ );
17935
+ }
17936
+ let raw;
17937
+ try {
17938
+ raw = JSON.parse(text);
17939
+ } catch (error) {
17940
+ throw new Error(
17941
+ `parent invocation.json unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`,
17942
+ { cause: error }
17943
+ );
17944
+ }
17945
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
17946
+ throw new Error(`parent invocation.json has non-object shape at ${path}`);
17947
+ }
17948
+ const host = raw.host;
17949
+ if (host === void 0) return void 0;
17950
+ if (typeof host !== "string" || host.trim() === "") {
17951
+ throw new Error(`parent invocation.json host must be a non-empty string at ${path}`);
17952
+ }
17953
+ return host;
17954
+ }
17135
17955
  async function summonGateOfficer(options) {
17136
17956
  let home = options.home;
17137
17957
  if (home === void 0) {
@@ -17145,6 +17965,7 @@ async function summonGateOfficer(options) {
17145
17965
  submission: options.submission
17146
17966
  });
17147
17967
  }
17968
+ const parentHost = await parentInvocationHost(options.sourceRunDirectory);
17148
17969
  const common = {
17149
17970
  cwd: options.cwd,
17150
17971
  ...home === void 0 ? {} : { home },
@@ -17153,6 +17974,7 @@ async function summonGateOfficer(options) {
17153
17974
  ...options.signal === void 0 ? {} : { signal: options.signal },
17154
17975
  ...options.reask === void 0 ? {} : { reviewReask: options.reask },
17155
17976
  ...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction },
17977
+ ...parentHost === void 0 ? {} : { host: parentHost },
17156
17978
  ...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
17157
17979
  ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
17158
17980
  };
@@ -17318,7 +18140,7 @@ var init_compliance_transport = __esm({
17318
18140
  });
17319
18141
 
17320
18142
  // src/dossier-resolution.ts
17321
- import { existsSync as existsSync8, statSync as statSync2 } from "node:fs";
18143
+ import { existsSync as existsSync9, statSync as statSync2 } from "node:fs";
17322
18144
  import { resolve as resolve12 } from "node:path";
17323
18145
  function resolveAuditDossier(env = process.env) {
17324
18146
  const raw = env[AUDIT_RUN_DIR_ENV];
@@ -17327,7 +18149,7 @@ function resolveAuditDossier(env = process.env) {
17327
18149
  }
17328
18150
  const runDirectory = resolve12(raw);
17329
18151
  try {
17330
- if (!existsSync8(runDirectory) || !statSync2(runDirectory).isDirectory()) {
18152
+ if (!existsSync9(runDirectory) || !statSync2(runDirectory).isDirectory()) {
17331
18153
  return { status: "incomplete", observation: { kind: "missing-dossier" } };
17332
18154
  }
17333
18155
  } catch {
@@ -17577,11 +18399,11 @@ var init_navigator_session_contracts = __esm({
17577
18399
 
17578
18400
  // src/archivist-record-topology.ts
17579
18401
  import { createHash as createHash8 } from "node:crypto";
17580
- import { join as join28 } from "node:path";
18402
+ import { join as join29 } from "node:path";
17581
18403
  function subjectKeyedRecordDirectory(input) {
17582
18404
  const ledgerHome = input.parentSessionFile !== void 0 && input.parentSessionFile.length > 0 ? resolveActivationLedgerHomeForPath(input.parentSessionFile) : resolveActivationLedgerHome(input.home);
17583
18405
  const digest = createHash8("sha256").update(input.subject).digest("hex").slice(0, 32);
17584
- return join28(
18406
+ return join29(
17585
18407
  activationBookDirectory(ledgerHome, resolveBookKeyFromGit(input.cwd)),
17586
18408
  input.kind,
17587
18409
  digest
@@ -17605,11 +18427,11 @@ __export(archivist_record_entry_exports, {
17605
18427
  createRecordSessionOpen: () => createRecordSessionOpen,
17606
18428
  subjectKeyedRecordDirectory: () => subjectKeyedRecordDirectory
17607
18429
  });
17608
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync3, realpathSync as realpathSync2, writeFileSync as writeFileSync5 } from "node:fs";
17609
- import { dirname as dirname13, resolve as resolve13, join as join29 } from "node:path";
18430
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync3, realpathSync as realpathSync2, writeFileSync as writeFileSync5 } from "node:fs";
18431
+ import { dirname as dirname13, resolve as resolve13, join as join30 } from "node:path";
17610
18432
  import { SessionManager } from "@earendil-works/pi-coding-agent";
17611
18433
  function readCurrentSession(sessionDir) {
17612
- const ledger = join29(sessionDir, CURRENT_SESSION_LEDGER);
18434
+ const ledger = join30(sessionDir, CURRENT_SESSION_LEDGER);
17613
18435
  try {
17614
18436
  const value = JSON.parse(readFileSync3(ledger, "utf8"));
17615
18437
  if (typeof value !== "object" || value === null || typeof value.sessionFile !== "string" || value.sessionFile.length === 0) {
@@ -17624,7 +18446,7 @@ function readCurrentSession(sessionDir) {
17624
18446
  }
17625
18447
  }
17626
18448
  function writeCurrentSession(sessionDir, sessionFile) {
17627
- const ledger = join29(sessionDir, CURRENT_SESSION_LEDGER);
18449
+ const ledger = join30(sessionDir, CURRENT_SESSION_LEDGER);
17628
18450
  try {
17629
18451
  writeFileSync5(ledger, `${JSON.stringify({ sessionFile })}
17630
18452
  `, { flag: "wx" });
@@ -17685,10 +18507,10 @@ function createRecordSessionOpen(options) {
17685
18507
  return { session: SessionManager.inMemory(cwd), resumed: false };
17686
18508
  } else {
17687
18509
  const parentResolved = resolve13(parentFile);
17688
- sessionDir = physicallyContainedIn(ledgerHome, parentResolved) ? join29(dirname13(parentResolved), options.kind) : join29(activationBookDirectory(ledgerHome, resolveBookKeyFromGit(cwd)), options.kind);
18510
+ sessionDir = physicallyContainedIn(ledgerHome, parentResolved) ? join30(dirname13(parentResolved), options.kind) : join30(activationBookDirectory(ledgerHome, resolveBookKeyFromGit(cwd)), options.kind);
17689
18511
  parentSession = parentFile;
17690
18512
  }
17691
- const nestAlreadyExists = existsSync9(sessionDir);
18513
+ const nestAlreadyExists = existsSync10(sessionDir);
17692
18514
  ensureRealDirectoryTree(ledgerHome, sessionDir);
17693
18515
  const mayResumeSameNest = options.subject !== void 0 || options.kind === WORKER_SUBMISSION_GATE_KIND;
17694
18516
  if (mayResumeSameNest && nestAlreadyExists) {
@@ -17706,7 +18528,7 @@ function createRecordSessionOpen(options) {
17706
18528
  );
17707
18529
  if (session.isPersisted()) {
17708
18530
  const file = session.getSessionFile();
17709
- if (file !== void 0 && !existsSync9(file)) {
18531
+ if (file !== void 0 && !existsSync10(file)) {
17710
18532
  const header = session.getHeader();
17711
18533
  if (header !== null && header.type === "session") {
17712
18534
  writeFileSync5(file, `${JSON.stringify(header)}
@@ -17724,7 +18546,7 @@ function createRecordSession(options) {
17724
18546
  return createRecordSessionOpen(options).session;
17725
18547
  }
17726
18548
  function bookDirectOfficerRunPointer(options) {
17727
- const nest = join29(dirname13(options.parentSessionFile), "auditor-roles");
18549
+ const nest = join30(dirname13(options.parentSessionFile), "auditor-roles");
17728
18550
  mkdirSync4(nest, { recursive: true });
17729
18551
  const pointer = {
17730
18552
  version: 1,
@@ -17734,7 +18556,7 @@ function bookDirectOfficerRunPointer(options) {
17734
18556
  ...options.runDirectory !== void 0 && options.runDirectory.trim() !== "" ? { runDirectory: options.runDirectory } : {}
17735
18557
  };
17736
18558
  writeFileSync5(
17737
- join29(nest, `${options.officer}.pointer.json`),
18559
+ join30(nest, `${options.officer}.pointer.json`),
17738
18560
  `${JSON.stringify(pointer)}
17739
18561
  `,
17740
18562
  "utf8"
@@ -19432,132 +20254,6 @@ var init_tool_execution_observation = __esm({
19432
20254
  }
19433
20255
  });
19434
20256
 
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
20257
  // src/collector-receipt.ts
19562
20258
  function fail4(message) {
19563
20259
  throw new Error(message);
@@ -19567,13 +20263,15 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19567
20263
  if (ledger.unresolvedTransportFailure) fail4("Collector cannot output while a transport failure is unrecovered");
19568
20264
  if (ledger.latestCompleteSnapshotId === void 0) fail4("Collector output requires a complete final snapshot");
19569
20265
  if (ledger.activationTime === void 0 || ledger.deadlineTime === void 0) fail4("Collector output requires activation timeline");
20266
+ if (ledger.config.prNumber === void 0) {
20267
+ fail4("Collector output requires a bound PR target; call ak_collector_bind_target first or pass --pr");
20268
+ }
19570
20269
  if (clock !== void 0) ledger.assertOutputObservationLaw(clock);
19571
20270
  else if (ledger.observedGeneration !== ledger.mutationGeneration || ledger.finalObservationRequired && !ledger.finalObservationCompleted) {
19572
20271
  fail4("Collector output requires a complete observe after the latest request/wait mutation");
19573
20272
  }
19574
20273
  const finalSnapshot = ledger.getSnapshot(ledger.latestCompleteSnapshotId);
19575
20274
  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
20275
  const evidenceRecords = [...ledger.allEvidence()];
19578
20276
  const snapshots = [...ledger.allSnapshots()];
19579
20277
  const evidenceIndex = new Map(evidenceRecords.map((record4) => [record4.evidenceId, record4]));
@@ -19586,7 +20284,7 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19586
20284
  for (const id of snapshot.evidenceIds) if (!evidenceIndex.has(id)) fail4(`Collector snapshot ref "${id}" does not resolve`);
19587
20285
  }
19588
20286
  const groups = extractCollectorEvidenceIdentityGroups(evidenceRecords, finalSnapshot.headOid);
19589
- enrichCollectorFindings({
20287
+ const findingsProjection = enrichCollectorFindings({
19590
20288
  candidate: candidateRaw,
19591
20289
  records: evidenceRecords,
19592
20290
  groups,
@@ -19595,7 +20293,6 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19595
20293
  prNumber: ledger.config.prNumber
19596
20294
  });
19597
20295
  for (const group of groups) {
19598
- if (group.attendance !== true) fail4("Collector group lacks attendance");
19599
20296
  for (const material of group.materials) {
19600
20297
  if (material.evidenceId === void 0 || !evidenceIndex.has(material.evidenceId)) fail4("Collector material lacks a receipt-local evidence ref");
19601
20298
  }
@@ -19603,10 +20300,20 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19603
20300
  if (finding2.source.evidenceId === void 0 || !evidenceIndex.has(finding2.source.evidenceId)) fail4("Collector finding lacks a receipt-local evidence ref");
19604
20301
  }
19605
20302
  }
20303
+ const unfinished = extractCollectorUnfinishedReasons(candidateRaw);
20304
+ const submissionProjection = {
20305
+ findingsSource: findingsProjection.findingsSource,
20306
+ findingsProjectedCount: findingsProjection.findingsProjectedCount,
20307
+ findingsUnprojected: findingsProjection.findingsUnprojected,
20308
+ unfinishedReasonsSource: unfinished.source,
20309
+ unfinishedReasonsProjectedCount: unfinished.reasons?.length ?? 0,
20310
+ unfinishedReasonsUnprojected: unfinished.unprojected
20311
+ };
19606
20312
  return {
19607
20313
  host: COLLECTOR_HOST,
19608
20314
  repository: ledger.config.repository.canonical,
19609
20315
  prNumber: ledger.config.prNumber,
20316
+ prState: finalSnapshot.prState,
19610
20317
  manifestDigest: ledger.config.manifest.digest,
19611
20318
  activationTime: ledger.activationTime.toISOString(),
19612
20319
  deadlineTime: ledger.deadlineTime.toISOString(),
@@ -19614,6 +20321,8 @@ function buildCollectorReceipt(ledger, candidateRaw, clock) {
19614
20321
  finalSnapshotId: finalSnapshot.snapshotId,
19615
20322
  targetHead: finalSnapshot.headOid,
19616
20323
  groups,
20324
+ ...unfinished.reasons === void 0 ? {} : { unfinishedReasons: unfinished.reasons },
20325
+ submissionProjection,
19617
20326
  requestAttempts: [...ledger.requestAttempts()],
19618
20327
  snapshots,
19619
20328
  evidenceRecords: evidenceRecords.map(toReceiptEvidenceRecord)
@@ -19643,340 +20352,26 @@ var init_collector_receipt = __esm({
19643
20352
 
19644
20353
  // src/collector-role.ts
19645
20354
  function buildMethodContext(activation) {
20355
+ const pr = activation.ledger.config.prNumber;
19646
20356
  return [
19647
20357
  "<collector_method>",
19648
20358
  `host: github.com`,
19649
20359
  `repository: ${activation.repository.canonical}`,
19650
- `prNumber: ${activation.prNumber}`,
20360
+ `prNumber: ${pr === void 0 ? "unbound \u2014 call ak_collector_bind_target with the role-decided issue/PR before observe" : String(pr)}`,
19651
20361
  `requests: ${JSON.stringify(activation.manifest.requests.map((request) => ({ id: request.id })))}`,
19652
20362
  "</collector_method>"
19653
20363
  ].join("\n");
19654
20364
  }
19655
- function createCollectorRoleRuntime(pi, dependencies, hostActions) {
19656
- let activation;
19657
- let inputCount = 0;
19658
- let lifecycleRegistered = false;
19659
- 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
- );
19729
- }
19730
- if (!firstDispatchDone) {
19731
- firstDispatchDone = true;
19732
- activation.ledger.recordActivation(activation.clock);
19733
- }
19734
- return {
19735
- systemPrompt: [
19736
- event.systemPrompt,
19737
- "",
19738
- "<collector_soul>",
19739
- activation.soul,
19740
- "</collector_soul>",
19741
- "",
19742
- buildMethodContext(activation)
19743
- ].join("\n")
19744
- };
19745
- });
19746
- pi.on("tool_call", (event) => {
19747
- if (activation === void 0) return;
19748
- if (activation.ledger.fatal) {
19749
- return {
19750
- block: true,
19751
- reason: activation.ledger.fatalReason ?? "\u901A\u8FDB\u53F8\u81F4\u547D\u72B6\u6001"
19752
- };
19753
- }
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
- if (event.toolName === COLLECTOR_OUTPUT_TOOL) {
19761
- activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, event.toolCallId);
19762
- }
19763
- if (activation.ledger.outputCandidate && event.toolName !== COLLECTOR_OUTPUT_TOOL) {
19764
- return {
19765
- block: true,
19766
- reason: "\u901A\u8FDB\u53F8\u5DF2\u4EA7\u51FA\u8F93\u51FA\u5019\u9009\uFF0C\u672C\u5C40\u4E0D\u518D\u53D7\u7406\u64CD\u4F5C"
19767
- };
19768
- }
19769
- return void 0;
19770
- });
19771
- pi.on("tool_result", (event) => {
19772
- if (activation === void 0) return;
19773
- 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") {
19806
- }
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
- }
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);
19838
- }
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
- }
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) {
19957
- hostActions.failInfrastructure(error, ctx, toolCallId);
19958
- }
19959
- throw error;
19960
- } finally {
19961
- activation.ledger.completeOperational(toolCallId);
19962
- }
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
- }
20365
+ function parsePositiveTicket(raw, label) {
20366
+ if (raw === void 0 || raw === null) return void 0;
20367
+ if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) return raw;
20368
+ if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim())) return Number(raw.trim());
20369
+ throw new CollectorTargetBindError(`ak_collector_bind_target ${label} must be a positive safe integer`);
20370
+ }
20371
+ function createCollectorRoleRuntime(pi, dependencies, hostActions) {
20372
+ let toolsRegistered = false;
20373
+ return {
20374
+ async activate(ctx) {
19980
20375
  const soul = (await dependencies.loadSoul()).trim();
19981
20376
  if (soul.length === 0) throw new Error("Collector soul is empty");
19982
20377
  const repoFlag = pi.getFlag("ak-collector-repo");
@@ -19985,58 +20380,14 @@ function createCollectorRoleRuntime(pi, dependencies, hostActions) {
19985
20380
  if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
19986
20381
  throw new Error("Collector requires --ak-collector-repo");
19987
20382
  }
19988
- if (typeof prFlag !== "string" && typeof prFlag !== "number") {
19989
- throw new Error("Collector requires --ak-collector-pr");
19990
- }
19991
20383
  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
- });
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
- );
20026
- }
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}`);
20033
- }
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}`);
20038
- }
20384
+ let prNumber;
20385
+ if (typeof prFlag === "string" && prFlag.trim().length > 0) {
20386
+ prNumber = parseCollectorPrNumber(prFlag);
20387
+ } else if (typeof prFlag === "number") {
20388
+ prNumber = parseCollectorPrNumber(prFlag);
20039
20389
  }
20390
+ const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0 ? await loadCollectorManifest(requestManifestFlag) : emptyCollectorManifest();
20040
20391
  const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
20041
20392
  const transport = dependencies.createTransport();
20042
20393
  const ledger = dependencies.createLedger(
@@ -20044,27 +20395,292 @@ function createCollectorRoleRuntime(pi, dependencies, hostActions) {
20044
20395
  clock,
20045
20396
  ctx
20046
20397
  );
20047
- if (ledger.activationRecorded) {
20048
- firstDispatchDone = true;
20049
- }
20050
- activation = {
20398
+ return {
20051
20399
  soul,
20052
20400
  repository,
20053
- prNumber,
20054
20401
  manifest,
20055
20402
  ledger,
20056
20403
  transport,
20057
20404
  clock
20058
20405
  };
20406
+ },
20407
+ assembleMaterials(activation, baseSystemPrompt) {
20408
+ return [
20409
+ baseSystemPrompt,
20410
+ "",
20411
+ "<collector_soul>",
20412
+ activation.soul,
20413
+ "</collector_soul>",
20414
+ "",
20415
+ buildMethodContext(activation)
20416
+ ].join("\n");
20417
+ },
20418
+ onToolCall(activation, event) {
20419
+ if (activation.ledger.fatal) {
20420
+ return {
20421
+ block: true,
20422
+ reason: activation.ledger.fatalReason ?? "\u901A\u8FDB\u53F8\u81F4\u547D\u72B6\u6001"
20423
+ };
20424
+ }
20425
+ if (event.toolName === COLLECTOR_OUTPUT_TOOL) {
20426
+ activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, event.toolCallId);
20427
+ }
20428
+ if (activation.ledger.outputCandidate && event.toolName !== COLLECTOR_OUTPUT_TOOL) {
20429
+ return {
20430
+ block: true,
20431
+ reason: "\u901A\u8FDB\u53F8\u5DF2\u4EA7\u51FA\u8F93\u51FA\u5019\u9009\uFF0C\u672C\u5C40\u4E0D\u518D\u53D7\u7406\u64CD\u4F5C"
20432
+ };
20433
+ }
20434
+ return void 0;
20435
+ },
20436
+ onToolResult(activation, event) {
20437
+ activation.ledger.completeOperational(event.toolCallId);
20438
+ },
20439
+ registerBusinessTools(getActivation) {
20440
+ if (toolsRegistered) return;
20441
+ toolsRegistered = true;
20442
+ pi.registerTool({
20443
+ name: COLLECTOR_BIND_TARGET_TOOL,
20444
+ label: "\u901A\u8FDB\u53F8\u8BA4\u7968\u7ED1\u5B9A",
20445
+ 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",
20446
+ promptSnippet: "\u7ED1\u5B9A\u89D2\u8272\u5224\u5B9A\u7684 issue/PR \u76EE\u6807",
20447
+ parameters: bindSchema,
20448
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
20449
+ const activation = getActivation();
20450
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20451
+ try {
20452
+ activation.ledger.beginOperational(COLLECTOR_BIND_TARGET_TOOL, toolCallId);
20453
+ const prNumber = parsePositiveTicket(params.prNumber, "prNumber");
20454
+ const issueNumber = parsePositiveTicket(params.issueNumber, "issueNumber");
20455
+ if (prNumber === void 0 && issueNumber === void 0) {
20456
+ throw new CollectorTargetBindError(
20457
+ "ak_collector_bind_target requires role-decided prNumber and/or issueNumber"
20458
+ );
20459
+ }
20460
+ let bound = prNumber;
20461
+ if (issueNumber !== void 0) {
20462
+ const associated = await listPullRequestNumbersByTicket(createGhApiRunner(), {
20463
+ owner: activation.repository.owner,
20464
+ repo: activation.repository.repo,
20465
+ ticketNumber: issueNumber
20466
+ });
20467
+ if (associated.length === 0) {
20468
+ throw new CollectorTargetBindError(
20469
+ `no PR associated with issue #${issueNumber} in ${activation.repository.canonical}; pass an explicit --pr or a different issueNumber`
20470
+ );
20471
+ }
20472
+ if (associated.length > 1) {
20473
+ throw new CollectorTargetBindError(
20474
+ `multiple PRs associated with issue #${issueNumber}: ${associated.join(", ")}; pass an explicit prNumber or --pr`
20475
+ );
20476
+ }
20477
+ const fromIssue = associated[0];
20478
+ if (prNumber !== void 0 && prNumber !== fromIssue) {
20479
+ throw new CollectorTargetBindError(
20480
+ `prNumber ${prNumber} conflicts with issue #${issueNumber} association PR ${fromIssue}`
20481
+ );
20482
+ }
20483
+ bound = fromIssue;
20484
+ }
20485
+ activation.ledger.bindTarget(bound);
20486
+ activation.ledger.completeOperational(toolCallId);
20487
+ return {
20488
+ content: [{
20489
+ type: "text",
20490
+ text: `\u76EE\u6807\u5DF2\u7ED1\u5B9A\uFF1A${activation.repository.canonical}#${bound}`
20491
+ }],
20492
+ details: {
20493
+ repository: activation.repository.canonical,
20494
+ prNumber: bound,
20495
+ ...issueNumber === void 0 ? {} : { issueNumber }
20496
+ }
20497
+ };
20498
+ } catch (error) {
20499
+ if (isCorrectableExecuteError(error)) throw error;
20500
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20501
+ } finally {
20502
+ try {
20503
+ activation.ledger.completeOperational(toolCallId);
20504
+ } catch {
20505
+ }
20506
+ }
20507
+ }
20508
+ });
20509
+ pi.registerTool({
20510
+ name: COLLECTOR_OBSERVE_TOOL,
20511
+ label: "\u901A\u8FDB\u53F8\u89C2\u5BDF",
20512
+ 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",
20513
+ promptSnippet: "\u6293\u53D6\u914D\u7F6E\u76EE\u6807 PR \u8BC1\u636E",
20514
+ parameters: observeSchema,
20515
+ async execute(toolCallId, _params, signal, _onUpdate, ctx) {
20516
+ const activation = getActivation();
20517
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20518
+ try {
20519
+ activation.ledger.beginOperational(COLLECTOR_OBSERVE_TOOL, toolCallId);
20520
+ const { snapshot, contextView } = await activation.ledger.observe(
20521
+ activation.transport,
20522
+ activation.clock,
20523
+ signal
20524
+ );
20525
+ activation.ledger.completeOperational(toolCallId);
20526
+ return {
20527
+ content: [{
20528
+ type: "text",
20529
+ text: JSON.stringify(contextView)
20530
+ }],
20531
+ details: contextView
20532
+ };
20533
+ } catch (error) {
20534
+ if (isCorrectableExecuteError(error)) throw error;
20535
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20536
+ }
20537
+ }
20538
+ });
20539
+ pi.registerTool({
20540
+ name: COLLECTOR_READ_TOOL,
20541
+ label: "\u901A\u8FDB\u53F8\u5F00\u5377",
20542
+ 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",
20543
+ promptSnippet: "\u6309\u6307\u9488\u5F00\u5377\u8BFB\u6750\u6599",
20544
+ parameters: readSchema,
20545
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
20546
+ const activation = getActivation();
20547
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20548
+ try {
20549
+ activation.ledger.beginOperational(COLLECTOR_READ_TOOL, toolCallId);
20550
+ const record4 = activation.ledger.getEvidence(params.evidenceId);
20551
+ if (record4 === void 0 || record4.kind !== "review" && record4.kind !== "issue_comment" && record4.kind !== "review_comment" && record4.kind !== "reaction" || typeof record4.body !== "string") {
20552
+ throw new CollectorUnknownEvidenceError(params.evidenceId);
20553
+ }
20554
+ const material = projectEvidenceEntryView(record4);
20555
+ activation.ledger.completeOperational(toolCallId);
20556
+ return {
20557
+ content: [{
20558
+ type: "text",
20559
+ text: JSON.stringify(material)
20560
+ }],
20561
+ details: material
20562
+ };
20563
+ } catch (error) {
20564
+ if (isCorrectableExecuteError(error)) throw error;
20565
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20566
+ }
20567
+ }
20568
+ });
20569
+ pi.registerTool({
20570
+ name: COLLECTOR_REQUEST_TOOL,
20571
+ label: "\u901A\u8FDB\u53F8\u8BF7\u6C42",
20572
+ 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",
20573
+ promptSnippet: "\u6309\u914D\u7F6E\u53D1\u4E00\u6B21\u8BF7\u6C42",
20574
+ parameters: requestSchema,
20575
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
20576
+ const activation = getActivation();
20577
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20578
+ try {
20579
+ activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
20580
+ const details = await activation.ledger.request(
20581
+ params,
20582
+ activation.transport,
20583
+ activation.clock,
20584
+ signal
20585
+ );
20586
+ activation.ledger.completeOperational(toolCallId);
20587
+ return {
20588
+ content: [{
20589
+ type: "text",
20590
+ text: `\u8BF7\u6C42\u5C1D\u8BD5\u5DF2\u8BB0\u5F55\uFF1Arequest ${params.requestId}`
20591
+ }],
20592
+ details
20593
+ };
20594
+ } catch (error) {
20595
+ if (isCorrectableExecuteError(error)) throw error;
20596
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20597
+ }
20598
+ }
20599
+ });
20600
+ pi.registerTool({
20601
+ name: COLLECTOR_WAIT_TOOL,
20602
+ label: "\u901A\u8FDB\u53F8\u7B49\u5F85",
20603
+ description: "\u518D\u89C2\u5BDF\u524D\u7B49\u5F85\uFF1B\u5355\u6B21\u4E0A\u9650\u4E94\u5206\u949F\u4E14\u4E0D\u8D85\u5269\u4F59\u8D44\u683C\u3002",
20604
+ promptSnippet: "\u8D44\u683C\u622A\u6B62\u524D\u7B49\u5F85",
20605
+ parameters: waitSchema,
20606
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
20607
+ const activation = getActivation();
20608
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20609
+ try {
20610
+ activation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
20611
+ const details = await activation.ledger.wait(
20612
+ params,
20613
+ activation.clock,
20614
+ signal
20615
+ );
20616
+ activation.ledger.completeOperational(toolCallId);
20617
+ return {
20618
+ content: [{
20619
+ type: "text",
20620
+ text: `\u5DF2\u7B49\u5F85 ${String(details.effectiveMs)}ms`
20621
+ }],
20622
+ details
20623
+ };
20624
+ } catch (error) {
20625
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20626
+ }
20627
+ }
20628
+ });
20629
+ pi.registerTool({
20630
+ name: COLLECTOR_OUTPUT_TOOL,
20631
+ label: "\u901A\u8FDB\u53F8\u8F93\u51FA",
20632
+ 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",
20633
+ promptSnippet: "\u63D0\u4EA4\u901A\u8FDB\u53F8\u56DE\u6267",
20634
+ bounceInfrastructureDeclaration(params) {
20635
+ const activation = getActivation();
20636
+ if (activation === void 0) return void 0;
20637
+ try {
20638
+ buildCollectorReceipt(activation.ledger, params, activation.clock);
20639
+ } catch {
20640
+ return void 0;
20641
+ }
20642
+ return new CollectorNormalCompletionDeclarationError();
20643
+ },
20644
+ parameters: outputSchema,
20645
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
20646
+ const activation = getActivation();
20647
+ if (activation === void 0) throw new Error("\u901A\u8FDB\u53F8\u672A\u6FC0\u6D3B");
20648
+ try {
20649
+ activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, toolCallId);
20650
+ const receipt = buildCollectorReceipt(
20651
+ activation.ledger,
20652
+ params,
20653
+ activation.clock
20654
+ );
20655
+ activation.ledger.recordOutputCandidate();
20656
+ return {
20657
+ content: [{
20658
+ type: "text",
20659
+ text: COLLECTOR_ACCEPTED_TEXT
20660
+ }],
20661
+ details: receipt,
20662
+ terminate: true
20663
+ };
20664
+ } catch (error) {
20665
+ if (error instanceof Error && error.collectorFatal === true) {
20666
+ hostActions.failInfrastructure(error, ctx, toolCallId);
20667
+ }
20668
+ throw error;
20669
+ } finally {
20670
+ activation.ledger.completeOperational(toolCallId);
20671
+ }
20672
+ }
20673
+ });
20059
20674
  }
20060
20675
  };
20061
20676
  }
20062
- var CollectorNormalCompletionDeclarationError, COLLECTOR_REQUIRED_TOOLS, observeSchema, readSchema, requestSchema, waitSchema, outputSchema;
20677
+ var CollectorNormalCompletionDeclarationError, CollectorTargetBindError, COLLECTOR_REQUIRED_TOOLS, COLLECTOR_TRANSPORT_FLAGS, observeSchema, readSchema, requestSchema, waitSchema, bindSchema, outputSchema;
20063
20678
  var init_collector_role = __esm({
20064
20679
  "src/collector-role.ts"() {
20065
20680
  "use strict";
20066
20681
  init_collector_config();
20067
20682
  init_collector_evidence();
20683
+ init_collector_github();
20068
20684
  init_collector_ledger();
20069
20685
  init_collector_receipt();
20070
20686
  init_collector_tool_schemas();
@@ -20072,24 +20688,54 @@ var init_collector_role = __esm({
20072
20688
  init_submission_correctable_error();
20073
20689
  init_collector_identity();
20074
20690
  init_collector_identity();
20075
- init_collector_config();
20076
20691
  CollectorNormalCompletionDeclarationError = class extends CorrectableSubmissionError {
20077
20692
  constructor() {
20078
20693
  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
20694
  this.name = "CollectorNormalCompletionDeclarationError";
20080
20695
  }
20081
20696
  };
20697
+ CollectorTargetBindError = class extends CorrectableSubmissionError {
20698
+ constructor(message) {
20699
+ super(message);
20700
+ this.name = "CollectorTargetBindError";
20701
+ }
20702
+ };
20082
20703
  COLLECTOR_REQUIRED_TOOLS = [
20704
+ COLLECTOR_BIND_TARGET_TOOL,
20083
20705
  COLLECTOR_OBSERVE_TOOL,
20084
20706
  COLLECTOR_READ_TOOL,
20085
20707
  COLLECTOR_REQUEST_TOOL,
20086
20708
  COLLECTOR_WAIT_TOOL,
20087
20709
  COLLECTOR_OUTPUT_TOOL
20088
20710
  ];
20711
+ COLLECTOR_TRANSPORT_FLAGS = Object.freeze([
20712
+ Object.freeze({
20713
+ name: "ak-collector-repo",
20714
+ definition: Object.freeze({
20715
+ description: "GitHub owner/repo target for Collector (github.com only; conservative ASCII grammar).",
20716
+ type: "string"
20717
+ })
20718
+ }),
20719
+ Object.freeze({
20720
+ name: "ak-collector-pr",
20721
+ definition: Object.freeze({
20722
+ description: "Optional positive safe-integer pull request number for Collector. Omit when the role will bind from task materials.",
20723
+ type: "string"
20724
+ })
20725
+ }),
20726
+ Object.freeze({
20727
+ name: "ak-collector-request-manifest",
20728
+ definition: Object.freeze({
20729
+ description: "Path to the Collector v1 request manifest JSON file.",
20730
+ type: "string"
20731
+ })
20732
+ })
20733
+ ]);
20089
20734
  observeSchema = collectorObserveArgsSchema;
20090
20735
  readSchema = collectorReadArgsSchema;
20091
20736
  requestSchema = collectorRequestArgsSchema;
20092
20737
  waitSchema = collectorWaitArgsSchema;
20738
+ bindSchema = collectorBindTargetArgsSchema;
20093
20739
  outputSchema = collectorOutputArgsSchema;
20094
20740
  }
20095
20741
  });
@@ -21070,11 +21716,11 @@ var init_reviewer_role = __esm({
21070
21716
  });
21071
21717
 
21072
21718
  // src/worker-submission-gates.ts
21073
- import { execFileSync as execFileSync3 } from "node:child_process";
21074
- import { existsSync as existsSync10, lstatSync as lstatSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmdirSync, rmSync } from "node:fs";
21719
+ import { execFileSync as execFileSync4 } from "node:child_process";
21720
+ import { existsSync as existsSync11, lstatSync as lstatSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmdirSync, rmSync } from "node:fs";
21075
21721
  import { resolve as resolve18 } from "node:path";
21076
21722
  function git2(cwd, args) {
21077
- return execFileSync3("git", args, {
21723
+ return execFileSync4("git", args, {
21078
21724
  cwd,
21079
21725
  encoding: "utf8",
21080
21726
  stdio: ["ignore", "pipe", "pipe"],
@@ -21082,7 +21728,7 @@ function git2(cwd, args) {
21082
21728
  }).trim();
21083
21729
  }
21084
21730
  function gitFile(file, args) {
21085
- return execFileSync3("git", ["config", "--file", file, ...args], {
21731
+ return execFileSync4("git", ["config", "--file", file, ...args], {
21086
21732
  encoding: "utf8",
21087
21733
  stdio: ["ignore", "pipe", "pipe"],
21088
21734
  env: { ...process.env, GIT_DIR: void 0, GIT_WORK_TREE: void 0, GIT_COMMON_DIR: void 0 }
@@ -21092,7 +21738,7 @@ function statusOf(error) {
21092
21738
  return typeof error === "object" && error !== null && "status" in error ? error.status : void 0;
21093
21739
  }
21094
21740
  function tryGetAll(file, key) {
21095
- if (!existsSync10(file)) return [];
21741
+ if (!existsSync11(file)) return [];
21096
21742
  try {
21097
21743
  const out = gitFile(file, ["--get-all", key]);
21098
21744
  return out.length === 0 ? [] : out.split("\n");
@@ -21102,7 +21748,7 @@ function tryGetAll(file, key) {
21102
21748
  }
21103
21749
  }
21104
21750
  function ownedHook(path) {
21105
- if (!existsSync10(path)) return false;
21751
+ if (!existsSync11(path)) return false;
21106
21752
  return readFileSync4(path, "utf8").includes(HOOK_MARKER);
21107
21753
  }
21108
21754
  function escapeGitConfigValueRegex(value) {
@@ -21129,11 +21775,11 @@ function rmOwnedDir(dir) {
21129
21775
  const hookPath = resolve18(dir, HOOK_FILE);
21130
21776
  if (!ownedHook(hookPath)) return;
21131
21777
  rmSync(hookPath, { force: true });
21132
- if (existsSync10(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
21778
+ if (existsSync11(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
21133
21779
  }
21134
21780
  function linkedGitDirs(commonDir) {
21135
21781
  const root = resolve18(commonDir, "worktrees");
21136
- if (!existsSync10(root)) return [];
21782
+ if (!existsSync11(root)) return [];
21137
21783
  return readdirSync2(root).map((name) => resolve18(root, name)).filter((dir) => lstatSync3(dir).isDirectory());
21138
21784
  }
21139
21785
  function uninstallPackageWorkerHooks(cwd) {
@@ -21822,7 +22468,7 @@ var init_activation_reconciliation = __esm({
21822
22468
 
21823
22469
  // src/role-runtime.ts
21824
22470
  import { readFileSync as readFileSync5, writeSync as writeSync4 } from "node:fs";
21825
- import { join as join31 } from "node:path";
22471
+ import { join as join32 } from "node:path";
21826
22472
  import { Value as Value5 } from "typebox/value";
21827
22473
  function decodeReviewerAdmittedInputs(getFlag) {
21828
22474
  let reviewScopeKeys;
@@ -22194,7 +22840,7 @@ function readDiaristRunCoordinates() {
22194
22840
  if (typeof runDirectory !== "string" || runDirectory.trim() === "") {
22195
22841
  throw new Error("diarist accept requires AK_ROLE_RUN_DIR");
22196
22842
  }
22197
- const admittedPath = join31(runDirectory, "admitted-request.json");
22843
+ const admittedPath = join32(runDirectory, "admitted-request.json");
22198
22844
  const admitted = JSON.parse(readFileSync5(admittedPath, "utf8"));
22199
22845
  if (typeof admitted.projectRoot !== "string" || admitted.projectRoot.trim() === "") {
22200
22846
  throw new Error(`diarist admitted-request missing projectRoot (${admittedPath})`);
@@ -22310,6 +22956,9 @@ function createRoleRuntimeExtension(dependencies) {
22310
22956
  for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
22311
22957
  roleHost.registerFlag(flag.name, flag.definition);
22312
22958
  }
22959
+ for (const flag of COLLECTOR_TRANSPORT_FLAGS) {
22960
+ roleHost.registerFlag(flag.name, flag.definition);
22961
+ }
22313
22962
  let admitted = false;
22314
22963
  let selectedRole;
22315
22964
  let activeReviewerParent;
@@ -22440,10 +23089,41 @@ function createRoleRuntimeExtension(dependencies) {
22440
23089
  })
22441
23090
  };
22442
23091
  }
23092
+ if (role === "collector" && activeCollector !== void 0) {
23093
+ const options = event.systemPromptOptions;
23094
+ if (options.skills && options.skills.length > 0) {
23095
+ failInfrastructure(
23096
+ activeCollector.ledger.latchFatal("\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230\u7CFB\u7EDF\u63D0\u793A\u4E2D\u7684\u73AF\u5883 skills"),
23097
+ ctx
23098
+ );
23099
+ }
23100
+ if (options.contextFiles && options.contextFiles.length > 0) {
23101
+ failInfrastructure(
23102
+ activeCollector.ledger.latchFatal("\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230\u7CFB\u7EDF\u63D0\u793A\u4E2D\u7684\u73AF\u5883 context files"),
23103
+ ctx
23104
+ );
23105
+ }
23106
+ if (typeof options.appendSystemPrompt === "string" && options.appendSystemPrompt.trim().length > 0) {
23107
+ failInfrastructure(
23108
+ activeCollector.ledger.latchFatal("\u901A\u8FDB\u53F8\u68C0\u6D4B\u5230 appendSystemPrompt \u6F02\u79FB"),
23109
+ ctx
23110
+ );
23111
+ }
23112
+ if (!collectorFirstDispatchDone) {
23113
+ collectorFirstDispatchDone = true;
23114
+ activeCollector.ledger.recordActivation(activeCollector.clock);
23115
+ }
23116
+ return {
23117
+ systemPrompt: collectorBusiness.assembleMaterials(activeCollector, event.systemPrompt)
23118
+ };
23119
+ }
22443
23120
  });
22444
23121
  roleHost.on("tool_result", async (event) => {
22445
23122
  const role = selectedRole;
22446
23123
  if (role === void 0) return;
23124
+ if (role === "collector" && activeCollector !== void 0) {
23125
+ collectorBusiness.onToolResult(activeCollector, event);
23126
+ }
22447
23127
  const pendingInfra = pendingInfrastructureFailures.get(event.toolCallId);
22448
23128
  const isRoleInfrastructureFailure = pendingInfra !== void 0;
22449
23129
  if (pendingInfra !== void 0) pendingInfrastructureFailures.delete(event.toolCallId);
@@ -22541,6 +23221,11 @@ function createRoleRuntimeExtension(dependencies) {
22541
23221
  priorFetch = void 0;
22542
23222
  fetchWrapped = false;
22543
23223
  }
23224
+ if (selectedRole === "collector" && activeCollector !== void 0 && activeCollector.ledger.fatal) {
23225
+ if (process.exitCode === void 0 || process.exitCode === 0) {
23226
+ process.exitCode = 1;
23227
+ }
23228
+ }
22544
23229
  const presentation = pendingNavigatorPresentation;
22545
23230
  pendingNavigatorPresentation = void 0;
22546
23231
  if (presentation !== void 0) {
@@ -22742,7 +23427,9 @@ function createRoleRuntimeExtension(dependencies) {
22742
23427
  }
22743
23428
  }
22744
23429
  }, hostActions);
22745
- const collector = createCollectorRoleRuntime(
23430
+ let activeCollector;
23431
+ let collectorFirstDispatchDone = false;
23432
+ const collectorBusiness = createCollectorRoleRuntime(
22746
23433
  roleHost,
22747
23434
  {
22748
23435
  async loadSoul() {
@@ -22771,13 +23458,90 @@ function createRoleRuntimeExtension(dependencies) {
22771
23458
  dossierEntries: context.sessionManager?.getEntries?.() ?? []
22772
23459
  });
22773
23460
  },
22774
- ...dependencies.createCollectorClock === void 0 ? {} : { createClock: dependencies.createCollectorClock },
22775
- ...dependencies.collectorPackageExtensionPath === void 0 ? {} : {
22776
- packageExtensionPath: dependencies.collectorPackageExtensionPath
22777
- }
23461
+ ...dependencies.createCollectorClock === void 0 ? {} : { createClock: dependencies.createCollectorClock }
22778
23462
  },
22779
23463
  hostActions
22780
23464
  );
23465
+ let collectorToolCallRegistered = false;
23466
+ const collector = {
23467
+ async activate(context, event) {
23468
+ activeCollector = void 0;
23469
+ collectorFirstDispatchDone = false;
23470
+ if (context.mode !== "print" && context.mode !== "json") {
23471
+ throw new Error(
23472
+ `Collector supports only print or json mode (got ${context.mode})`
23473
+ );
23474
+ }
23475
+ if (event.reason === "fork" || event.reason === "reload") {
23476
+ throw new Error(
23477
+ `Collector does not support session_start reason ${event.reason}`
23478
+ );
23479
+ }
23480
+ const commands = roleHost.getCommands?.() ?? [];
23481
+ const ambientCommands = commands.filter((command) => {
23482
+ const name = command.name.toLowerCase();
23483
+ return name.includes("skill") || name.includes("prompt") || name.startsWith("template");
23484
+ });
23485
+ if (ambientCommands.length > 0) {
23486
+ throw new Error(
23487
+ `Collector detected ambient instruction commands: ${ambientCommands.map((c) => c.name).join(", ")}`
23488
+ );
23489
+ }
23490
+ const preExisting = roleHost.getAllTools();
23491
+ const alreadyRegistered = COLLECTOR_REQUIRED_TOOLS.every(
23492
+ (required) => preExisting.some((tool) => tool.name === required)
23493
+ );
23494
+ if (!alreadyRegistered) {
23495
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
23496
+ const prior = preExisting.filter((tool) => tool.name === required);
23497
+ if (prior.length > 0) {
23498
+ throw new Error(`Collector required tool name collision: ${required}`);
23499
+ }
23500
+ }
23501
+ }
23502
+ collectorBusiness.registerBusinessTools(() => activeCollector);
23503
+ const allTools = roleHost.getAllTools();
23504
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
23505
+ const matches = allTools.filter((tool) => tool.name === required);
23506
+ if (matches.length === 0) {
23507
+ throw new Error(`Collector required tool missing: ${required}`);
23508
+ }
23509
+ if (matches.length > 1) {
23510
+ throw new Error(`Collector required tool name collision: ${required}`);
23511
+ }
23512
+ }
23513
+ roleHost.setActiveTools([...COLLECTOR_REQUIRED_TOOLS]);
23514
+ const active = new Set(roleHost.getActiveTools());
23515
+ for (const required of COLLECTOR_REQUIRED_TOOLS) {
23516
+ if (!active.has(required)) {
23517
+ throw new Error(`Collector failed to activate required tool ${required}`);
23518
+ }
23519
+ }
23520
+ for (const name of active) {
23521
+ if (!COLLECTOR_REQUIRED_TOOLS.includes(name)) {
23522
+ throw new Error(`Collector active tool surface includes unexpected ${name}`);
23523
+ }
23524
+ }
23525
+ if (!collectorToolCallRegistered) {
23526
+ collectorToolCallRegistered = true;
23527
+ roleHost.on("tool_call", (toolEvent) => {
23528
+ if (activeCollector === void 0 || selectedRole !== "collector") return;
23529
+ if (!COLLECTOR_REQUIRED_TOOLS.includes(toolEvent.toolName)) {
23530
+ return {
23531
+ block: true,
23532
+ reason: `\u901A\u8FDB\u53F8\u7981\u7528\u5DE5\u5177 ${toolEvent.toolName}`
23533
+ };
23534
+ }
23535
+ return collectorBusiness.onToolCall(activeCollector, toolEvent);
23536
+ });
23537
+ }
23538
+ const activation = await collectorBusiness.activate(context);
23539
+ if (activation.ledger.activationRecorded) {
23540
+ collectorFirstDispatchDone = true;
23541
+ }
23542
+ activeCollector = activation;
23543
+ }
23544
+ };
22781
23545
  const clock = dependencies.activationClock ?? (() => (/* @__PURE__ */ new Date()).toISOString());
22782
23546
  const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
22783
23547
  const observationFace = createToolExecutionObservationFace({
@@ -23437,7 +24201,7 @@ __export(in_process_session_exports, {
23437
24201
  });
23438
24202
  import { mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
23439
24203
  import { tmpdir as tmpdir2 } from "node:os";
23440
- import { join as join32 } from "node:path";
24204
+ import { join as join33 } from "node:path";
23441
24205
  import {
23442
24206
  createAgentSession,
23443
24207
  DefaultResourceLoader,
@@ -23560,7 +24324,7 @@ async function openPiInProcessSession(options) {
23560
24324
  let scratchDir;
23561
24325
  let resolvedAgentDir = options.agentDir;
23562
24326
  if (resolvedAgentDir === void 0) {
23563
- scratchDir = await mkdtemp2(join32(options.credentialScratchParent ?? tmpdir2(), "ak-institutional-"));
24327
+ scratchDir = await mkdtemp2(join33(options.credentialScratchParent ?? tmpdir2(), "ak-institutional-"));
23564
24328
  resolvedAgentDir = scratchDir;
23565
24329
  }
23566
24330
  try {
@@ -24256,7 +25020,7 @@ var init_reviewer_child_executor = __esm({
24256
25020
  import { spawn as spawn4 } from "node:child_process";
24257
25021
  import { mkdtemp as mkdtemp3, rm as rm3 } from "node:fs/promises";
24258
25022
  import { tmpdir as tmpdir3 } from "node:os";
24259
- import { join as join33 } from "node:path";
25023
+ import { join as join34 } from "node:path";
24260
25024
  async function runCommand(command, args, options = {}) {
24261
25025
  return await new Promise((resolve20, reject) => {
24262
25026
  const child = spawn4(command, args, { ...options.cwd === void 0 ? {} : { cwd: options.cwd }, stdio: ["ignore", "pipe", "pipe"], signal: options.signal });
@@ -24305,8 +25069,8 @@ async function prepareSnapshot(accepted, signal, dependencies) {
24305
25069
  if (!sameReviewerPinnedTarget({ repositoryRoot: accepted.repositoryRoot, objectFormat, targetHead }, accepted)) throw new Error("Accepted Reviewer target identity no longer matches the repository");
24306
25070
  await git3(accepted.repositoryRoot, ["cat-file", "-e", `${targetHead}^{commit}`], signal);
24307
25071
  dependencies.fault?.("mirror.before-create");
24308
- mirrorRoot = await mkdtemp3(join33(tmpdir3(), "ak-reviewer-snapshot-"));
24309
- const mirrorPath = join33(mirrorRoot, "repository.git");
25072
+ mirrorRoot = await mkdtemp3(join34(tmpdir3(), "ak-reviewer-snapshot-"));
25073
+ const mirrorPath = join34(mirrorRoot, "repository.git");
24310
25074
  dependencies.fault?.("mirror.create");
24311
25075
  await runCommand("git", ["init", "--bare", `--object-format=${accepted.objectFormat}`, mirrorPath], signal === void 0 ? {} : { signal });
24312
25076
  await git3(mirrorPath, ["fetch", "--no-tags", accepted.repositoryRoot, targetHead], signal);
@@ -24323,7 +25087,7 @@ async function prepareClone(snapshot, signal, dependencies) {
24323
25087
  const target = { repositoryRoot: snapshot.repositoryRoot, objectFormat: snapshot.objectFormat, targetHead: snapshot.targetHead, refs: { ...snapshot.refs } };
24324
25088
  try {
24325
25089
  dependencies.fault?.("workspace.before-create");
24326
- workspace = await mkdtemp3(join33(tmpdir3(), "ak-reviewer-leg-"));
25090
+ workspace = await mkdtemp3(join34(tmpdir3(), "ak-reviewer-leg-"));
24327
25091
  dependencies.fault?.("workspace.init");
24328
25092
  await git3(workspace, ["init", `--object-format=${snapshot.objectFormat}`, "--initial-branch=ak-reviewer-unborn"], signal);
24329
25093
  dependencies.fault?.("workspace.fetch");
@@ -24738,9 +25502,9 @@ init_auditor_soul();
24738
25502
  init_session_opening_materials();
24739
25503
 
24740
25504
  // src/acp-host/description.ts
24741
- import { join as join34 } from "node:path";
25505
+ import { join as join35 } from "node:path";
24742
25506
  function resolveAcpBinary(description, operatorHome) {
24743
- return join34(operatorHome, ...description.binaryFromHome);
25507
+ return join35(operatorHome, ...description.binaryFromHome);
24744
25508
  }
24745
25509
  function acpStdioArgs(description, model, seat) {
24746
25510
  const { prefix, suffix, modelFlag, thinkingFlag } = description.argv;
@@ -24766,7 +25530,7 @@ init_role_runtime();
24766
25530
  import { randomUUID as randomUUID6 } from "node:crypto";
24767
25531
  import { mkdir as mkdir5, readFile as readFile18, writeFile as writeFile8 } from "node:fs/promises";
24768
25532
  import { createServer } from "node:net";
24769
- import { basename as basename7, dirname as dirname17, join as join35 } from "node:path";
25533
+ import { basename as basename7, dirname as dirname17, join as join36 } from "node:path";
24770
25534
  import { fileURLToPath as fileURLToPath2 } from "node:url";
24771
25535
 
24772
25536
  // src/acp-host/role-turn-host.ts
@@ -25050,7 +25814,6 @@ ${priorNativePaths.join("\n")}` : prepared.prompt;
25050
25814
  }
25051
25815
 
25052
25816
  // src/acp-host/role-envelope.ts
25053
- init_submission_errors();
25054
25817
  init_submission_correctable_error();
25055
25818
  init_navigator_invocation_identity();
25056
25819
  function parseCanonicalSkillInvocation(prompt) {
@@ -25096,7 +25859,7 @@ function projectAcpActivationFlags(request) {
25096
25859
  }
25097
25860
  if (activation.role === "collector") {
25098
25861
  flags.set("ak-collector-repo", activation.repo);
25099
- flags.set("ak-collector-pr", activation.pr);
25862
+ if (activation.pr !== void 0) flags.set("ak-collector-pr", activation.pr);
25100
25863
  if (activation.requestManifestPath !== void 0) flags.set("ak-collector-request-manifest", activation.requestManifestPath);
25101
25864
  }
25102
25865
  return flags;
@@ -25122,8 +25885,23 @@ function createComposedAcpRoleTurnHost(config) {
25122
25885
  })
25123
25886
  });
25124
25887
  }
25888
+ function terminatingToolJsonSchema(parameters) {
25889
+ const cloned = JSON.parse(JSON.stringify(parameters));
25890
+ return Object.freeze({
25891
+ ...cloned,
25892
+ $schema: "http://json-schema.org/draft-07/schema#"
25893
+ });
25894
+ }
25125
25895
  async function prepareAcpRoleEnvelope(options) {
25126
25896
  const { request } = options;
25897
+ if (options.socketPath === "") {
25898
+ throw new Error("prepareAcpRoleEnvelope requires socketPath");
25899
+ }
25900
+ const listTerminatingToolOnMcp = options.listTerminatingToolOnMcp !== false;
25901
+ const earlyTerminatingTool = packagedRoleOutputTool(request.activation.role);
25902
+ if (earlyTerminatingTool === void 0) {
25903
+ throw new Error(`role has no terminating tool: ${request.activation.role}`);
25904
+ }
25127
25905
  const flags = projectAcpActivationFlags(request);
25128
25906
  const tools = /* @__PURE__ */ new Map();
25129
25907
  const handlers = /* @__PURE__ */ new Map();
@@ -25143,7 +25921,7 @@ async function prepareAcpRoleEnvelope(options) {
25143
25921
  const raw = await readFile18(method.path, "utf8");
25144
25922
  methodSkills.set(name, { path: method.path, body: stripSkillFrontmatter(raw).trim() });
25145
25923
  }
25146
- let sessionFile = options.sessionFile ?? join35(request.runDirectory, "session", "session.jsonl");
25924
+ let sessionFile = options.sessionFile ?? join36(request.runDirectory, "session", "session.jsonl");
25147
25925
  await mkdir5(dirname17(sessionFile), { recursive: true });
25148
25926
  if (request.continuation.kind !== "resume") {
25149
25927
  try {
@@ -25346,6 +26124,68 @@ async function prepareAcpRoleEnvelope(options) {
25346
26124
  await emit("tool_execution_end", { toolCallId, toolName, isError: projected.isError });
25347
26125
  return projected;
25348
26126
  }
26127
+ async function invokeAkTool(name, args) {
26128
+ const tool = tools.get(name);
26129
+ if (tool === void 0) throw new Error(`Unknown AK tool: ${name}`);
26130
+ const toolCallId = randomUUID6();
26131
+ calls.push({ toolCallId, toolName: name });
26132
+ sessionEntries.push({
26133
+ type: "message",
26134
+ message: {
26135
+ role: "assistant",
26136
+ content: [{
26137
+ type: "toolCall",
26138
+ id: toolCallId,
26139
+ name,
26140
+ arguments: args ?? {}
26141
+ }]
26142
+ }
26143
+ });
26144
+ try {
26145
+ await emit("tool_execution_start", { toolCallId, toolName: name });
26146
+ const blocked = (await emit("tool_call", { toolCallId, toolName: name, input: args ?? {} })).some((value) => typeof value === "object" && value !== null && "block" in value && value.block === true);
26147
+ if (blocked) {
26148
+ return { content: [{ type: "text", text: `AK tool blocked: ${name}` }], isError: true, blocked: true };
26149
+ }
26150
+ } catch (error) {
26151
+ const declared = declareRoundInfrastructureFailure(error);
26152
+ try {
26153
+ const projected = await projectToolResult(toolCallId, name, {
26154
+ content: declared.content,
26155
+ details: declared.details,
26156
+ isError: true
26157
+ });
26158
+ return { content: projected.content, isError: true };
26159
+ } catch {
26160
+ return { content: declared.content, isError: true };
26161
+ }
26162
+ }
26163
+ try {
26164
+ const result = await tool.execute(toolCallId, args ?? {}, void 0, void 0, context);
26165
+ const projected = await projectToolResult(toolCallId, name, {
26166
+ content: result.content,
26167
+ details: result.details,
26168
+ isError: false
26169
+ });
26170
+ return { content: projected.content, isError: projected.isError };
26171
+ } catch (error) {
26172
+ let content;
26173
+ let details;
26174
+ if (isCorrectableExecuteError(error)) {
26175
+ const projected2 = projectCorrectableExecuteRejection(error);
26176
+ content = [{ type: "text", text: projected2.diagnostic }];
26177
+ details = projected2.details;
26178
+ } else {
26179
+ ({ content, details } = declareRoundInfrastructureFailure(error));
26180
+ }
26181
+ const projected = await projectToolResult(toolCallId, name, {
26182
+ content,
26183
+ details,
26184
+ isError: true
26185
+ });
26186
+ return { content: projected.content, isError: projected.isError };
26187
+ }
26188
+ }
25349
26189
  function reply(socket, id, result, error) {
25350
26190
  const rpcError = error instanceof Error ? { code: "ak-relay-failure", name: error.name, message: error.message } : { code: "ak-relay-failure", name: "RelayFailure", message: String(error) };
25351
26191
  socket.write(`${JSON.stringify({ id, ...error === void 0 ? { result } : { error: rpcError } })}
@@ -25374,8 +26214,9 @@ async function prepareAcpRoleEnvelope(options) {
25374
26214
  }
25375
26215
  try {
25376
26216
  if (rpc.method === "tools/list") {
25377
- reply(socket, rpc.id, { tools: [...tools.values()].map((tool2) => {
25378
- return { name: tool2.name, description: tool2.description, inputSchema: tool2.parameters };
26217
+ const listed = [...tools.values()].filter((tool) => listTerminatingToolOnMcp || tool.name !== earlyTerminatingTool);
26218
+ reply(socket, rpc.id, { tools: listed.map((tool) => {
26219
+ return { name: tool.name, description: tool.description, inputSchema: tool.parameters };
25379
26220
  }) });
25380
26221
  return;
25381
26222
  }
@@ -25383,82 +26224,15 @@ async function prepareAcpRoleEnvelope(options) {
25383
26224
  const params = rpc.params;
25384
26225
  const name = params?.name;
25385
26226
  if (typeof name !== "string") throw new Error("MCP tool name is missing");
25386
- const tool = tools.get(name);
25387
- if (tool === void 0) throw new Error(`Unknown AK tool: ${name}`);
25388
- const toolCallId = randomUUID6();
25389
- calls.push({ toolCallId, toolName: name });
25390
- {
25391
- const message = {
25392
- role: "assistant",
25393
- content: [{
25394
- type: "toolCall",
25395
- id: toolCallId,
25396
- name,
25397
- arguments: params?.arguments ?? {}
25398
- }]
25399
- };
25400
- sessionEntries.push({ type: "message", message });
26227
+ if (!listTerminatingToolOnMcp && name === earlyTerminatingTool) {
26228
+ throw new Error(`terminating tool ${name} is schema-channel only on this host`);
25401
26229
  }
25402
- try {
25403
- await emit("tool_execution_start", { toolCallId, toolName: name });
25404
- const blocked = (await emit("tool_call", { toolCallId, toolName: name, input: params?.arguments ?? {} })).some((value) => typeof value === "object" && value !== null && "block" in value && value.block === true);
25405
- if (blocked) {
25406
- reply(socket, rpc.id, void 0, new Error(`AK tool blocked: ${name}`));
25407
- return;
25408
- }
25409
- } catch (error) {
25410
- const declared = declareRoundInfrastructureFailure(error);
25411
- try {
25412
- const projected = await projectToolResult(toolCallId, name, {
25413
- content: declared.content,
25414
- details: declared.details,
25415
- isError: true
25416
- });
25417
- reply(socket, rpc.id, {
25418
- content: projected.content,
25419
- isError: true
25420
- });
25421
- } catch {
25422
- reply(socket, rpc.id, {
25423
- content: declared.content,
25424
- isError: true
25425
- });
25426
- }
26230
+ const outcome = await invokeAkTool(name, params?.arguments ?? {});
26231
+ if (outcome.blocked === true) {
26232
+ reply(socket, rpc.id, void 0, new Error(outcome.content.map((p) => p.type === "text" ? p.text : "").join("")));
25427
26233
  return;
25428
26234
  }
25429
- try {
25430
- const result = await tool.execute(toolCallId, params?.arguments ?? {}, void 0, void 0, context);
25431
- const projected = await projectToolResult(toolCallId, name, {
25432
- content: result.content,
25433
- details: result.details,
25434
- isError: false
25435
- });
25436
- reply(socket, rpc.id, { content: projected.content, ...projected.isError ? { isError: true } : {} });
25437
- } catch (error) {
25438
- let content;
25439
- let details;
25440
- 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
- }
25452
- } else {
25453
- ({ content, details } = declareRoundInfrastructureFailure(error));
25454
- }
25455
- const projected = await projectToolResult(toolCallId, name, {
25456
- content,
25457
- details,
25458
- isError: true
25459
- });
25460
- reply(socket, rpc.id, { content: projected.content, ...projected.isError ? { isError: true } : {} });
25461
- }
26235
+ reply(socket, rpc.id, { content: outcome.content, ...outcome.isError ? { isError: true } : {} });
25462
26236
  } catch (error) {
25463
26237
  reply(socket, rpc.id, void 0, error);
25464
26238
  }
@@ -25466,8 +26240,8 @@ async function prepareAcpRoleEnvelope(options) {
25466
26240
  }
25467
26241
  });
25468
26242
  }
25469
- await listen(server, options.socketPath);
25470
26243
  const relay = fileURLToPath2(new URL("./mcp-relay.mjs", import.meta.url));
26244
+ await listen(server, options.socketPath);
25471
26245
  let disposed = false;
25472
26246
  let priorAkRoleRunDir;
25473
26247
  let priorAkRoleCourtAttempt;
@@ -25510,6 +26284,11 @@ async function prepareAcpRoleEnvelope(options) {
25510
26284
  });
25511
26285
  }
25512
26286
  };
26287
+ const terminatingToolName = earlyTerminatingTool;
26288
+ async function ingestStructuredOutput(params) {
26289
+ if (calls.some((call) => call.toolName === terminatingToolName)) return;
26290
+ await invokeAkTool(terminatingToolName, params ?? {});
26291
+ }
25513
26292
  const closeRound = async () => {
25514
26293
  if (calls.length > 0) {
25515
26294
  const roundCalls = [...calls];
@@ -25576,6 +26355,11 @@ async function prepareAcpRoleEnvelope(options) {
25576
26355
  if (request.courtAttemptId === void 0) delete process.env.AK_ROLE_COURT_ATTEMPT;
25577
26356
  else process.env.AK_ROLE_COURT_ATTEMPT = request.courtAttemptId;
25578
26357
  runDirInjected = true;
26358
+ const terminating = tools.get(terminatingToolName);
26359
+ if (terminating === void 0) {
26360
+ throw new Error(`terminating tool not registered after activation: ${terminatingToolName}`);
26361
+ }
26362
+ const jsonSchema = terminatingToolJsonSchema(terminating.parameters);
25579
26363
  return {
25580
26364
  mcpServers: [{
25581
26365
  name: `ak-${request.activation.role}`,
@@ -25590,7 +26374,10 @@ async function prepareAcpRoleEnvelope(options) {
25590
26374
  prompt,
25591
26375
  abortSignal: hostAbort.signal,
25592
26376
  closeRound,
25593
- dispose
26377
+ dispose,
26378
+ jsonSchema,
26379
+ terminatingToolName,
26380
+ ingestStructuredOutput
25594
26381
  };
25595
26382
  } catch (error) {
25596
26383
  try {
@@ -25609,12 +26396,12 @@ async function prepareAcpRoleEnvelope(options) {
25609
26396
  // src/acp-host/seat-profile-soul.ts
25610
26397
  import { constants as constants3 } from "node:fs";
25611
26398
  import { access as access4, copyFile, lstat as lstat6, mkdir as mkdir6, readlink, symlink, unlink as unlink4 } from "node:fs/promises";
25612
- import { dirname as dirname18, join as join36, relative as relative3, resolve as resolve19 } from "node:path";
26399
+ import { dirname as dirname18, join as join37, relative as relative3, resolve as resolve19 } from "node:path";
25613
26400
  function seatProfileName(spec, role) {
25614
26401
  return `${spec.namePrefix}${role}`;
25615
26402
  }
25616
26403
  function packageRoleSoulPath(packageRoot, role) {
25617
- return join36(packageRoot, "souls", `${role}.md`);
26404
+ return join37(packageRoot, "souls", `${role}.md`);
25618
26405
  }
25619
26406
  async function pathExists(path) {
25620
26407
  try {
@@ -25631,16 +26418,16 @@ async function ensureSeatProfileSoul(options) {
25631
26418
  if (!await pathExists(soulTarget)) {
25632
26419
  throw new Error(`packaged role soul missing: ${soulTarget}`);
25633
26420
  }
25634
- const profilesRoot = join36(operatorHome, ...spec.profilesRootFromHome);
25635
- const profileDir = join36(profilesRoot, profileName);
26421
+ const profilesRoot = join37(operatorHome, ...spec.profilesRootFromHome);
26422
+ const profileDir = join37(profilesRoot, profileName);
25636
26423
  const hostRoot = dirname18(profilesRoot);
25637
- const soulPath = join36(profileDir, spec.soulFileName);
26424
+ const soulPath = join37(profileDir, spec.soulFileName);
25638
26425
  if (!await pathExists(profileDir)) {
25639
26426
  await mkdir6(profileDir, { recursive: true });
25640
26427
  for (const name of ["auth.json", ".env", "config.yaml"]) {
25641
- const source = join36(hostRoot, name);
26428
+ const source = join37(hostRoot, name);
25642
26429
  if (!await pathExists(source)) continue;
25643
- await copyFile(source, join36(profileDir, name));
26430
+ await copyFile(source, join37(profileDir, name));
25644
26431
  }
25645
26432
  } else {
25646
26433
  await mkdir6(profileDir, { recursive: true });
@@ -25667,9 +26454,9 @@ async function ensureSeatProfileSoul(options) {
25667
26454
 
25668
26455
  // src/acp-host/session-identity.ts
25669
26456
  import { mkdir as mkdir7, readFile as readFile19, rename, writeFile as writeFile9 } from "node:fs/promises";
25670
- import { dirname as dirname19, join as join37 } from "node:path";
26457
+ import { dirname as dirname19, join as join38 } from "node:path";
25671
26458
  function createAcpSessionIdentityAuthority(authority, sessionBindingFile) {
25672
- const bindingPath = (principal) => join37(authority.decode(principal).sessionDirectory, sessionBindingFile);
26459
+ const bindingPath = (principal) => join38(authority.decode(principal).sessionDirectory, sessionBindingFile);
25673
26460
  return {
25674
26461
  resolveSessionFile(principal) {
25675
26462
  return authority.decode(principal).sessionFile;