@akagilnc/pi-workflow-roles 0.1.3783 → 0.1.3795
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.
- package/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/dist/acp-host/production-host.js +681 -269
- package/dist/atomic-write.js +23 -0
- package/dist/collector-evidence.js +4 -1
- package/dist/collector-github.js +1 -0
- package/dist/collector-handbook.js +142 -0
- package/dist/collector-identity.js +11 -0
- package/dist/collector-ledger.js +109 -63
- package/dist/collector-role.js +182 -18
- package/dist/collector-tool-schemas.js +40 -3
- package/dist/headless-host/production-host.js +682 -270
- package/dist/pi/role-turn-host.js +1 -0
- package/dist/public-cli/invocation.js +15 -0
- package/dist/public-cli/main.js +45 -77
- package/dist/public-cli/option-definitions.js +16 -2
- package/dist/public-cli/run-lifecycle.js +8 -0
- package/dist/role-runtime.js +3 -0
- package/extensions/role-runtime.ts +2 -0
- package/package.json +1 -1
- package/resources/collector-bot-handbook.md +33 -0
- package/src/acp-host/production-host.ts +4 -0
- package/src/acp-host/role-envelope.ts +1 -0
- package/src/collector-evidence.ts +6 -1
- package/src/collector-github.ts +3 -0
- package/src/collector-handbook.ts +194 -0
- package/src/collector-identity.ts +14 -0
- package/src/collector-ledger.ts +132 -80
- package/src/collector-role.ts +198 -14
- package/src/collector-tool-schemas.ts +42 -3
- package/src/host-contracts.ts +2 -0
- package/src/pi/role-turn-host.ts +1 -0
- package/src/public-cli/collector-run.ts +2 -0
- package/src/public-cli/invocation.ts +21 -0
- package/src/public-cli/option-definitions.ts +16 -2
- package/src/public-cli/run-lifecycle.ts +10 -0
- package/src/role-runtime.ts +5 -0
package/src/collector-role.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type CollectorRepository,
|
|
18
18
|
} from "./collector-config.ts";
|
|
19
19
|
import {
|
|
20
|
+
COLLECTOR_DEFAULT_WAIT_WINDOW_MS,
|
|
20
21
|
createSystemCollectorClock,
|
|
21
22
|
type CollectorClock,
|
|
22
23
|
} from "./collector-evidence.ts";
|
|
@@ -25,9 +26,16 @@ import {
|
|
|
25
26
|
listPullRequestNumbersByTicket,
|
|
26
27
|
type CollectorGitHubTransport,
|
|
27
28
|
} from "./collector-github.ts";
|
|
29
|
+
import {
|
|
30
|
+
createCollectorHandbookStore,
|
|
31
|
+
resolveCollectorHandbookRoot,
|
|
32
|
+
type CollectorHandbookStore,
|
|
33
|
+
} from "./collector-handbook.ts";
|
|
28
34
|
import {
|
|
29
35
|
COLLECTOR_BIND_TARGET_TOOL,
|
|
36
|
+
COLLECTOR_HANDBOOK_WRITE_TOOL,
|
|
30
37
|
COLLECTOR_OBSERVE_TOOL,
|
|
38
|
+
COLLECTOR_OPEN_WAIT_WINDOW_TOOL,
|
|
31
39
|
COLLECTOR_OUTPUT_TOOL,
|
|
32
40
|
COLLECTOR_READ_TOOL,
|
|
33
41
|
COLLECTOR_REQUEST_TOOL,
|
|
@@ -42,7 +50,9 @@ import {
|
|
|
42
50
|
} from "./collector-receipt.ts";
|
|
43
51
|
import {
|
|
44
52
|
collectorBindTargetArgsSchema,
|
|
53
|
+
collectorHandbookWriteArgsSchema,
|
|
45
54
|
collectorObserveArgsSchema,
|
|
55
|
+
collectorOpenWaitWindowArgsSchema,
|
|
46
56
|
collectorOutputArgsSchema,
|
|
47
57
|
collectorReadArgsSchema,
|
|
48
58
|
collectorRequestArgsSchema,
|
|
@@ -76,7 +86,9 @@ export class CollectorTargetBindError extends CorrectableSubmissionError {
|
|
|
76
86
|
|
|
77
87
|
export {
|
|
78
88
|
COLLECTOR_BIND_TARGET_TOOL,
|
|
89
|
+
COLLECTOR_HANDBOOK_WRITE_TOOL,
|
|
79
90
|
COLLECTOR_OBSERVE_TOOL,
|
|
91
|
+
COLLECTOR_OPEN_WAIT_WINDOW_TOOL,
|
|
80
92
|
COLLECTOR_OUTPUT_TOOL,
|
|
81
93
|
COLLECTOR_READ_TOOL,
|
|
82
94
|
COLLECTOR_REQUEST_TOOL,
|
|
@@ -88,7 +100,9 @@ export const COLLECTOR_REQUIRED_TOOLS = [
|
|
|
88
100
|
COLLECTOR_OBSERVE_TOOL,
|
|
89
101
|
COLLECTOR_READ_TOOL,
|
|
90
102
|
COLLECTOR_REQUEST_TOOL,
|
|
103
|
+
COLLECTOR_OPEN_WAIT_WINDOW_TOOL,
|
|
91
104
|
COLLECTOR_WAIT_TOOL,
|
|
105
|
+
COLLECTOR_HANDBOOK_WRITE_TOOL,
|
|
92
106
|
COLLECTOR_OUTPUT_TOOL,
|
|
93
107
|
] as const;
|
|
94
108
|
|
|
@@ -118,26 +132,50 @@ export const COLLECTOR_TRANSPORT_FLAGS = Object.freeze([
|
|
|
118
132
|
type: "string" as const,
|
|
119
133
|
}),
|
|
120
134
|
}),
|
|
135
|
+
Object.freeze({
|
|
136
|
+
name: "ak-collector-wait-ms",
|
|
137
|
+
definition: Object.freeze({
|
|
138
|
+
description:
|
|
139
|
+
`Collector wait-window duration in milliseconds (default ${COLLECTOR_DEFAULT_WAIT_WINDOW_MS}). Caller-configurable; opens at a work step, not session start (#678 D4).`,
|
|
140
|
+
type: "string" as const,
|
|
141
|
+
}),
|
|
142
|
+
}),
|
|
121
143
|
] as const);
|
|
122
144
|
|
|
123
145
|
const observeSchema = collectorObserveArgsSchema;
|
|
124
146
|
const readSchema = collectorReadArgsSchema;
|
|
125
147
|
const requestSchema = collectorRequestArgsSchema;
|
|
148
|
+
const openWaitWindowSchema = collectorOpenWaitWindowArgsSchema;
|
|
126
149
|
const waitSchema = collectorWaitArgsSchema;
|
|
127
150
|
const bindSchema = collectorBindTargetArgsSchema;
|
|
151
|
+
const handbookWriteSchema = collectorHandbookWriteArgsSchema;
|
|
128
152
|
const outputSchema = collectorOutputArgsSchema;
|
|
129
153
|
|
|
130
154
|
type RequestParams = Static<typeof requestSchema>;
|
|
131
155
|
type ReadParams = Static<typeof readSchema>;
|
|
156
|
+
type OpenWaitWindowParams = Static<typeof openWaitWindowSchema>;
|
|
132
157
|
type WaitParams = Static<typeof waitSchema>;
|
|
133
158
|
type BindParams = Static<typeof bindSchema>;
|
|
159
|
+
type HandbookWriteParams = Static<typeof handbookWriteSchema>;
|
|
134
160
|
type OutputParams = Static<typeof outputSchema>;
|
|
135
161
|
|
|
162
|
+
function parseWaitWindowMsFlag(raw: unknown): number {
|
|
163
|
+
if (raw === undefined || raw === null || raw === "") return COLLECTOR_DEFAULT_WAIT_WINDOW_MS;
|
|
164
|
+
if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) return raw;
|
|
165
|
+
if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim())) {
|
|
166
|
+
const value = Number(raw.trim());
|
|
167
|
+
if (Number.isSafeInteger(value) && value >= 1) return value;
|
|
168
|
+
}
|
|
169
|
+
throw new Error("Collector --ak-collector-wait-ms must be a positive safe-integer millisecond string");
|
|
170
|
+
}
|
|
171
|
+
|
|
136
172
|
export type CollectorRoleDependencies = {
|
|
137
173
|
loadSoul(): Promise<string>;
|
|
138
174
|
createTransport(): CollectorGitHubTransport;
|
|
139
175
|
createClock?(): CollectorClock;
|
|
140
176
|
createLedger(config: CollectorConfigState, clock: CollectorClock, ctx: HostContext): CollectorLedger;
|
|
177
|
+
/** Optional packaged seed for first-use general handbook (#677). */
|
|
178
|
+
loadHandbookSeed?(): Promise<string>;
|
|
141
179
|
};
|
|
142
180
|
|
|
143
181
|
export type CollectorRoleHostActions = {
|
|
@@ -151,25 +189,52 @@ export type CollectorActivation = {
|
|
|
151
189
|
ledger: CollectorLedger;
|
|
152
190
|
transport: CollectorGitHubTransport;
|
|
153
191
|
clock: CollectorClock;
|
|
192
|
+
handbook: CollectorHandbookStore;
|
|
193
|
+
/** Loaded at activate; refreshed after handbook write for same-session materials. */
|
|
194
|
+
handbookView: {
|
|
195
|
+
general: string;
|
|
196
|
+
repo: string;
|
|
197
|
+
generalSource: "book" | "seed" | "empty";
|
|
198
|
+
repoSource: "book" | "empty";
|
|
199
|
+
generalPath: string;
|
|
200
|
+
repoPath: string;
|
|
201
|
+
};
|
|
154
202
|
};
|
|
155
203
|
|
|
156
204
|
function buildMethodContext(activation: CollectorActivation): string {
|
|
157
205
|
const pr = activation.ledger.config.prNumber;
|
|
158
|
-
|
|
206
|
+
const handbook = activation.handbookView;
|
|
207
|
+
const lines = [
|
|
159
208
|
"<collector_method>",
|
|
160
209
|
`host: github.com`,
|
|
161
210
|
`repository: ${activation.repository.canonical}`,
|
|
162
|
-
`prNumber: ${pr === undefined ? "
|
|
211
|
+
`prNumber: ${pr === undefined ? "未绑定" : String(pr)}`,
|
|
212
|
+
`waitWindowMs: ${String(activation.ledger.config.waitWindowMs)}`,
|
|
163
213
|
`requests: ${JSON.stringify(activation.manifest.requests.map((request) => ({ id: request.id })))}`,
|
|
214
|
+
`handbookGeneralSource: ${handbook.generalSource}`,
|
|
215
|
+
`handbookRepoSource: ${handbook.repoSource}`,
|
|
164
216
|
"</collector_method>",
|
|
165
|
-
]
|
|
217
|
+
];
|
|
218
|
+
// Opaque working memory only — no directional instructions (ADR 0073).
|
|
219
|
+
// JSON + `<` → \u003c keeps bodies from forging the delivery close tag (#677).
|
|
220
|
+
if (handbook.general.length > 0 || handbook.repo.length > 0) {
|
|
221
|
+
const payload = JSON.stringify({
|
|
222
|
+
general: handbook.general,
|
|
223
|
+
repo: handbook.repo,
|
|
224
|
+
generalSource: handbook.generalSource,
|
|
225
|
+
repoSource: handbook.repoSource,
|
|
226
|
+
repository: activation.repository.canonical,
|
|
227
|
+
}).replaceAll("<", "\\u003c");
|
|
228
|
+
lines.push("", "<collector_handbook>", payload, "</collector_handbook>");
|
|
229
|
+
}
|
|
230
|
+
return lines.join("\n");
|
|
166
231
|
}
|
|
167
232
|
|
|
168
233
|
function parsePositiveTicket(raw: unknown, label: string): number | undefined {
|
|
169
234
|
if (raw === undefined || raw === null) return undefined;
|
|
170
235
|
if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) return raw;
|
|
171
236
|
if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim())) return Number(raw.trim());
|
|
172
|
-
throw new CollectorTargetBindError(
|
|
237
|
+
throw new CollectorTargetBindError(`通进司绑定 ${label} 须为正安全整数`);
|
|
173
238
|
}
|
|
174
239
|
|
|
175
240
|
/**
|
|
@@ -205,6 +270,7 @@ export function createCollectorRoleRuntime(
|
|
|
205
270
|
const repoFlag = pi.getFlag("ak-collector-repo");
|
|
206
271
|
const prFlag = pi.getFlag("ak-collector-pr");
|
|
207
272
|
const requestManifestFlag = pi.getFlag("ak-collector-request-manifest");
|
|
273
|
+
const waitMsFlag = pi.getFlag("ak-collector-wait-ms");
|
|
208
274
|
if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
|
|
209
275
|
throw new Error("Collector requires --ak-collector-repo");
|
|
210
276
|
}
|
|
@@ -219,15 +285,36 @@ export function createCollectorRoleRuntime(
|
|
|
219
285
|
const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0
|
|
220
286
|
? await loadCollectorManifest(requestManifestFlag)
|
|
221
287
|
: emptyCollectorManifest();
|
|
288
|
+
const waitWindowMs = parseWaitWindowMsFlag(waitMsFlag);
|
|
222
289
|
|
|
223
290
|
const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
|
|
224
291
|
const transport = dependencies.createTransport();
|
|
225
292
|
const ledger = dependencies.createLedger(
|
|
226
|
-
{ repository, prNumber, manifest },
|
|
293
|
+
{ repository, prNumber, manifest, waitWindowMs },
|
|
227
294
|
clock,
|
|
228
295
|
ctx,
|
|
229
296
|
);
|
|
230
297
|
|
|
298
|
+
// #677: handbook under admitted book topology (session path → books/<key>/collector-handbook).
|
|
299
|
+
const sessionPath = ctx.sessionManager?.getSessionFile?.()
|
|
300
|
+
?? ctx.sessionManager?.getSessionDir?.();
|
|
301
|
+
if (typeof sessionPath !== "string" || sessionPath.length === 0) {
|
|
302
|
+
throw new Error("通进司手册要求 session 路径位于 books/<bookKey>/");
|
|
303
|
+
}
|
|
304
|
+
const placement = resolveCollectorHandbookRoot(sessionPath);
|
|
305
|
+
const seedGeneral = dependencies.loadHandbookSeed === undefined
|
|
306
|
+
? undefined
|
|
307
|
+
: (await dependencies.loadHandbookSeed()).trim();
|
|
308
|
+
const handbook = createCollectorHandbookStore({
|
|
309
|
+
ledgerHome: placement.ledgerHome,
|
|
310
|
+
handbookRoot: placement.root,
|
|
311
|
+
repositoryCanonical: repository.canonical,
|
|
312
|
+
...(seedGeneral === undefined || seedGeneral.length === 0
|
|
313
|
+
? {}
|
|
314
|
+
: { seedGeneral }),
|
|
315
|
+
});
|
|
316
|
+
const handbookView = await handbook.read();
|
|
317
|
+
|
|
231
318
|
return {
|
|
232
319
|
soul,
|
|
233
320
|
repository,
|
|
@@ -235,6 +322,8 @@ export function createCollectorRoleRuntime(
|
|
|
235
322
|
ledger,
|
|
236
323
|
transport,
|
|
237
324
|
clock,
|
|
325
|
+
handbook,
|
|
326
|
+
handbookView,
|
|
238
327
|
};
|
|
239
328
|
},
|
|
240
329
|
|
|
@@ -297,7 +386,7 @@ export function createCollectorRoleRuntime(
|
|
|
297
386
|
const issueNumber = parsePositiveTicket(params.issueNumber, "issueNumber");
|
|
298
387
|
if (prNumber === undefined && issueNumber === undefined) {
|
|
299
388
|
throw new CollectorTargetBindError(
|
|
300
|
-
"
|
|
389
|
+
"通进司绑定须由角色判定 prNumber 与/或 issueNumber",
|
|
301
390
|
);
|
|
302
391
|
}
|
|
303
392
|
|
|
@@ -310,18 +399,18 @@ export function createCollectorRoleRuntime(
|
|
|
310
399
|
});
|
|
311
400
|
if (associated.length === 0) {
|
|
312
401
|
throw new CollectorTargetBindError(
|
|
313
|
-
|
|
402
|
+
`${activation.repository.canonical} 的 issue #${issueNumber} 无关联 PR;请改用明确 --pr 或其他 issueNumber`,
|
|
314
403
|
);
|
|
315
404
|
}
|
|
316
405
|
if (associated.length > 1) {
|
|
317
406
|
throw new CollectorTargetBindError(
|
|
318
|
-
`
|
|
407
|
+
`issue #${issueNumber} 关联多个 PR:${associated.join("、")};请改用明确 prNumber 或 --pr`,
|
|
319
408
|
);
|
|
320
409
|
}
|
|
321
410
|
const fromIssue = associated[0]!;
|
|
322
411
|
if (prNumber !== undefined && prNumber !== fromIssue) {
|
|
323
412
|
throw new CollectorTargetBindError(
|
|
324
|
-
`prNumber ${prNumber}
|
|
413
|
+
`prNumber ${prNumber} 与 issue #${issueNumber} 关联 PR ${fromIssue} 冲突`,
|
|
325
414
|
);
|
|
326
415
|
}
|
|
327
416
|
bound = fromIssue;
|
|
@@ -422,8 +511,8 @@ export function createCollectorRoleRuntime(
|
|
|
422
511
|
pi.registerTool({
|
|
423
512
|
name: COLLECTOR_REQUEST_TOOL,
|
|
424
513
|
label: "通进司请求",
|
|
425
|
-
description: "
|
|
426
|
-
promptSnippet: "
|
|
514
|
+
description: "在所引最新快照 HEAD 发一次请求。requestId 可取配置清单,或角色依手册/现场判定的稳定 id;后者须同时提供 body。",
|
|
515
|
+
promptSnippet: "发一次评审请求",
|
|
427
516
|
parameters: requestSchema,
|
|
428
517
|
async execute(toolCallId: string, params: RequestParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
|
|
429
518
|
const activation = getActivation();
|
|
@@ -431,7 +520,11 @@ export function createCollectorRoleRuntime(
|
|
|
431
520
|
try {
|
|
432
521
|
activation.ledger.beginOperational(COLLECTOR_REQUEST_TOOL, toolCallId);
|
|
433
522
|
const details = await activation.ledger.request(
|
|
434
|
-
|
|
523
|
+
{
|
|
524
|
+
requestId: params.requestId,
|
|
525
|
+
snapshotId: params.snapshotId,
|
|
526
|
+
...(typeof params.body === "string" ? { body: params.body } : {}),
|
|
527
|
+
},
|
|
435
528
|
activation.transport,
|
|
436
529
|
activation.clock,
|
|
437
530
|
signal,
|
|
@@ -451,11 +544,101 @@ export function createCollectorRoleRuntime(
|
|
|
451
544
|
},
|
|
452
545
|
});
|
|
453
546
|
|
|
547
|
+
pi.registerTool({
|
|
548
|
+
name: COLLECTOR_HANDBOOK_WRITE_TOOL,
|
|
549
|
+
label: "通进司手册写入",
|
|
550
|
+
description: "写入通用手册或当前仓库差异全文(整份替换)。",
|
|
551
|
+
promptSnippet: "更新 bot 手册",
|
|
552
|
+
parameters: handbookWriteSchema,
|
|
553
|
+
async execute(toolCallId: string, params: HandbookWriteParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
|
|
554
|
+
const activation = getActivation();
|
|
555
|
+
if (activation === undefined) throw new Error("通进司未激活");
|
|
556
|
+
try {
|
|
557
|
+
activation.ledger.beginOperational(COLLECTOR_HANDBOOK_WRITE_TOOL, toolCallId);
|
|
558
|
+
// scope/body shape authority = collectorHandbookWriteArgsSchema (host parameters).
|
|
559
|
+
const details = await activation.handbook.write(params.scope, params.body);
|
|
560
|
+
activation.handbookView = await activation.handbook.read();
|
|
561
|
+
activation.ledger.completeOperational(toolCallId);
|
|
562
|
+
return {
|
|
563
|
+
content: [{
|
|
564
|
+
type: "text" as const,
|
|
565
|
+
text: `手册已写入:${details.scope}`,
|
|
566
|
+
}],
|
|
567
|
+
details: {
|
|
568
|
+
scope: details.scope,
|
|
569
|
+
path: details.path,
|
|
570
|
+
byteLength: details.byteLength,
|
|
571
|
+
generalSource: activation.handbookView.generalSource,
|
|
572
|
+
repoSource: activation.handbookView.repoSource,
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
} catch (error) {
|
|
576
|
+
if (isCorrectableExecuteError(error)) throw error;
|
|
577
|
+
hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
578
|
+
} finally {
|
|
579
|
+
try {
|
|
580
|
+
activation.ledger.completeOperational(toolCallId);
|
|
581
|
+
} catch {
|
|
582
|
+
// already completed or not begun
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
pi.registerTool({
|
|
589
|
+
name: COLLECTOR_OPEN_WAIT_WINDOW_TOOL,
|
|
590
|
+
label: "通进司开启等待窗",
|
|
591
|
+
description:
|
|
592
|
+
"在工作步骤上开启等待窗:新建并自动触发的 PR 传创建成功时刻;已有 PR 在触发阶段结束后省略 startedAt(=现在)。窗长由使用方配置,默认十分钟;开启后不因 PR 更新重置。",
|
|
593
|
+
promptSnippet: "按工作步骤开启等待窗",
|
|
594
|
+
parameters: openWaitWindowSchema,
|
|
595
|
+
async execute(toolCallId: string, params: OpenWaitWindowParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
|
|
596
|
+
const activation = getActivation();
|
|
597
|
+
if (activation === undefined) throw new Error("通进司未激活");
|
|
598
|
+
try {
|
|
599
|
+
activation.ledger.beginOperational(COLLECTOR_OPEN_WAIT_WINDOW_TOOL, toolCallId);
|
|
600
|
+
let startedAt: Date | undefined;
|
|
601
|
+
if (typeof params.startedAt === "string" && params.startedAt.trim().length > 0) {
|
|
602
|
+
const parsed = new Date(params.startedAt);
|
|
603
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
604
|
+
throw new Error("通进司等待窗 startedAt 须为有效 ISO 时间");
|
|
605
|
+
}
|
|
606
|
+
startedAt = parsed;
|
|
607
|
+
}
|
|
608
|
+
activation.ledger.openWaitWindow(
|
|
609
|
+
activation.clock,
|
|
610
|
+
startedAt === undefined ? undefined : { startedAt },
|
|
611
|
+
);
|
|
612
|
+
activation.ledger.completeOperational(toolCallId);
|
|
613
|
+
return {
|
|
614
|
+
content: [{
|
|
615
|
+
type: "text" as const,
|
|
616
|
+
text: "等待窗已开启",
|
|
617
|
+
}],
|
|
618
|
+
details: {
|
|
619
|
+
activationTime: activation.ledger.activationTime?.toISOString(),
|
|
620
|
+
deadlineTime: activation.ledger.deadlineTime?.toISOString(),
|
|
621
|
+
waitWindowMs: activation.ledger.config.waitWindowMs,
|
|
622
|
+
},
|
|
623
|
+
};
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (isCorrectableExecuteError(error)) throw error;
|
|
626
|
+
hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
627
|
+
} finally {
|
|
628
|
+
try {
|
|
629
|
+
activation.ledger.completeOperational(toolCallId);
|
|
630
|
+
} catch {
|
|
631
|
+
// already completed or not begun
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
|
|
454
637
|
pi.registerTool({
|
|
455
638
|
name: COLLECTOR_WAIT_TOOL,
|
|
456
639
|
label: "通进司等待",
|
|
457
|
-
description: "
|
|
458
|
-
promptSnippet: "
|
|
640
|
+
description: "再观察前等待;实际睡眠不超过剩余等待窗。",
|
|
641
|
+
promptSnippet: "等待窗内等待",
|
|
459
642
|
parameters: waitSchema,
|
|
460
643
|
async execute(toolCallId: string, params: WaitParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
|
|
461
644
|
const activation = getActivation();
|
|
@@ -476,6 +659,7 @@ export function createCollectorRoleRuntime(
|
|
|
476
659
|
details,
|
|
477
660
|
};
|
|
478
661
|
} catch (error) {
|
|
662
|
+
if (isCorrectableExecuteError(error)) throw error;
|
|
479
663
|
hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
480
664
|
}
|
|
481
665
|
},
|
|
@@ -1,18 +1,56 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
|
-
import { COLLECTOR_ELIGIBILITY_MS } from "./collector-evidence.ts";
|
|
3
2
|
import { openToolObject } from "./open-tool-schema.ts";
|
|
4
3
|
import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
|
|
5
4
|
|
|
6
5
|
export const collectorObserveArgsSchema = Type.Object({}, { additionalProperties: false });
|
|
6
|
+
/**
|
|
7
|
+
* Stable request identity: non-empty after trim, no leading/trailing whitespace.
|
|
8
|
+
* Host schema + ledger identity seam share this contract (#677 / D2 dedup).
|
|
9
|
+
*/
|
|
10
|
+
export const COLLECTOR_REQUEST_ID_PATTERN = /^\S(?:.*\S)?$/;
|
|
7
11
|
export const collectorRequestArgsSchema = Type.Object({
|
|
8
|
-
requestId: Type.String({
|
|
12
|
+
requestId: Type.String({
|
|
13
|
+
minLength: 1,
|
|
14
|
+
pattern: COLLECTOR_REQUEST_ID_PATTERN.source,
|
|
15
|
+
description: "请求身份(配置 id 或角色判定的稳定 id;首尾无空白)",
|
|
16
|
+
}),
|
|
9
17
|
snapshotId: Type.String({ minLength: 1, description: "最新留存观察快照" }),
|
|
18
|
+
body: Type.Optional(Type.String({
|
|
19
|
+
minLength: 1,
|
|
20
|
+
description: "角色判定的请求正文;request-manifest 未收录该 requestId 时必填",
|
|
21
|
+
})),
|
|
22
|
+
}, { additionalProperties: false });
|
|
23
|
+
/** UTF-8 byte ceiling per handbook body — keeps next activation materials inside context budget. */
|
|
24
|
+
export const COLLECTOR_HANDBOOK_MAX_BYTES = 64 * 1024;
|
|
25
|
+
export const collectorHandbookWriteArgsSchema = Type.Object({
|
|
26
|
+
scope: Type.Union([
|
|
27
|
+
Type.Literal("general"),
|
|
28
|
+
Type.Literal("repo"),
|
|
29
|
+
], { description: "general=通用手册;repo=当前仓库差异" }),
|
|
30
|
+
body: Type.String({
|
|
31
|
+
description: `手册全文(opaque 工作记忆;整份替换;UTF-8 至多 ${COLLECTOR_HANDBOOK_MAX_BYTES} 字节)`,
|
|
32
|
+
}),
|
|
10
33
|
}, { additionalProperties: false });
|
|
11
34
|
export const collectorReadArgsSchema = Type.Object({
|
|
12
35
|
evidenceId: Type.String({ minLength: 1, description: "observe 返回的材料证据 id(evidenceId)" }),
|
|
13
36
|
}, { additionalProperties: false });
|
|
14
37
|
export const collectorWaitArgsSchema = Type.Object({
|
|
15
|
-
durationMs: Type.Integer({
|
|
38
|
+
durationMs: Type.Integer({
|
|
39
|
+
minimum: 1,
|
|
40
|
+
description: "等待毫秒;实际睡眠不超过剩余等待窗(#678;无包内单次任意上限)",
|
|
41
|
+
}),
|
|
42
|
+
}, { additionalProperties: false });
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* #678 D4: open the wait window at a work step.
|
|
46
|
+
* Omit startedAt for existing-PR trigger-phase end (= now).
|
|
47
|
+
* Pass PR creation success time for new-PR auto-trigger rounds.
|
|
48
|
+
*/
|
|
49
|
+
export const collectorOpenWaitWindowArgsSchema = Type.Object({
|
|
50
|
+
startedAt: Type.Optional(Type.String({
|
|
51
|
+
minLength: 1,
|
|
52
|
+
description: "等待窗起点 ISO 时间;新建 PR 用创建成功时刻;省略=现在(触发阶段结束)",
|
|
53
|
+
})),
|
|
16
54
|
}, { additionalProperties: false });
|
|
17
55
|
|
|
18
56
|
/**
|
|
@@ -88,5 +126,6 @@ export type CollectorObserveArgs = Static<typeof collectorObserveArgsSchema>;
|
|
|
88
126
|
export type CollectorRequestArgs = Static<typeof collectorRequestArgsSchema>;
|
|
89
127
|
export type CollectorReadArgs = Static<typeof collectorReadArgsSchema>;
|
|
90
128
|
export type CollectorWaitArgs = Static<typeof collectorWaitArgsSchema>;
|
|
129
|
+
export type CollectorOpenWaitWindowArgs = Static<typeof collectorOpenWaitWindowArgsSchema>;
|
|
91
130
|
export type CollectorBindTargetArgs = Static<typeof collectorBindTargetArgsSchema>;
|
|
92
131
|
export type CollectorOutputArgs = Static<typeof collectorOutputArgsSchema>;
|
package/src/host-contracts.ts
CHANGED
|
@@ -106,6 +106,8 @@ export type RoleTurnActivation =
|
|
|
106
106
|
/** Bound PR when known at admission; omit when role will bind from materials. */
|
|
107
107
|
readonly pr?: string;
|
|
108
108
|
readonly requestManifestPath?: string;
|
|
109
|
+
/** Wait-window ms as decimal string (#678 D4); omit → package default 10 minutes. */
|
|
110
|
+
readonly waitMs?: string;
|
|
109
111
|
}
|
|
110
112
|
| { readonly role: "doctor"; readonly casePath: string }
|
|
111
113
|
| {
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -116,6 +116,7 @@ function buildActivationFlagArgs(activation: RoleTurnActivation): string[] {
|
|
|
116
116
|
...(activation.requestManifestPath === undefined
|
|
117
117
|
? []
|
|
118
118
|
: ["--ak-collector-request-manifest", activation.requestManifestPath]),
|
|
119
|
+
...(activation.waitMs === undefined ? [] : ["--ak-collector-wait-ms", activation.waitMs]),
|
|
119
120
|
];
|
|
120
121
|
case "doctor":
|
|
121
122
|
return ["--ak-role", "doctor", "--ak-doctor-case", activation.casePath];
|
|
@@ -54,6 +54,7 @@ export function buildCollectorTurnRequest(
|
|
|
54
54
|
// #676 A: omit pr when unbound — role binds via ak_collector_bind_target.
|
|
55
55
|
...(admitted.prNumber === undefined ? {} : { pr: String(admitted.prNumber) }),
|
|
56
56
|
...(admitted.requestManifestPath === undefined ? {} : { requestManifestPath: admitted.requestManifestPath }),
|
|
57
|
+
...(admitted.waitWindowMs === undefined ? {} : { waitMs: String(admitted.waitWindowMs) }),
|
|
57
58
|
},
|
|
58
59
|
},
|
|
59
60
|
options,
|
|
@@ -83,6 +84,7 @@ export async function runPublicCollector(
|
|
|
83
84
|
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
84
85
|
...(parsed.repo === undefined ? {} : { repo: parsed.repo }),
|
|
85
86
|
...(parsed.requestManifestPath === undefined ? {} : { requestManifestPath: parsed.requestManifestPath }),
|
|
87
|
+
...(parsed.waitWindowMs === undefined ? {} : { waitWindowMs: parsed.waitWindowMs }),
|
|
86
88
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
87
89
|
...(env.model === undefined ? {} : { model: env.model }),
|
|
88
90
|
});
|
|
@@ -178,6 +178,8 @@ export type AdmittedCollectorInvocation = AdmittedRoleInvocationBase & {
|
|
|
178
178
|
readonly repository: CollectorRepository;
|
|
179
179
|
readonly requestManifestPath?: string;
|
|
180
180
|
readonly manifestDigest: string;
|
|
181
|
+
/** Wait-window ms (#678 D4); default applied at role activate when omitted. */
|
|
182
|
+
readonly waitWindowMs?: number;
|
|
181
183
|
};
|
|
182
184
|
|
|
183
185
|
export type AdmittedDoctorInvocation = AdmittedRoleInvocationBase & {
|
|
@@ -727,6 +729,8 @@ export type ParseCollectorArgvResult = {
|
|
|
727
729
|
project?: string;
|
|
728
730
|
repo?: string;
|
|
729
731
|
requestManifestPath?: string;
|
|
732
|
+
/** Optional wait-window ms (#678 D4). */
|
|
733
|
+
waitWindowMs?: number;
|
|
730
734
|
};
|
|
731
735
|
|
|
732
736
|
export type ParseDoctorArgvResult = {
|
|
@@ -1722,6 +1726,7 @@ export function parseCollectorArgv(args: readonly string[]): ParseCollectorArgvR
|
|
|
1722
1726
|
let repo: string | undefined;
|
|
1723
1727
|
let prNumber: number | undefined;
|
|
1724
1728
|
let requestManifestPath: string | undefined;
|
|
1729
|
+
let waitWindowMs: number | undefined;
|
|
1725
1730
|
const positional: string[] = [];
|
|
1726
1731
|
const tokens = [...args];
|
|
1727
1732
|
const definitions = roleOptions("collector");
|
|
@@ -1754,6 +1759,17 @@ export function parseCollectorArgv(args: readonly string[]): ParseCollectorArgvR
|
|
|
1754
1759
|
requestManifestPath = requireOptionPath(taken.def.canonical, taken.value);
|
|
1755
1760
|
continue;
|
|
1756
1761
|
}
|
|
1762
|
+
if (taken.def.id === "wait-ms") {
|
|
1763
|
+
if (taken.value === undefined || !/^[1-9]\d*$/.test(taken.value.trim())) {
|
|
1764
|
+
throw new CliUsageError("--wait-ms requires a positive safe-integer millisecond value");
|
|
1765
|
+
}
|
|
1766
|
+
const parsed = Number(taken.value.trim());
|
|
1767
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
1768
|
+
throw new CliUsageError("--wait-ms requires a positive safe-integer millisecond value");
|
|
1769
|
+
}
|
|
1770
|
+
waitWindowMs = parsed;
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1757
1773
|
throw new CliUsageError(`unknown collector option: ${taken.def.canonical}`);
|
|
1758
1774
|
}
|
|
1759
1775
|
const token = tokens.shift()!;
|
|
@@ -1772,6 +1788,7 @@ export function parseCollectorArgv(args: readonly string[]): ParseCollectorArgvR
|
|
|
1772
1788
|
...(project === undefined ? {} : { project }),
|
|
1773
1789
|
...(repo === undefined ? {} : { repo }),
|
|
1774
1790
|
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
1791
|
+
...(waitWindowMs === undefined ? {} : { waitWindowMs }),
|
|
1775
1792
|
};
|
|
1776
1793
|
}
|
|
1777
1794
|
|
|
@@ -1845,6 +1862,8 @@ export type AdmitCollectorInvocationOptions = {
|
|
|
1845
1862
|
repo?: string;
|
|
1846
1863
|
/** Optional public request configuration; copied into the admitted run. */
|
|
1847
1864
|
requestManifestPath?: string;
|
|
1865
|
+
/** Optional wait-window ms (#678 D4). */
|
|
1866
|
+
waitWindowMs?: number;
|
|
1848
1867
|
createRunId?: () => string;
|
|
1849
1868
|
/** Effective model for this invocation — written onto invocation.json. */
|
|
1850
1869
|
model?: InvocationEffectiveModel;
|
|
@@ -1947,6 +1966,7 @@ export async function admitCollectorInvocation(
|
|
|
1947
1966
|
repository: repository.canonical,
|
|
1948
1967
|
repositoryDisplay: repository.display,
|
|
1949
1968
|
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
1969
|
+
...(options.waitWindowMs === undefined ? {} : { waitWindowMs: options.waitWindowMs }),
|
|
1950
1970
|
manifestDigest,
|
|
1951
1971
|
attachments: attachments.map((a) => ({
|
|
1952
1972
|
provenancePath: a.provenancePath,
|
|
@@ -1977,6 +1997,7 @@ export async function admitCollectorInvocation(
|
|
|
1977
1997
|
...(prNumber === undefined ? {} : { prNumber }),
|
|
1978
1998
|
repository,
|
|
1979
1999
|
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
2000
|
+
...(options.waitWindowMs === undefined ? {} : { waitWindowMs: options.waitWindowMs }),
|
|
1980
2001
|
manifestDigest,
|
|
1981
2002
|
};
|
|
1982
2003
|
}
|
|
@@ -598,8 +598,22 @@ 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
|
+
},
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
id: "wait-ms",
|
|
607
|
+
owner: "collector",
|
|
608
|
+
canonical: "--wait-ms",
|
|
609
|
+
aliases: [],
|
|
610
|
+
valueMetavar: "ms",
|
|
611
|
+
required: false,
|
|
612
|
+
repeatable: false,
|
|
613
|
+
form: "option",
|
|
614
|
+
description: {
|
|
615
|
+
en: "Wait-window duration in milliseconds after the work step opens (default 600000 = 10 minutes). Does not require a code change to adjust.",
|
|
616
|
+
zh: "工作步骤开启后的等待窗时长(毫秒;默认 600000=十分钟)。改配置即可,无需改代码。",
|
|
603
617
|
},
|
|
604
618
|
},
|
|
605
619
|
] as const satisfies readonly PublicOptionDefinition[];
|
|
@@ -1149,6 +1149,8 @@ type LoadedAdmittedRequestFields = {
|
|
|
1149
1149
|
readonly repositoryDisplay?: string;
|
|
1150
1150
|
readonly requestManifestPath?: string;
|
|
1151
1151
|
readonly manifestDigest?: string;
|
|
1152
|
+
/** Collector wait-window ms restored on resume (#678). */
|
|
1153
|
+
readonly waitWindowMs?: number;
|
|
1152
1154
|
/** Doctor — admitted single-case identity restored on resume (#633). */
|
|
1153
1155
|
readonly issueNumber?: number;
|
|
1154
1156
|
readonly caseRunsPath?: string;
|
|
@@ -1244,6 +1246,7 @@ async function loadResumableRunRecord(
|
|
|
1244
1246
|
let repositoryDisplay: string | undefined;
|
|
1245
1247
|
let requestManifestPath: string | undefined;
|
|
1246
1248
|
let manifestDigest: string | undefined;
|
|
1249
|
+
let waitWindowMs: number | undefined;
|
|
1247
1250
|
let issueNumber: number | undefined;
|
|
1248
1251
|
let caseRunsPath: string | undefined;
|
|
1249
1252
|
let caseIdentity: DoctorCaseIdentity | undefined;
|
|
@@ -1304,6 +1307,9 @@ async function loadResumableRunRecord(
|
|
|
1304
1307
|
if (typeof record.manifestDigest === "string" && record.manifestDigest.trim() !== "") {
|
|
1305
1308
|
manifestDigest = record.manifestDigest;
|
|
1306
1309
|
}
|
|
1310
|
+
if (typeof record.waitWindowMs === "number" && Number.isSafeInteger(record.waitWindowMs) && record.waitWindowMs >= 1) {
|
|
1311
|
+
waitWindowMs = record.waitWindowMs;
|
|
1312
|
+
}
|
|
1307
1313
|
// Doctor — admitted single-case identity (#633 resume).
|
|
1308
1314
|
if (typeof record.issueNumber === "number" && Number.isSafeInteger(record.issueNumber) && record.issueNumber >= 1) {
|
|
1309
1315
|
issueNumber = record.issueNumber;
|
|
@@ -1471,6 +1477,7 @@ async function loadResumableRunRecord(
|
|
|
1471
1477
|
...(repositoryDisplay === undefined ? {} : { repositoryDisplay }),
|
|
1472
1478
|
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
1473
1479
|
...(manifestDigest === undefined ? {} : { manifestDigest }),
|
|
1480
|
+
...(waitWindowMs === undefined ? {} : { waitWindowMs }),
|
|
1474
1481
|
...(issueNumber === undefined ? {} : { issueNumber }),
|
|
1475
1482
|
...(caseRunsPath === undefined ? {} : { caseRunsPath }),
|
|
1476
1483
|
...(caseIdentity === undefined ? {} : { caseIdentity }),
|
|
@@ -1913,6 +1920,9 @@ export async function loadResumableCollectorRun(
|
|
|
1913
1920
|
...(loaded.admittedFields.requestManifestPath === undefined
|
|
1914
1921
|
? {}
|
|
1915
1922
|
: { requestManifestPath: loaded.admittedFields.requestManifestPath }),
|
|
1923
|
+
...(loaded.admittedFields.waitWindowMs === undefined
|
|
1924
|
+
? {}
|
|
1925
|
+
: { waitWindowMs: loaded.admittedFields.waitWindowMs }),
|
|
1916
1926
|
manifestDigest,
|
|
1917
1927
|
};
|
|
1918
1928
|
return seatLoadedResult(loaded, admitted);
|
package/src/role-runtime.ts
CHANGED
|
@@ -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
|
);
|