@akagilnc/pi-workflow-roles 0.1.5096 → 0.1.5124

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.
@@ -11,6 +11,8 @@ import { isAbsolute, join, resolve } from "node:path";
11
11
 
12
12
  import {
13
13
  buildAutoResumeContinuationPrompt,
14
+ findRunDirectoryById,
15
+ readRoleRunState,
14
16
  RESUME_TRANSPORT_ENVELOPE,
15
17
  type PublicResumeRequest,
16
18
  type SameTicketSummonsMaterials,
@@ -26,12 +28,14 @@ import {
26
28
  import { readRecordedSubmissionRows } from "../submission-ledger.ts";
27
29
  import { pathContainedIn } from "../activation-ledger-topology.ts";
28
30
  import { pickEngineAxis } from "../package-resources/engine-material.ts";
29
- import { readStoredHostSessionId, resolveHostAwareSessionAvailability } from "../session-identity.ts";
31
+ import {
32
+ readStoredHostSessionId,
33
+ resolveHostAwareSessionAvailability,
34
+ } from "../session-identity.ts";
30
35
  import { rewriteRunDirectoryPathValue } from "../role-run-relocation.ts";
31
36
 
32
37
  import type {
33
38
  ControlledFailureCause,
34
- DurablePrincipal,
35
39
  DurablePrincipalAuthority,
36
40
  RoleTurnHost,
37
41
  RoleTurnKnownFailure,
@@ -94,19 +98,23 @@ import {
94
98
  exitCodeForTerminalOutcome,
95
99
  explicitInternalKnownFailureClassificationInput,
96
100
  formatCliDiagnostic,
101
+ formatErrorCauseDetail,
97
102
  formatTerminalResult,
98
103
  inspectJudgeSession,
99
104
  isLawfulTypedTerminalOutcome,
100
105
  presentFailureTerminal,
106
+ publishedFailureErrorPath,
101
107
  presentStructuralRejection,
102
108
  resolveAuditedRunnerFailureResolution,
103
109
  resolveControlledFailureResumeObservation,
104
110
  settleFailureTerminalResult,
105
111
  settleHostEndedNoReceipt,
106
112
  attachRecordedSubmissions,
113
+ rewritePublishedFailureErrorPath,
114
+ type ControlledFailure,
107
115
  } from "./settlement.ts";
108
116
  import type { CliIo } from "./cli-io.ts";
109
- import type { AdmittedRoleInvocation } from "./invocation.ts";
117
+ import type { AdmittedRoleInvocation, RunDirectoryRelocation } from "./invocation.ts";
110
118
  import type { NamedRoleTurnHostAdapter } from "./role-turn-host-resolution.ts";
111
119
  import {
112
120
  type TerminalResult,
@@ -140,6 +148,11 @@ function projectRelocatedTurnIdentity(
140
148
  }
141
149
  }
142
150
  if (result.terminal !== undefined) {
151
+ rewritePublishedFailureErrorPath(
152
+ result.terminal,
153
+ relocation.oldRunDirectory,
154
+ relocation.newRunDirectory,
155
+ );
143
156
  for (const artifact of result.terminal.artifacts) {
144
157
  artifact.path = rewrite(artifact.path) as string;
145
158
  }
@@ -361,7 +374,12 @@ export type PostAdmissionAdapters<
361
374
  * held lease (when present) follows the run directory and failures stay
362
375
  * controlled. Public manual resume (#987) may omit the lease.
363
376
  */
364
- afterDispatch?: (admitted: A, lease?: RunWriterLease) => Promise<void> | void;
377
+ afterDispatch?: (
378
+ admitted: A,
379
+ lease?: RunWriterLease,
380
+ ) => Promise<RunDirectoryRelocation | undefined>
381
+ | RunDirectoryRelocation
382
+ | void;
365
383
  };
366
384
 
367
385
  export type ControlledFailureInput = {
@@ -442,7 +460,7 @@ export async function resolveResumeMethodMaterialAdapters<
442
460
  },
443
461
  input.emptyAdapters,
444
462
  input.authority,
445
- input.io,
463
+ { ...input.io, omitFailureStderrDiagnostic: true },
446
464
  );
447
465
  return { kind: "terminal", ...terminal };
448
466
  }
@@ -522,23 +540,26 @@ export async function presentControlledFailure<
522
540
  await persistReturnedRunState(admitted, authority);
523
541
  }
524
542
 
525
- const terminal = await attachRecordedSubmissions(
526
- admitted,
527
- await settleFailureTerminalResult(
528
- admitted,
529
- failure,
530
- authority,
531
- {
532
- ...(resumable
533
- ? { resume: { command: renderResumeCommand(admitted.runId) } }
534
- : {}),
535
- ...(failureInput.invocationScopeId === undefined ||
536
- failureInput.invocationScopeId.length === 0
537
- ? {}
538
- : { invocationScopeId: failureInput.invocationScopeId }),
539
- },
540
- ),
541
- );
543
+ let publishedErrorPath: string | undefined;
544
+ let terminal: TerminalResult;
545
+ try {
546
+ const settled = await settleFailureTerminalResult(admitted, failure, authority, {
547
+ ...(resumable
548
+ ? { resume: { command: renderResumeCommand(admitted.runId) } }
549
+ : {}),
550
+ ...(failureInput.invocationScopeId === undefined ||
551
+ failureInput.invocationScopeId.length === 0
552
+ ? {}
553
+ : { invocationScopeId: failureInput.invocationScopeId }),
554
+ onErrorPublished: (path) => { publishedErrorPath = path; },
555
+ });
556
+ terminal = await attachRecordedSubmissions(admitted, settled);
557
+ } catch (error) {
558
+ if (io.omitFailureStderrDiagnostic && publishedErrorPath !== undefined) {
559
+ writeResumeFailurePointer(io, publishedErrorPath);
560
+ }
561
+ throw error;
562
+ }
542
563
  presentFailureTerminal(terminal, io);
543
564
  return {
544
565
  exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
@@ -738,7 +759,12 @@ export async function dispatchPostAdmissionTurn<
738
759
  if (relocation !== undefined) {
739
760
  projectRelocatedTurnIdentity(request, result, admitted, relocation);
740
761
  }
741
- if (adapters.afterDispatch !== undefined) await adapters.afterDispatch(admitted, lease);
762
+ const afterDispatchRelocation = adapters.afterDispatch === undefined
763
+ ? undefined
764
+ : await adapters.afterDispatch(admitted, lease);
765
+ if (afterDispatchRelocation !== undefined) {
766
+ projectRelocatedTurnIdentity(request, result, admitted, afterDispatchRelocation);
767
+ }
742
768
  return result;
743
769
  } catch (error) {
744
770
  if (result.terminal !== undefined) {
@@ -1524,17 +1550,31 @@ export async function runPostAdmissionSeatResume<
1524
1550
  try {
1525
1551
  loaded = await input.load(request);
1526
1552
  } catch (error) {
1527
- if (error instanceof CliUsageError) {
1528
- presentStructuralRejection(error, input.io);
1529
- return { exitCode: 2 };
1553
+ const usageError = error instanceof CliUsageError;
1554
+ // Unknown usage failures retain the structural fallback. Other load
1555
+ // failures keep the CLI's outer handling unless an existing run is found.
1556
+ const recorded = await pointExistingRunFailure(
1557
+ input.env.home,
1558
+ request.runId,
1559
+ input.env.principalAuthority,
1560
+ input.io,
1561
+ error,
1562
+ );
1563
+ if (!recorded) {
1564
+ if (usageError) {
1565
+ presentStructuralRejection(error, input.io);
1566
+ return { exitCode: 2 };
1567
+ }
1568
+ throw error;
1530
1569
  }
1531
- throw error;
1570
+ return { exitCode: usageError ? 2 : 1 };
1532
1571
  }
1533
1572
 
1534
1573
  let adapters = input.adapters;
1535
1574
  if (input.afterAdmittedLoad !== undefined) {
1536
1575
  const prepared = await input.afterAdmittedLoad(loaded.admitted);
1537
1576
  if (prepared.kind === "terminal") {
1577
+ showResumeErrorPointer(input.io, prepared.exitCode, prepared.terminal);
1538
1578
  return {
1539
1579
  exitCode: prepared.exitCode,
1540
1580
  admitted: prepared.admitted,
@@ -1710,26 +1750,141 @@ export async function runPostAdmissionSeatResume<
1710
1750
  }),
1711
1751
  });
1712
1752
  }
1713
- return await runPostAdmissionManualResume({
1753
+ const resumed = await runPostAdmissionManualResume({
1714
1754
  admitted: loaded.admitted,
1715
1755
  env,
1716
- io: input.io,
1756
+ io: { ...input.io, omitFailureStderrDiagnostic: true },
1717
1757
  adapters,
1718
1758
  ...(input.effectiveEngine === undefined
1719
1759
  ? {}
1720
1760
  : { effectiveEngine: input.effectiveEngine }),
1721
1761
  buildRequestAfterLease,
1722
1762
  });
1763
+ showResumeErrorPointer(input.io, resumed.exitCode, resumed.terminal);
1764
+ return resumed;
1723
1765
  } catch (error) {
1724
- // Turn construction may still surface structural rejection.
1725
- if (error instanceof CliUsageError) {
1726
- presentStructuralRejection(error, input.io);
1727
- return { exitCode: 2 };
1766
+ if (error instanceof TurnDispatchedFailure) throw error;
1767
+ await presentResumeFailurePointer(
1768
+ input.io,
1769
+ error,
1770
+ (failure) => writeResumeDiagnosticFile(
1771
+ loaded.admitted.runDirectory,
1772
+ loaded.admitted.runId,
1773
+ failure,
1774
+ loaded.admitted.role,
1775
+ ),
1776
+ );
1777
+ return { exitCode: error instanceof CliUsageError ? 2 : 1 };
1778
+ }
1779
+ }
1780
+
1781
+ async function pointExistingRunFailure(
1782
+ home: string,
1783
+ runId: string,
1784
+ authority: DurablePrincipalAuthority,
1785
+ io: CliIo,
1786
+ thrown: unknown,
1787
+ ): Promise<boolean> {
1788
+ let foundRunDirectory: string | undefined;
1789
+ try {
1790
+ foundRunDirectory = await findRunDirectoryById(home, runId);
1791
+ } catch {
1792
+ return false;
1793
+ }
1794
+ if (foundRunDirectory === undefined) return false;
1795
+ const runDirectory = foundRunDirectory;
1796
+
1797
+ let record: Awaited<ReturnType<typeof readRoleRunState>>;
1798
+ try {
1799
+ record = await readRoleRunState(runDirectory, authority);
1800
+ } catch {
1801
+ await presentResumeFailurePointer(io, thrown, (failure) =>
1802
+ writeResumeDiagnosticFile(runDirectory, runId, failure),
1803
+ );
1804
+ return true;
1805
+ }
1806
+ if (record === undefined) return false;
1807
+ await presentResumeFailurePointer(io, thrown, (failure) =>
1808
+ writeResumeDiagnosticFile(runDirectory, runId, failure, record.role),
1809
+ );
1810
+ return true;
1811
+ }
1812
+
1813
+ export async function presentLocatedResumeFailure(
1814
+ home: string,
1815
+ runId: string,
1816
+ authority: DurablePrincipalAuthority,
1817
+ io: CliIo,
1818
+ thrown: unknown,
1819
+ ): Promise<boolean> {
1820
+ return await pointExistingRunFailure(home, runId, authority, io, thrown);
1821
+ }
1822
+
1823
+ function writeResumeFailurePointer(io: CliIo, errorPath: string): void {
1824
+ io.stderr(formatCliDiagnostic(`续跑失败,当次错误记录:${errorPath}`));
1825
+ }
1826
+
1827
+ export function showResumeErrorPointer(
1828
+ io: CliIo,
1829
+ exitCode: number,
1830
+ terminal: TerminalResult | undefined,
1831
+ ): void {
1832
+ if (exitCode === 0 || terminal === undefined) return;
1833
+ const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path
1834
+ ?? publishedFailureErrorPath(terminal);
1835
+ if (publishedError !== undefined) writeResumeFailurePointer(io, publishedError);
1836
+ }
1837
+
1838
+ async function presentResumeFailurePointer(
1839
+ io: CliIo,
1840
+ thrown: unknown,
1841
+ publish: (failure: ControlledFailure) => Promise<string>,
1842
+ ): Promise<void> {
1843
+ const failure = classifyPostAdmissionFailure({
1844
+ timedOut: false,
1845
+ code: null,
1846
+ stderr: "",
1847
+ thrown,
1848
+ });
1849
+ try {
1850
+ writeResumeFailurePointer(io, await publish(failure));
1851
+ } catch (publishError) {
1852
+ const cause: { thrownCause?: string; publishError: string } = {
1853
+ publishError: formatErrorCauseDetail(publishError),
1854
+ };
1855
+ if (thrown instanceof Error && thrown.cause !== undefined) {
1856
+ cause.thrownCause = formatErrorCauseDetail(thrown.cause);
1728
1857
  }
1729
- throw error;
1858
+ presentStructuralRejection({
1859
+ message: thrown instanceof Error ? thrown.message : String(thrown),
1860
+ cause,
1861
+ }, io);
1730
1862
  }
1731
1863
  }
1732
1864
 
1865
+ async function writeResumeDiagnosticFile(
1866
+ runDirectory: string,
1867
+ runId: string,
1868
+ failure: ControlledFailure,
1869
+ role?: AdmittedRoleInvocation["role"],
1870
+ ): Promise<string> {
1871
+ const dir = await ensureRealArtifactsDirectory(runDirectory);
1872
+ const path = join(dir, `resume-diagnostic-${randomUUID()}.json`);
1873
+ await writeFile(
1874
+ path,
1875
+ `${JSON.stringify({
1876
+ runId,
1877
+ ...(role === undefined ? {} : { role }),
1878
+ diagnostic: failure.diagnostic,
1879
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
1880
+ ...(failure.identity === undefined ? {} : { identity: failure.identity }),
1881
+ ...(failure.details === undefined ? {} : { details: failure.details }),
1882
+ }, null, 2)}\n`,
1883
+ { encoding: "utf8", flag: "wx" },
1884
+ );
1885
+ return path;
1886
+ }
1887
+
1733
1888
  /**
1734
1889
  * Shared post-admission one-shot path: folds into runPostAdmissionResumable (#840 / #416).
1735
1890
  * All callable roles share the single auto-resume loop.
@@ -116,6 +116,7 @@ import type {
116
116
  DurablePrincipalCoordinates,
117
117
  } from "../host-contracts.ts";
118
118
  import { roleRunArtifactsDirectory } from "../role-run-placement.ts";
119
+ import { rewriteRunDirectoryPathValue } from "../role-run-relocation.ts";
119
120
  import {
120
121
  listSeamOwnedUniqueErrorFacePaths,
121
122
  RUN_TERMINAL_ARTIFACT_FILES,
@@ -286,10 +287,13 @@ export async function attachRecordedSubmissions<T extends TerminalResult>(
286
287
  // pass courtAttemptId here — roleOutcome already carries this-court payloads
287
288
  // from sealedLedgerOutcome when scoped; withSubmissions keeps them.
288
289
  void scope;
289
- return withSubmissions(
290
+ const result = withSubmissions(
290
291
  terminal,
291
292
  await recordedSubmissionPayloads(admitted, undefined),
292
293
  );
294
+ const errorPath = privateFailureErrorPaths.get(terminal);
295
+ if (errorPath !== undefined) privateFailureErrorPaths.set(result, errorPath);
296
+ return result;
293
297
  }
294
298
 
295
299
  /**
@@ -373,6 +377,28 @@ export type ControlledFailure = {
373
377
  readonly details?: Readonly<Record<string, unknown>>;
374
378
  };
375
379
 
380
+ // A resumable public Terminal omits artifact paths (#108); its actual error
381
+ // publication remains available only to the CLI's resume presentation seam.
382
+ const privateFailureErrorPaths = new WeakMap<TerminalResult, string>();
383
+
384
+ export function publishedFailureErrorPath(terminal: TerminalResult): string | undefined {
385
+ return privateFailureErrorPaths.get(terminal);
386
+ }
387
+
388
+ export function rewritePublishedFailureErrorPath(
389
+ terminal: TerminalResult,
390
+ oldRunDirectory: string,
391
+ newRunDirectory: string,
392
+ ): void {
393
+ const errorPath = privateFailureErrorPaths.get(terminal);
394
+ if (errorPath !== undefined) {
395
+ privateFailureErrorPaths.set(
396
+ terminal,
397
+ rewriteRunDirectoryPathValue(errorPath, oldRunDirectory, newRunDirectory) as string,
398
+ );
399
+ }
400
+ }
401
+
376
402
  /**
377
403
  * Host stderr as recorded — full bytes, no flood filter, no char clip (#836).
378
404
  */
@@ -3185,6 +3211,7 @@ export async function publishFailureArtifacts(
3185
3211
  admitted: AdmittedRoleInvocation,
3186
3212
  failure: ControlledFailure,
3187
3213
  authority: DurablePrincipalAuthority,
3214
+ onErrorPublished?: (path: string) => void,
3188
3215
  ): Promise<TerminalArtifactRef[]> {
3189
3216
  const { sessionDirectory, sessionFile } = coordinatesFromAdmitted(authority, admitted);
3190
3217
  const { baseDir, attempt: baseAttempt } = await resolveFailureArtifactsBase(
@@ -3267,6 +3294,7 @@ export async function publishFailureArtifacts(
3267
3294
  errorPayloadBase,
3268
3295
  priorIssues,
3269
3296
  );
3297
+ onErrorPublished?.(errorWrite.path);
3270
3298
 
3271
3299
  const evidencePayload: Record<string, unknown> = {
3272
3300
  runId: admitted.runId,
@@ -3306,6 +3334,7 @@ export async function settleFailureTerminalResult(
3306
3334
  authority: DurablePrincipalAuthority,
3307
3335
  options: SettlementCourtScope & {
3308
3336
  readonly resume?: TerminalResume;
3337
+ readonly onErrorPublished?: (path: string) => void;
3309
3338
  } = {},
3310
3339
  ): Promise<TerminalResult> {
3311
3340
  const coordinates = coordinatesFromAdmitted(authority, admitted);
@@ -3376,7 +3405,8 @@ export async function settleFailureTerminalResult(
3376
3405
  // Exact-session attendance only — never infer no-advice from caller omission.
3377
3406
  const navigator = await extractNavigatorFactFromAdmittedSession(sessionFile);
3378
3407
  // Private durable artifacts retain the original diagnostic identity (including run ID).
3379
- const artifacts = await publishFailureArtifacts(admitted, failure, authority);
3408
+ const artifacts = await publishFailureArtifacts(admitted, failure, authority, options.onErrorPublished);
3409
+ const errorPath = artifacts.find((artifact) => artifact.kind === "error")?.path;
3380
3410
  const decisiveFacts: Record<string, unknown> = {
3381
3411
  ...(failure.cause === undefined ? {} : { cause: failure.cause }),
3382
3412
  diagnostic: failure.diagnostic,
@@ -3402,7 +3432,7 @@ export async function settleFailureTerminalResult(
3402
3432
  diagnostic: failure.diagnostic,
3403
3433
  decisiveFacts,
3404
3434
  };
3405
- return withOptionalGateProjection(
3435
+ const terminal = await withOptionalGateProjection(
3406
3436
  {
3407
3437
  roleOutcome,
3408
3438
  navigator,
@@ -3412,6 +3442,8 @@ export async function settleFailureTerminalResult(
3412
3442
  sessionDirectory,
3413
3443
  detourGateContext(admitted, options),
3414
3444
  );
3445
+ if (errorPath !== undefined) privateFailureErrorPaths.set(terminal, errorPath);
3446
+ return terminal;
3415
3447
  }
3416
3448
  const roleOutcome: TerminalRoleOutcome = {
3417
3449
  kind: "failure",
@@ -3451,13 +3483,14 @@ export async function settleJudgeFailureTerminalResult(
3451
3483
  */
3452
3484
  export function presentFailureTerminal(
3453
3485
  terminal: TerminalResult,
3454
- io: { stdout: (text: string) => void; stderr: (text: string) => void },
3486
+ io: { stdout: (text: string) => void; stderr: (text: string) => void; omitFailureStderrDiagnostic?: boolean },
3455
3487
  ): void {
3456
3488
  if (terminal.roleOutcome.kind !== "failure" && terminal.roleOutcome.kind !== "no_receipt") {
3457
3489
  throw new TypeError("presentFailureTerminal requires a failure or no-receipt role outcome");
3458
3490
  }
3459
3491
  io.stdout(formatTerminalResult(terminal));
3460
3492
  if (terminal.roleOutcome.kind === "failure") {
3493
+ if (io.omitFailureStderrDiagnostic) return;
3461
3494
  io.stderr(formatFailureStderrDiagnostic({
3462
3495
  ...(terminal.roleOutcome.cause === undefined ? {} : { cause: terminal.roleOutcome.cause }),
3463
3496
  diagnostic: terminal.roleOutcome.diagnostic,