@akagilnc/pi-workflow-roles 0.1.3783 → 0.1.3789

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.
@@ -25,8 +25,14 @@ import {
25
25
  listPullRequestNumbersByTicket,
26
26
  type CollectorGitHubTransport,
27
27
  } from "./collector-github.ts";
28
+ import {
29
+ createCollectorHandbookStore,
30
+ resolveCollectorHandbookRoot,
31
+ type CollectorHandbookStore,
32
+ } from "./collector-handbook.ts";
28
33
  import {
29
34
  COLLECTOR_BIND_TARGET_TOOL,
35
+ COLLECTOR_HANDBOOK_WRITE_TOOL,
30
36
  COLLECTOR_OBSERVE_TOOL,
31
37
  COLLECTOR_OUTPUT_TOOL,
32
38
  COLLECTOR_READ_TOOL,
@@ -42,6 +48,7 @@ import {
42
48
  } from "./collector-receipt.ts";
43
49
  import {
44
50
  collectorBindTargetArgsSchema,
51
+ collectorHandbookWriteArgsSchema,
45
52
  collectorObserveArgsSchema,
46
53
  collectorOutputArgsSchema,
47
54
  collectorReadArgsSchema,
@@ -76,6 +83,7 @@ export class CollectorTargetBindError extends CorrectableSubmissionError {
76
83
 
77
84
  export {
78
85
  COLLECTOR_BIND_TARGET_TOOL,
86
+ COLLECTOR_HANDBOOK_WRITE_TOOL,
79
87
  COLLECTOR_OBSERVE_TOOL,
80
88
  COLLECTOR_OUTPUT_TOOL,
81
89
  COLLECTOR_READ_TOOL,
@@ -89,6 +97,7 @@ export const COLLECTOR_REQUIRED_TOOLS = [
89
97
  COLLECTOR_READ_TOOL,
90
98
  COLLECTOR_REQUEST_TOOL,
91
99
  COLLECTOR_WAIT_TOOL,
100
+ COLLECTOR_HANDBOOK_WRITE_TOOL,
92
101
  COLLECTOR_OUTPUT_TOOL,
93
102
  ] as const;
94
103
 
@@ -125,12 +134,14 @@ const readSchema = collectorReadArgsSchema;
125
134
  const requestSchema = collectorRequestArgsSchema;
126
135
  const waitSchema = collectorWaitArgsSchema;
127
136
  const bindSchema = collectorBindTargetArgsSchema;
137
+ const handbookWriteSchema = collectorHandbookWriteArgsSchema;
128
138
  const outputSchema = collectorOutputArgsSchema;
129
139
 
130
140
  type RequestParams = Static<typeof requestSchema>;
131
141
  type ReadParams = Static<typeof readSchema>;
132
142
  type WaitParams = Static<typeof waitSchema>;
133
143
  type BindParams = Static<typeof bindSchema>;
144
+ type HandbookWriteParams = Static<typeof handbookWriteSchema>;
134
145
  type OutputParams = Static<typeof outputSchema>;
135
146
 
136
147
  export type CollectorRoleDependencies = {
@@ -138,6 +149,8 @@ export type CollectorRoleDependencies = {
138
149
  createTransport(): CollectorGitHubTransport;
139
150
  createClock?(): CollectorClock;
140
151
  createLedger(config: CollectorConfigState, clock: CollectorClock, ctx: HostContext): CollectorLedger;
152
+ /** Optional packaged seed for first-use general handbook (#677). */
153
+ loadHandbookSeed?(): Promise<string>;
141
154
  };
142
155
 
143
156
  export type CollectorRoleHostActions = {
@@ -151,25 +164,51 @@ export type CollectorActivation = {
151
164
  ledger: CollectorLedger;
152
165
  transport: CollectorGitHubTransport;
153
166
  clock: CollectorClock;
167
+ handbook: CollectorHandbookStore;
168
+ /** Loaded at activate; refreshed after handbook write for same-session materials. */
169
+ handbookView: {
170
+ general: string;
171
+ repo: string;
172
+ generalSource: "book" | "seed" | "empty";
173
+ repoSource: "book" | "empty";
174
+ generalPath: string;
175
+ repoPath: string;
176
+ };
154
177
  };
155
178
 
156
179
  function buildMethodContext(activation: CollectorActivation): string {
157
180
  const pr = activation.ledger.config.prNumber;
158
- return [
181
+ const handbook = activation.handbookView;
182
+ const lines = [
159
183
  "<collector_method>",
160
184
  `host: github.com`,
161
185
  `repository: ${activation.repository.canonical}`,
162
- `prNumber: ${pr === undefined ? "unbound — call ak_collector_bind_target with the role-decided issue/PR before observe" : String(pr)}`,
186
+ `prNumber: ${pr === undefined ? "未绑定" : String(pr)}`,
163
187
  `requests: ${JSON.stringify(activation.manifest.requests.map((request) => ({ id: request.id })))}`,
188
+ `handbookGeneralSource: ${handbook.generalSource}`,
189
+ `handbookRepoSource: ${handbook.repoSource}`,
164
190
  "</collector_method>",
165
- ].join("\n");
191
+ ];
192
+ // Opaque working memory only — no directional instructions (ADR 0073).
193
+ // JSON + `<` → \u003c keeps bodies from forging the delivery close tag (#677).
194
+ if (handbook.general.length > 0 || handbook.repo.length > 0) {
195
+ const payload = JSON.stringify({
196
+ general: handbook.general,
197
+ repo: handbook.repo,
198
+ generalSource: handbook.generalSource,
199
+ repoSource: handbook.repoSource,
200
+ repository: activation.repository.canonical,
201
+ }).replaceAll("<", "\\u003c");
202
+ lines.push("", "<collector_handbook>", payload, "</collector_handbook>");
203
+ }
204
+ return lines.join("\n");
166
205
  }
167
206
 
168
207
  function parsePositiveTicket(raw: unknown, label: string): number | undefined {
169
208
  if (raw === undefined || raw === null) return undefined;
170
209
  if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) return raw;
171
210
  if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim())) return Number(raw.trim());
172
- throw new CollectorTargetBindError(`ak_collector_bind_target ${label} must be a positive safe integer`);
211
+ throw new CollectorTargetBindError(`通进司绑定 ${label} 须为正安全整数`);
173
212
  }
174
213
 
175
214
  /**
@@ -228,6 +267,26 @@ export function createCollectorRoleRuntime(
228
267
  ctx,
229
268
  );
230
269
 
270
+ // #677: handbook under admitted book topology (session path → books/<key>/collector-handbook).
271
+ const sessionPath = ctx.sessionManager?.getSessionFile?.()
272
+ ?? ctx.sessionManager?.getSessionDir?.();
273
+ if (typeof sessionPath !== "string" || sessionPath.length === 0) {
274
+ throw new Error("通进司手册要求 session 路径位于 books/<bookKey>/");
275
+ }
276
+ const placement = resolveCollectorHandbookRoot(sessionPath);
277
+ const seedGeneral = dependencies.loadHandbookSeed === undefined
278
+ ? undefined
279
+ : (await dependencies.loadHandbookSeed()).trim();
280
+ const handbook = createCollectorHandbookStore({
281
+ ledgerHome: placement.ledgerHome,
282
+ handbookRoot: placement.root,
283
+ repositoryCanonical: repository.canonical,
284
+ ...(seedGeneral === undefined || seedGeneral.length === 0
285
+ ? {}
286
+ : { seedGeneral }),
287
+ });
288
+ const handbookView = await handbook.read();
289
+
231
290
  return {
232
291
  soul,
233
292
  repository,
@@ -235,6 +294,8 @@ export function createCollectorRoleRuntime(
235
294
  ledger,
236
295
  transport,
237
296
  clock,
297
+ handbook,
298
+ handbookView,
238
299
  };
239
300
  },
240
301
 
@@ -297,7 +358,7 @@ export function createCollectorRoleRuntime(
297
358
  const issueNumber = parsePositiveTicket(params.issueNumber, "issueNumber");
298
359
  if (prNumber === undefined && issueNumber === undefined) {
299
360
  throw new CollectorTargetBindError(
300
- "ak_collector_bind_target requires role-decided prNumber and/or issueNumber",
361
+ "通进司绑定须由角色判定 prNumber 与/或 issueNumber",
301
362
  );
302
363
  }
303
364
 
@@ -310,18 +371,18 @@ export function createCollectorRoleRuntime(
310
371
  });
311
372
  if (associated.length === 0) {
312
373
  throw new CollectorTargetBindError(
313
- `no PR associated with issue #${issueNumber} in ${activation.repository.canonical}; pass an explicit --pr or a different issueNumber`,
374
+ `${activation.repository.canonical} 的 issue #${issueNumber} 无关联 PR;请改用明确 --pr 或其他 issueNumber`,
314
375
  );
315
376
  }
316
377
  if (associated.length > 1) {
317
378
  throw new CollectorTargetBindError(
318
- `multiple PRs associated with issue #${issueNumber}: ${associated.join(", ")}; pass an explicit prNumber or --pr`,
379
+ `issue #${issueNumber} 关联多个 PR:${associated.join("、")};请改用明确 prNumber 或 --pr`,
319
380
  );
320
381
  }
321
382
  const fromIssue = associated[0]!;
322
383
  if (prNumber !== undefined && prNumber !== fromIssue) {
323
384
  throw new CollectorTargetBindError(
324
- `prNumber ${prNumber} conflicts with issue #${issueNumber} association PR ${fromIssue}`,
385
+ `prNumber ${prNumber} 与 issue #${issueNumber} 关联 PR ${fromIssue} 冲突`,
325
386
  );
326
387
  }
327
388
  bound = fromIssue;
@@ -422,8 +483,8 @@ export function createCollectorRoleRuntime(
422
483
  pi.registerTool({
423
484
  name: COLLECTOR_REQUEST_TOOL,
424
485
  label: "通进司请求",
425
- description: "按配置请求体与关联标记,在所引最新快照 HEAD 发一次请求。",
426
- promptSnippet: "按配置发一次请求",
486
+ description: "在所引最新快照 HEAD 发一次请求。requestId 可取配置清单,或角色依手册/现场判定的稳定 id;后者须同时提供 body。",
487
+ promptSnippet: "发一次评审请求",
427
488
  parameters: requestSchema,
428
489
  async execute(toolCallId: string, params: RequestParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
429
490
  const activation = getActivation();
@@ -431,7 +492,11 @@ export function createCollectorRoleRuntime(
431
492
  try {
432
493
  activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
433
494
  const details = await activation.ledger.request(
434
- params,
495
+ {
496
+ requestId: params.requestId,
497
+ snapshotId: params.snapshotId,
498
+ ...(typeof params.body === "string" ? { body: params.body } : {}),
499
+ },
435
500
  activation.transport,
436
501
  activation.clock,
437
502
  signal,
@@ -451,6 +516,47 @@ export function createCollectorRoleRuntime(
451
516
  },
452
517
  });
453
518
 
519
+ pi.registerTool({
520
+ name: COLLECTOR_HANDBOOK_WRITE_TOOL,
521
+ label: "通进司手册写入",
522
+ description: "写入通用手册或当前仓库差异全文(整份替换)。",
523
+ promptSnippet: "更新 bot 手册",
524
+ parameters: handbookWriteSchema,
525
+ async execute(toolCallId: string, params: HandbookWriteParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
526
+ const activation = getActivation();
527
+ if (activation === undefined) throw new Error("通进司未激活");
528
+ try {
529
+ activation.ledger.beginOperational(COLLECTOR_HANDBOOK_WRITE_TOOL, toolCallId);
530
+ // scope/body shape authority = collectorHandbookWriteArgsSchema (host parameters).
531
+ const details = await activation.handbook.write(params.scope, params.body);
532
+ activation.handbookView = await activation.handbook.read();
533
+ activation.ledger.completeOperational(toolCallId);
534
+ return {
535
+ content: [{
536
+ type: "text" as const,
537
+ text: `手册已写入:${details.scope}`,
538
+ }],
539
+ details: {
540
+ scope: details.scope,
541
+ path: details.path,
542
+ byteLength: details.byteLength,
543
+ generalSource: activation.handbookView.generalSource,
544
+ repoSource: activation.handbookView.repoSource,
545
+ },
546
+ };
547
+ } catch (error) {
548
+ if (isCorrectableExecuteError(error)) throw error;
549
+ hostActions.failInfrastructure(error, ctx, toolCallId);
550
+ } finally {
551
+ try {
552
+ activation.ledger.completeOperational(toolCallId);
553
+ } catch {
554
+ // already completed or not begun
555
+ }
556
+ }
557
+ },
558
+ });
559
+
454
560
  pi.registerTool({
455
561
  name: COLLECTOR_WAIT_TOOL,
456
562
  label: "通进司等待",
@@ -4,9 +4,33 @@ import { openToolObject } from "./open-tool-schema.ts";
4
4
  import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
5
5
 
6
6
  export const collectorObserveArgsSchema = Type.Object({}, { additionalProperties: false });
7
+ /**
8
+ * Stable request identity: non-empty after trim, no leading/trailing whitespace.
9
+ * Host schema + ledger identity seam share this contract (#677 / D2 dedup).
10
+ */
11
+ export const COLLECTOR_REQUEST_ID_PATTERN = /^\S(?:.*\S)?$/;
7
12
  export const collectorRequestArgsSchema = Type.Object({
8
- requestId: Type.String({ minLength: 1, description: "配置请求身份" }),
13
+ requestId: Type.String({
14
+ minLength: 1,
15
+ pattern: COLLECTOR_REQUEST_ID_PATTERN.source,
16
+ description: "请求身份(配置 id 或角色判定的稳定 id;首尾无空白)",
17
+ }),
9
18
  snapshotId: Type.String({ minLength: 1, description: "最新留存观察快照" }),
19
+ body: Type.Optional(Type.String({
20
+ minLength: 1,
21
+ description: "角色判定的请求正文;request-manifest 未收录该 requestId 时必填",
22
+ })),
23
+ }, { additionalProperties: false });
24
+ /** UTF-8 byte ceiling per handbook body — keeps next activation materials inside context budget. */
25
+ export const COLLECTOR_HANDBOOK_MAX_BYTES = 64 * 1024;
26
+ export const collectorHandbookWriteArgsSchema = Type.Object({
27
+ scope: Type.Union([
28
+ Type.Literal("general"),
29
+ Type.Literal("repo"),
30
+ ], { description: "general=通用手册;repo=当前仓库差异" }),
31
+ body: Type.String({
32
+ description: `手册全文(opaque 工作记忆;整份替换;UTF-8 至多 ${COLLECTOR_HANDBOOK_MAX_BYTES} 字节)`,
33
+ }),
10
34
  }, { additionalProperties: false });
11
35
  export const collectorReadArgsSchema = Type.Object({
12
36
  evidenceId: Type.String({ minLength: 1, description: "observe 返回的材料证据 id(evidenceId)" }),
@@ -598,8 +598,8 @@ const COLLECTOR_OPTIONS = [
598
598
  repeatable: false,
599
599
  form: "option",
600
600
  description: {
601
- en: "Optional request manifest JSON path ({requests:[{id,body}]}).",
602
- zh: "可选 request manifest JSON 路径({requests:[{id,body}]})。",
601
+ en: "Optional request manifest JSON path ({requests:[{id,body}]}). Role may also decide request bodies from handbook/field activity without this file.",
602
+ zh: "可选 request manifest JSON 路径({requests:[{id,body}]})。角色亦可依手册/现场自行判定请求正文,不必依赖本文件。",
603
603
  },
604
604
  },
605
605
  ] as const satisfies readonly PublicOptionDefinition[];
@@ -611,6 +611,8 @@ export type RoleRuntimeDependencies = {
611
611
  /** Shared-seam issue-fetch capability for Reviewer Spec self-fetch (#343). */
612
612
  createReviewerIssueFetcher?(): ReviewerIssueFetcher;
613
613
  loadCollectorSoul?(): Promise<string>;
614
+ /** #677: optional packaged seed for first-use general bot handbook. */
615
+ loadCollectorHandbookSeed?(): Promise<string>;
614
616
  createCollectorTransport?(): CollectorGitHubTransport;
615
617
  loadDoctorSoul?(): Promise<string>;
616
618
  loadNotarySoul?(): Promise<string>;
@@ -1717,6 +1719,9 @@ export function createRoleRuntimeExtension(
1717
1719
  ...(dependencies.createCollectorClock === undefined
1718
1720
  ? {}
1719
1721
  : { createClock: dependencies.createCollectorClock }),
1722
+ ...(dependencies.loadCollectorHandbookSeed === undefined
1723
+ ? {}
1724
+ : { loadHandbookSeed: dependencies.loadCollectorHandbookSeed }),
1720
1725
  },
1721
1726
  hostActions,
1722
1727
  );