@akagilnc/pi-workflow-roles 0.1.3789 → 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.
@@ -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";
@@ -34,6 +35,7 @@ import {
34
35
  COLLECTOR_BIND_TARGET_TOOL,
35
36
  COLLECTOR_HANDBOOK_WRITE_TOOL,
36
37
  COLLECTOR_OBSERVE_TOOL,
38
+ COLLECTOR_OPEN_WAIT_WINDOW_TOOL,
37
39
  COLLECTOR_OUTPUT_TOOL,
38
40
  COLLECTOR_READ_TOOL,
39
41
  COLLECTOR_REQUEST_TOOL,
@@ -50,6 +52,7 @@ import {
50
52
  collectorBindTargetArgsSchema,
51
53
  collectorHandbookWriteArgsSchema,
52
54
  collectorObserveArgsSchema,
55
+ collectorOpenWaitWindowArgsSchema,
53
56
  collectorOutputArgsSchema,
54
57
  collectorReadArgsSchema,
55
58
  collectorRequestArgsSchema,
@@ -85,6 +88,7 @@ export {
85
88
  COLLECTOR_BIND_TARGET_TOOL,
86
89
  COLLECTOR_HANDBOOK_WRITE_TOOL,
87
90
  COLLECTOR_OBSERVE_TOOL,
91
+ COLLECTOR_OPEN_WAIT_WINDOW_TOOL,
88
92
  COLLECTOR_OUTPUT_TOOL,
89
93
  COLLECTOR_READ_TOOL,
90
94
  COLLECTOR_REQUEST_TOOL,
@@ -96,6 +100,7 @@ export const COLLECTOR_REQUIRED_TOOLS = [
96
100
  COLLECTOR_OBSERVE_TOOL,
97
101
  COLLECTOR_READ_TOOL,
98
102
  COLLECTOR_REQUEST_TOOL,
103
+ COLLECTOR_OPEN_WAIT_WINDOW_TOOL,
99
104
  COLLECTOR_WAIT_TOOL,
100
105
  COLLECTOR_HANDBOOK_WRITE_TOOL,
101
106
  COLLECTOR_OUTPUT_TOOL,
@@ -127,11 +132,20 @@ export const COLLECTOR_TRANSPORT_FLAGS = Object.freeze([
127
132
  type: "string" as const,
128
133
  }),
129
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
+ }),
130
143
  ] as const);
131
144
 
132
145
  const observeSchema = collectorObserveArgsSchema;
133
146
  const readSchema = collectorReadArgsSchema;
134
147
  const requestSchema = collectorRequestArgsSchema;
148
+ const openWaitWindowSchema = collectorOpenWaitWindowArgsSchema;
135
149
  const waitSchema = collectorWaitArgsSchema;
136
150
  const bindSchema = collectorBindTargetArgsSchema;
137
151
  const handbookWriteSchema = collectorHandbookWriteArgsSchema;
@@ -139,11 +153,22 @@ const outputSchema = collectorOutputArgsSchema;
139
153
 
140
154
  type RequestParams = Static<typeof requestSchema>;
141
155
  type ReadParams = Static<typeof readSchema>;
156
+ type OpenWaitWindowParams = Static<typeof openWaitWindowSchema>;
142
157
  type WaitParams = Static<typeof waitSchema>;
143
158
  type BindParams = Static<typeof bindSchema>;
144
159
  type HandbookWriteParams = Static<typeof handbookWriteSchema>;
145
160
  type OutputParams = Static<typeof outputSchema>;
146
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
+
147
172
  export type CollectorRoleDependencies = {
148
173
  loadSoul(): Promise<string>;
149
174
  createTransport(): CollectorGitHubTransport;
@@ -184,6 +209,7 @@ function buildMethodContext(activation: CollectorActivation): string {
184
209
  `host: github.com`,
185
210
  `repository: ${activation.repository.canonical}`,
186
211
  `prNumber: ${pr === undefined ? "未绑定" : String(pr)}`,
212
+ `waitWindowMs: ${String(activation.ledger.config.waitWindowMs)}`,
187
213
  `requests: ${JSON.stringify(activation.manifest.requests.map((request) => ({ id: request.id })))}`,
188
214
  `handbookGeneralSource: ${handbook.generalSource}`,
189
215
  `handbookRepoSource: ${handbook.repoSource}`,
@@ -244,6 +270,7 @@ export function createCollectorRoleRuntime(
244
270
  const repoFlag = pi.getFlag("ak-collector-repo");
245
271
  const prFlag = pi.getFlag("ak-collector-pr");
246
272
  const requestManifestFlag = pi.getFlag("ak-collector-request-manifest");
273
+ const waitMsFlag = pi.getFlag("ak-collector-wait-ms");
247
274
  if (typeof repoFlag !== "string" || repoFlag.trim().length === 0) {
248
275
  throw new Error("Collector requires --ak-collector-repo");
249
276
  }
@@ -258,11 +285,12 @@ export function createCollectorRoleRuntime(
258
285
  const manifest = typeof requestManifestFlag === "string" && requestManifestFlag.trim().length > 0
259
286
  ? await loadCollectorManifest(requestManifestFlag)
260
287
  : emptyCollectorManifest();
288
+ const waitWindowMs = parseWaitWindowMsFlag(waitMsFlag);
261
289
 
262
290
  const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
263
291
  const transport = dependencies.createTransport();
264
292
  const ledger = dependencies.createLedger(
265
- { repository, prNumber, manifest },
293
+ { repository, prNumber, manifest, waitWindowMs },
266
294
  clock,
267
295
  ctx,
268
296
  );
@@ -557,11 +585,60 @@ export function createCollectorRoleRuntime(
557
585
  },
558
586
  });
559
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
+
560
637
  pi.registerTool({
561
638
  name: COLLECTOR_WAIT_TOOL,
562
639
  label: "通进司等待",
563
- description: "再观察前等待;单次上限五分钟且不超剩余资格。",
564
- promptSnippet: "资格截止前等待",
640
+ description: "再观察前等待;实际睡眠不超过剩余等待窗。",
641
+ promptSnippet: "等待窗内等待",
565
642
  parameters: waitSchema,
566
643
  async execute(toolCallId: string, params: WaitParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
567
644
  const activation = getActivation();
@@ -582,6 +659,7 @@ export function createCollectorRoleRuntime(
582
659
  details,
583
660
  };
584
661
  } catch (error) {
662
+ if (isCorrectableExecuteError(error)) throw error;
585
663
  hostActions.failInfrastructure(error, ctx, toolCallId);
586
664
  }
587
665
  },
@@ -1,5 +1,4 @@
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
 
@@ -36,7 +35,22 @@ export const collectorReadArgsSchema = Type.Object({
36
35
  evidenceId: Type.String({ minLength: 1, description: "observe 返回的材料证据 id(evidenceId)" }),
37
36
  }, { additionalProperties: false });
38
37
  export const collectorWaitArgsSchema = Type.Object({
39
- durationMs: Type.Integer({ minimum: 1, maximum: COLLECTOR_ELIGIBILITY_MS, description: "等待毫秒;单次上限五分钟且不超剩余资格" }),
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
+ })),
40
54
  }, { additionalProperties: false });
41
55
 
42
56
  /**
@@ -112,5 +126,6 @@ export type CollectorObserveArgs = Static<typeof collectorObserveArgsSchema>;
112
126
  export type CollectorRequestArgs = Static<typeof collectorRequestArgsSchema>;
113
127
  export type CollectorReadArgs = Static<typeof collectorReadArgsSchema>;
114
128
  export type CollectorWaitArgs = Static<typeof collectorWaitArgsSchema>;
129
+ export type CollectorOpenWaitWindowArgs = Static<typeof collectorOpenWaitWindowArgsSchema>;
115
130
  export type CollectorBindTargetArgs = Static<typeof collectorBindTargetArgsSchema>;
116
131
  export type CollectorOutputArgs = Static<typeof collectorOutputArgsSchema>;
@@ -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
  | {
@@ -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
  }
@@ -602,6 +602,20 @@ const COLLECTOR_OPTIONS = [
602
602
  zh: "可选 request manifest JSON 路径({requests:[{id,body}]})。角色亦可依手册/现场自行判定请求正文,不必依赖本文件。",
603
603
  },
604
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=十分钟)。改配置即可,无需改代码。",
617
+ },
618
+ },
605
619
  ] as const satisfies readonly PublicOptionDefinition[];
606
620
 
607
621
  const DOCTOR_OPTIONS = [
@@ -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);