@akagilnc/pi-workflow-roles 0.1.2152 → 0.1.2157

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.
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Durable Role run lifecycle for public CLI (ADR 0052 / #11 / #108).
2
+ * Durable Role run lifecycle for public CLI (ADR 0052 / #11 / #108 / #416).
3
3
  * States: admitted → running → resumable | terminal.
4
- * v1 resume is limited to an observed typed HTTP 429 on Codex/xAI with no
5
- * lawful role terminal result. Prose is never regex-classified as quota evidence.
4
+ * #416 (owner 2026-08-22): resume no longer gates on terminal/resumable or typed 429
5
+ * any existing run with an available Pi session principal may be resumed; caller decides.
6
+ * Prose is never regex-classified as quota evidence.
6
7
  */
7
- import { lstat, open, readdir, readFile, unlink, writeFile } from "node:fs/promises";
8
+ import { chmod, lstat, open, readdir, readFile, unlink, writeFile } from "node:fs/promises";
8
9
  import { join } from "node:path";
9
10
 
10
11
  import {
@@ -38,7 +39,9 @@ import {
38
39
  type InvocationEffectiveModel,
39
40
  } from "./invocation.ts";
40
41
 
41
- /** Providers eligible for v1 typed-429 resume (Codex / xAI only). */
42
+ /** Providers eligible for v1 typed-429 resume (Codex / xAI only).
43
+ * @deprecated v1 429-only gate removed by #416 (owner 2026-08-22: "根本不要有限制"). Kept for compatibility; do not use for new branching.
44
+ */
42
45
  export const V1_RESUMABLE_PROVIDERS = ["openai-codex", "xai"] as const;
43
46
  export type V1ResumableProvider = (typeof V1_RESUMABLE_PROVIDERS)[number];
44
47
 
@@ -49,6 +52,9 @@ export type TypedHttp429Observation = {
49
52
  readonly provider: V1ResumableProvider;
50
53
  };
51
54
 
55
+ /** Per single LLM call auto-resume retries (call-local, no persistence). See #416 scope correction 2026-08-22. */
56
+ export const AUTO_RESUME_LIMIT = 2 as const;
57
+
52
58
  export type RoleRunRecord = {
53
59
  readonly runId: string;
54
60
  readonly role:
@@ -69,7 +75,9 @@ export type RoleRunRecord = {
69
75
  readonly admittedRequestPath: string;
70
76
  /** Coder/Fixer — preserved for resume continuation. */
71
77
  readonly phase?: CoderPhase | FixerPhase;
72
- /** Present only while state === "resumable". */
78
+ /** Present only while state === "resumable".
79
+ * @deprecated retained only for historical 429 runs; #416 no longer gates resume on this field.
80
+ */
73
81
  readonly resumable?: TypedHttp429Observation;
74
82
  };
75
83
 
@@ -79,12 +87,14 @@ export const RESUME_TRANSPORT_ENVELOPE = "[ak-role:resume-continue]" as const;
79
87
  const RUN_STATE_FILE = "run-state.json";
80
88
  const WRITER_LOCK_FILE = "writer.lock";
81
89
 
90
+ /** @deprecated #416: 429-only classification removed; kept for compatibility. */
82
91
  export function isV1ResumableProvider(
83
92
  provider: string,
84
93
  ): provider is V1ResumableProvider {
85
94
  return (V1_RESUMABLE_PROVIDERS as readonly string[]).includes(provider);
86
95
  }
87
96
 
97
+ /** @deprecated #416: 429-only observation no longer gates resume; kept for historical runs. */
88
98
  export async function readTypedHttp429Observation(
89
99
  runDirectory: string,
90
100
  ): Promise<TypedHttp429Observation | undefined> {
@@ -96,6 +106,7 @@ export async function readTypedHttp429Observation(
96
106
  }
97
107
 
98
108
  /**
109
+ * @deprecated #416: 429-only resumability predicate removed from gating. Kept for compatibility.
99
110
  * True only when a typed HTTP 429 was observed and no lawful terminal exists.
100
111
  * Callers must pass the lawful-terminal fact from settlement, not re-infer it.
101
112
  */
@@ -266,6 +277,7 @@ export async function markRunRunning(
266
277
  });
267
278
  }
268
279
 
280
+ /** @deprecated #416: 429-only resumable marker; kept for historical runs. */
269
281
  export async function markRunResumable(
270
282
  runDirectory: string,
271
283
  observation: TypedHttp429Observation,
@@ -352,7 +364,16 @@ export async function acquireRunWriterLease(
352
364
  if (released) return;
353
365
  released = true;
354
366
  await handle.close().catch(() => undefined);
355
- await unlink(lockPath).catch(() => undefined);
367
+ try {
368
+ await unlink(lockPath);
369
+ } catch (error) {
370
+ if ((error as { code?: unknown }).code === "EACCES") {
371
+ try {
372
+ await chmod(runDirectory, 0o755);
373
+ await unlink(lockPath);
374
+ } catch {}
375
+ }
376
+ }
356
377
  },
357
378
  };
358
379
  } catch (error) {
@@ -453,7 +474,7 @@ async function loadResumableRunRecord(
453
474
  runId: string,
454
475
  ): Promise<{
455
476
  readonly run: RoleRunRecord;
456
- readonly observation: TypedHttp429Observation;
477
+ readonly observation?: TypedHttp429Observation;
457
478
  readonly admittedFields: LoadedAdmittedRequestFields;
458
479
  }> {
459
480
  const runDirectory = await findRunDirectoryById(home, runId);
@@ -464,12 +485,8 @@ async function loadResumableRunRecord(
464
485
  if (run === undefined) {
465
486
  throw new CliUsageError(`unknown role run id: ${runId}`);
466
487
  }
467
- if (run.state === "terminal") {
468
- throw new CliUsageError(`role run is already terminal: ${runId}`);
469
- }
470
- if (run.state !== "resumable" || run.resumable === undefined) {
471
- throw new CliUsageError(`role run is not resumable: ${runId}`);
472
- }
488
+ // #416: removed terminal/resumable gates per owner decision "根本不要有限制" (2026-08-22).
489
+ // Only the exact Pi session principal check remains as honest failure.
473
490
  // Exact Pi session principal must be present before resume dispatches.
474
491
  if (!(await isSessionPrincipalAvailable(run.sessionFile))) {
475
492
  throw new CliUsageError(
@@ -618,7 +635,7 @@ async function loadResumableRunRecord(
618
635
  }
619
636
  return {
620
637
  run,
621
- observation: run.resumable,
638
+ ...(run.resumable === undefined ? {} : { observation: run.resumable }),
622
639
  admittedFields: {
623
640
  instruction,
624
641
  instructionEmpty,
@@ -641,25 +658,26 @@ async function loadResumableRunRecord(
641
658
  export type LoadedResumableJudgeRun = {
642
659
  readonly admitted: AdmittedJudgeInvocation;
643
660
  readonly run: RoleRunRecord;
644
- readonly observation: TypedHttp429Observation;
661
+ /** @deprecated #416: resumable observation no longer gates resume; may be undefined. */
662
+ readonly observation?: TypedHttp429Observation;
645
663
  };
646
664
 
647
665
  export type LoadedResumableCoderRun = {
648
666
  readonly admitted: AdmittedCoderInvocation;
649
667
  readonly run: RoleRunRecord;
650
- readonly observation: TypedHttp429Observation;
668
+ readonly observation?: TypedHttp429Observation;
651
669
  };
652
670
 
653
671
  export type LoadedResumableFixerRun = {
654
672
  readonly admitted: AdmittedFixerInvocation;
655
673
  readonly run: RoleRunRecord;
656
- readonly observation: TypedHttp429Observation;
674
+ readonly observation?: TypedHttp429Observation;
657
675
  };
658
676
 
659
677
  export type LoadedResumableReviewerRun = {
660
678
  readonly admitted: AdmittedReviewerInvocation;
661
679
  readonly run: RoleRunRecord;
662
- readonly observation: TypedHttp429Observation;
680
+ readonly observation?: TypedHttp429Observation;
663
681
  };
664
682
 
665
683
  /**
@@ -693,7 +711,7 @@ export async function loadResumableJudgeRun(
693
711
  return {
694
712
  admitted,
695
713
  run: loaded.run,
696
- observation: loaded.observation,
714
+ ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
697
715
  };
698
716
  }
699
717
 
@@ -747,7 +765,7 @@ export async function loadResumableCoderRun(
747
765
  return {
748
766
  admitted,
749
767
  run: loaded.run,
750
- observation: loaded.observation,
768
+ ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
751
769
  };
752
770
  }
753
771
 
@@ -806,7 +824,7 @@ export async function loadResumableFixerRun(
806
824
  return {
807
825
  admitted,
808
826
  run: loaded.run,
809
- observation: loaded.observation,
827
+ ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
810
828
  };
811
829
  }
812
830
 
@@ -853,14 +871,14 @@ export async function loadResumableReviewerRun(
853
871
  return {
854
872
  admitted,
855
873
  run: loaded.run,
856
- observation: loaded.observation,
874
+ ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
857
875
  };
858
876
  }
859
877
 
860
878
  export type LoadedResumableMergerRun = {
861
879
  readonly admitted: AdmittedMergerInvocation;
862
880
  readonly run: RoleRunRecord;
863
- readonly observation: TypedHttp429Observation;
881
+ readonly observation?: TypedHttp429Observation;
864
882
  };
865
883
 
866
884
  /**
@@ -913,7 +931,7 @@ export async function loadResumableMergerRun(
913
931
  return {
914
932
  admitted,
915
933
  run: loaded.run,
916
- observation: loaded.observation,
934
+ ...(loaded.observation === undefined ? {} : { observation: loaded.observation }),
917
935
  };
918
936
  }
919
937
 
@@ -231,11 +231,16 @@ export function redactExactRunId(text: string, runId: string): string {
231
231
  * One admitted Role run's typed Terminal aggregate.
232
232
  * Resumable failures carry `resume` and must not re-disclose the run ID via
233
233
  * top-level `runId` or public artifact path components — only `resume.command`.
234
+ * #416: autoResumeCount (0..2) is a call-local observation of how many in-place
235
+ * auto-resumes occurred during this single LLM call; it is not persisted to
236
+ * run-state.json and does not participate in limit decisions.
234
237
  */
235
238
  export type TerminalResult = {
236
239
  roleOutcome: TerminalRoleOutcome;
237
240
  navigator: TerminalNavigatorFact;
238
241
  artifacts: readonly TerminalArtifactRef[];
242
+ /** Call-local auto-resume observation (0..2) for this single LLM call; read-only, not persisted. */
243
+ autoResumeCount?: number;
239
244
  } & (
240
245
  | {
241
246
  /** Resumable failure: run ID appears only inside resume.command. */
@@ -330,5 +335,8 @@ export function formatTerminalResult(result: TerminalResult): string {
330
335
  } else if (result.runId !== undefined) {
331
336
  lines.push(`run\t${encodeTerminalField(result.runId)}`);
332
337
  }
338
+ if (result.autoResumeCount !== undefined) {
339
+ lines.push(`autoResumeCount\t${encodeTerminalField(String(result.autoResumeCount))}`);
340
+ }
333
341
  return `${lines.join("\n")}\n`;
334
342
  }