@bridge_gpt/mcp-server 0.2.62 → 0.2.65

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.
Files changed (80) hide show
  1. package/README.md +47 -8
  2. package/build/agent-launchers/claude-executor-adapter.js +134 -3
  3. package/build/agent-launchers/executor-adapter.js +54 -2
  4. package/build/agents.generated.js +10 -1
  5. package/build/bounded-wait.js +32 -1
  6. package/build/commands.generated.js +6 -5
  7. package/build/conductor/bridge-api-client.js +335 -22
  8. package/build/conductor/bring-up-facts.js +3 -1
  9. package/build/conductor/doctor.js +23 -14
  10. package/build/conductor/epic-child-continuation.js +324 -0
  11. package/build/conductor/epic-implementer-cli.js +142 -17
  12. package/build/conductor/git-hooks.js +529 -118
  13. package/build/conductor/hook-runtime-qualification.js +340 -0
  14. package/build/conductor/readiness-sections.js +5 -4
  15. package/build/conductor/readiness.js +22 -11
  16. package/build/conductor/recovery-cli.js +354 -13
  17. package/build/conductor/recovery-operations.js +251 -5
  18. package/build/conductor/repair-contract.js +8 -0
  19. package/build/conductor-bundle-artifacts.js +1 -0
  20. package/build/credential-store.js +50 -12
  21. package/build/docs.generated.js +2 -1
  22. package/build/doctor.js +77 -11
  23. package/build/drive-epic-key.js +12 -0
  24. package/build/drive-epic.js +1211 -116
  25. package/build/epic-coherence-input-cli.js +137 -0
  26. package/build/epic-coherence-input.js +920 -0
  27. package/build/epic-coherence-input.test-fixtures.js +175 -0
  28. package/build/epic-implementer-bin.js +35 -29
  29. package/build/epic-implementer-bundle-cli.js +14 -1
  30. package/build/epic-launch-profile.js +14 -5
  31. package/build/epic-run-status.js +5 -0
  32. package/build/epic-shared/bridge-client.js +46 -4
  33. package/build/executor/cli.js +39 -0
  34. package/build/executor/completion-contract.js +423 -0
  35. package/build/executor/completion-supervision.js +286 -0
  36. package/build/executor/credentials.js +3 -1
  37. package/build/executor/deps.js +65 -1
  38. package/build/executor/env.js +6 -0
  39. package/build/executor/heartbeat.js +13 -3
  40. package/build/executor/http-client.js +76 -4
  41. package/build/executor/job-errors.js +48 -0
  42. package/build/executor/job-runner.js +1294 -26
  43. package/build/executor/job-types.js +10 -2
  44. package/build/executor/preflight.js +99 -0
  45. package/build/executor/process-containment.js +756 -0
  46. package/build/executor/process.js +235 -5
  47. package/build/executor/prompt-spec.js +262 -1
  48. package/build/executor/provider-checks.js +69 -0
  49. package/build/executor/rebase-finalization.js +351 -0
  50. package/build/executor/reconcile-overlap-job.js +77 -84
  51. package/build/executor/required-verification.js +313 -0
  52. package/build/executor/required-work-ledger.js +1121 -0
  53. package/build/executor/results.js +142 -0
  54. package/build/executor/resume-pre-spawn.js +87 -4
  55. package/build/executor/runner.js +145 -0
  56. package/build/executor/test-declaration.js +20 -0
  57. package/build/executor/types.js +12 -0
  58. package/build/executor/worker-command.js +49 -13
  59. package/build/executor/worker-finalization.js +88 -11
  60. package/build/executor/worker-guard-hook.js +29 -4
  61. package/build/executor/worktree.js +13 -0
  62. package/build/executor-launch.js +658 -0
  63. package/build/index.js +537 -943
  64. package/build/install-bridge-conductor.js +231 -41
  65. package/build/merge-pull-request.js +1 -1
  66. package/build/pipelines.generated.js +8 -7
  67. package/build/plan-discovery.js +137 -0
  68. package/build/plan-epic-implementer-eligibility.js +2 -2
  69. package/build/plane/cli.js +37 -3
  70. package/build/plane/member-logs.js +3 -0
  71. package/build/plane/member-roster.js +7 -1
  72. package/build/plane/shutdown.js +59 -1
  73. package/build/plane/supervisor.js +142 -0
  74. package/build/readme.generated.js +1 -1
  75. package/build/scripts/executor-protocol-contract-driver.js +13 -3
  76. package/build/setup-epic.js +28 -17
  77. package/build/version.generated.js +3 -3
  78. package/build/worker-guard-hook-bin.js +2 -2
  79. package/docs/CONDUCTOR.md +116 -2
  80. package/package.json +2 -1
@@ -1,13 +1,14 @@
1
1
  /**
2
- * `conductor` operator recovery and repair verbs — ten CLI-only commands.
2
+ * `conductor` operator recovery and repair verbs — twelve CLI-only commands.
3
3
  *
4
4
  * The four BAPI-872 recovery verbs: `stop-run`, `abandon-run`, `unpark`,
5
5
  * `adopt-current-head-and-unpark`. The six run-addressed repair verbs: BAPI-1154's
6
6
  * `scope-reparse`, `dispatch-remediation`, `merge-child`, and BAPI-1176's
7
- * `scope-catch-up`, `patch-policy`, `resume-run`.
7
+ * `scope-catch-up`, `patch-policy`, `resume-run`. BAPI-1300's parked-child
8
+ * continuation pair: `continue-child` and the read-only `continuation-receipt`.
8
9
  *
9
10
  * CLI-only by design — this is an explicit ticket non-goal, not an oversight:
10
- * none of these ten command names is ever registered as an MCP tool, tool
11
+ * none of these twelve command names is ever registered as an MCP tool, tool
11
12
  * description, or generated tool-schema input (`index-static.test.ts` guards
12
13
  * this). They exist so an operator (or `plane down`, via
13
14
  * `recovery-operations.ts`) can recover a stuck run or ticket WITHOUT
@@ -23,7 +24,7 @@
23
24
  */
24
25
  import { readFile } from "node:fs/promises";
25
26
  import { ConductorValidationError, toConductorErrorEnvelope } from "./errors.js";
26
- import { DRIVER_CONTEXT_MAX_BYTES, POLICY_PATCH_MAX_BYTES } from "./repair-contract.js";
27
+ import { DRIVER_CONTEXT_MAX_BYTES, POLICY_PATCH_MAX_BYTES, RESUME_RUN_UNCONFIRMED_MESSAGE, } from "./repair-contract.js";
27
28
  // ---------------------------------------------------------------------------
28
29
  // Flag parsing
29
30
  // ---------------------------------------------------------------------------
@@ -43,6 +44,12 @@ const REJECTED_VERSION_FLAGS = new Set([
43
44
  "--expected_row_version",
44
45
  "--expected-generation",
45
46
  "--expected_generation",
47
+ // BAPI-1300: the continuation's park-episode binding is read once and stored
48
+ // by the client itself; an operator never supplies it.
49
+ "--park-generation",
50
+ "--park_generation",
51
+ "--source-park-generation",
52
+ "--source_park_generation",
46
53
  ]);
47
54
  /**
48
55
  * Tokenize `--flag value` / `--flag=value` / boolean flags for the recovery
@@ -266,16 +273,52 @@ const ADOPT_CURRENT_HEAD_USAGE = [
266
273
  "off its anchored commit: adopt the CURRENT PR head and unpark in one step.",
267
274
  "Retries a bounded number of times on its own if the ticket changes",
268
275
  "concurrently — never accepts a version counter.",
276
+ "",
277
+ "For a ticket parked as unexplained_review_head_drift this is \"Adopt current",
278
+ "head and resume CI\": the server reads the provider's current head, accepts it,",
279
+ "keeps every repair allowance already spent (no repair attempt is granted), and",
280
+ "resumes the ticket at CI. No worker is started and nothing is marked passed —",
281
+ "CI and review must pass again on the accepted head. A repeat of the same",
282
+ "request reports the recorded result instead of adopting twice.",
269
283
  ].join("\n");
270
- function renderTicketRecoveryResult(result, json) {
284
+ /** BAPI-1310 (E1): the fixed allowance sentence the adoption result carries. */
285
+ const REVIEW_HEAD_ALLOWANCE_LINE = "Allowance effect: repair allowances preserved; acceptance does not grant another repair.";
286
+ /** BAPI-1310 (E1): the warning shown when run control still holds the run. */
287
+ export const REVIEW_HEAD_HELD_WARNING = "Head accepted; run remains held by run control";
288
+ /** Render the E1 adoption result (committed or replayed) to stdout. */
289
+ function renderReviewHeadRecovery(ticketKey, epicRunId, recovery) {
290
+ const outcome = recovery.result === "replayed"
291
+ ? "replayed — this operation already committed; nothing changed now"
292
+ : "committed";
293
+ console.log(`Ticket ${ticketKey} (run ${epicRunId}): Adopt current head and resume CI — ${outcome}.`);
294
+ console.log(` Previous expected head: ${recovery.previous_expected_head_sha}`);
295
+ console.log(` Committed accepted head: ${recovery.accepted_head_sha}`);
296
+ console.log(` ${REVIEW_HEAD_ALLOWANCE_LINE}`);
297
+ console.log(` Resumed at: ${recovery.resumed_gate}`);
298
+ console.log(` Current gate: ${recovery.current_gate ?? "unavailable"}`);
299
+ console.log(" CI and review must pass again on the accepted head; no worker was started and nothing was marked passed.");
300
+ if (recovery.held_by_run_control) {
301
+ console.log(` WARNING: ${REVIEW_HEAD_HELD_WARNING}.`);
302
+ }
303
+ }
304
+ export function renderTicketRecoveryResult(result, json) {
271
305
  if (json) {
272
306
  console.log(JSON.stringify(result));
273
307
  return result.ok ? 0 : 1;
274
308
  }
275
309
  if (result.ok) {
310
+ if (result.reviewHeadRecovery) {
311
+ renderReviewHeadRecovery(result.ticketKey, result.epicRunId, result.reviewHeadRecovery);
312
+ return 0;
313
+ }
276
314
  console.log(`Ticket ${result.ticketKey} (run ${result.epicRunId}): unparked — status: ${result.status}.`);
277
315
  return 0;
278
316
  }
317
+ if (result.kind === "refused") {
318
+ console.error(`Ticket ${result.ticketKey} (run ${result.epicRunId}): refused (${result.errorCode}) — ` +
319
+ `${result.message}. The ticket remains parked; nothing was changed.`);
320
+ return 1;
321
+ }
279
322
  if (result.kind === "ticket-not-found") {
280
323
  console.error(`Ticket ${result.ticketKey}: not found in run ${result.epicRunId}.`);
281
324
  return 1;
@@ -440,7 +483,7 @@ function renderRepairResult(outcome, input, json, extraLines = () => []) {
440
483
  }
441
484
  console.log(JSON.stringify(outcome.kind === "refused"
442
485
  ? { ok: false, kind: "refused", code: outcome.code, message: outcome.message }
443
- : { ok: false, kind: "unavailable", message: outcome.message }));
486
+ : { ok: false, kind: "unavailable", message: input.unavailableMessage ?? outcome.message }));
444
487
  return 1;
445
488
  }
446
489
  if (!outcome.ok) {
@@ -448,6 +491,10 @@ function renderRepairResult(outcome, input, json, extraLines = () => []) {
448
491
  console.error(`Error: ${input.verb} refused on run ${input.epicRunId} (${input.targetLabel}) — ` +
449
492
  `${outcome.code}: ${outcome.message}`);
450
493
  }
494
+ else if (input.unavailableMessage) {
495
+ console.error(`Error: ${input.verb} on run ${input.epicRunId} (${input.targetLabel}) — ` +
496
+ `${input.unavailableMessage}.`);
497
+ }
451
498
  else {
452
499
  console.error(`Error: ${input.verb} could not complete on run ${input.epicRunId} ` +
453
500
  `(${input.targetLabel}) — ${outcome.message}.`);
@@ -788,11 +835,16 @@ export async function runPatchPolicyCommand(argv, deps = {}) {
788
835
  const RESUME_RUN_USAGE = [
789
836
  "Usage: epic-implementer resume-run --epic-run-id <id> [--clear-quarantine] [--json]",
790
837
  "",
791
- "Clear a paused run's control record and return it to active, after the server",
792
- "re-checks the run's activation requirements. A quarantined run resumes only with",
793
- "--clear-quarantine; a stopped run never resumes (start a new run). A run that is",
794
- "already active with no control record is a successful no-op. The server retries",
795
- "a concurrent control change itself — never supply a generation.",
838
+ "Return a held run to active, after the server re-checks the run's activation",
839
+ "requirements. A paused run's control record is cleared. A run blocked by an",
840
+ "eligible integration block has that block lifted (there is no control record",
841
+ "to clear); the server decides eligibility. A quarantined run resumes only with",
842
+ "--clear-quarantine; a stopped run never resumes (start a new run). A run that",
843
+ "is already active and unheld is a successful no-op (already current). The",
844
+ "server retries a concurrent control change itself — never supply a generation.",
845
+ "",
846
+ "If the response is lost or cannot be read, the resume may or may not have",
847
+ "committed: inspect the run read-only before any separately authorized retry.",
796
848
  ].join("\n");
797
849
  export async function runResumeRunCommand(argv) {
798
850
  try {
@@ -812,8 +864,9 @@ export async function runResumeRunCommand(argv) {
812
864
  return renderRepairResult(outcome, {
813
865
  verb: "resume-run",
814
866
  epicRunId: parsed.epicRunId,
815
- targetLabel: parsed.clearQuarantine ? "run control (clear quarantine)" : "run control",
816
- stateLabel: "run control",
867
+ targetLabel: parsed.clearQuarantine ? "run (clear quarantine)" : "run",
868
+ stateLabel: "run state",
869
+ unavailableMessage: RESUME_RUN_UNCONFIRMED_MESSAGE,
817
870
  }, parsed.json);
818
871
  }
819
872
  catch (error) {
@@ -821,3 +874,291 @@ export async function runResumeRunCommand(argv) {
821
874
  return 1;
822
875
  }
823
876
  }
877
+ // ---------------------------------------------------------------------------
878
+ // continue-child / continuation-receipt (BAPI-1300)
879
+ // ---------------------------------------------------------------------------
880
+ const CONTINUE_CHILD_VALUE_FLAGS = new Set(["--epic-run-id", "--ticket-key", "--reason"]);
881
+ const CONTINUATION_RECEIPT_VALUE_FLAGS = new Set(["--epic-run-id", "--ticket-key", "--request-id"]);
882
+ const MAX_REASON_CHARS = 500;
883
+ const CONTINUE_CHILD_USAGE = [
884
+ "Usage: epic-implementer continue-child --epic-run-id <id> --ticket-key <key> [--reason <text>] [--json]",
885
+ "",
886
+ "Continue a parked child's CURRENT park episode without resetting it: the same",
887
+ "attempt resumes on its retained work with only the allowance it has left (no",
888
+ "fresh budget, no counter or clock reset — use unpark for that). The server",
889
+ "commits one job and one durable receipt together, at most once per episode.",
890
+ "",
891
+ "If the response is lost, the outcome is reported UNCONFIRMED, never refused or",
892
+ "committed. Re-running continue-child reuses the SAME request (it never",
893
+ "approves a new head or episode); continuation-receipt looks the receipt up.",
894
+ "Never accepts a version counter or a park generation.",
895
+ ].join("\n");
896
+ const CONTINUATION_RECEIPT_USAGE = [
897
+ "Usage: epic-implementer continuation-receipt --epic-run-id <id> --ticket-key <key> [--request-id <id>] [--json]",
898
+ "",
899
+ "Read-only. Show the child's current park episode and, for the pending local",
900
+ "continuation request (or --request-id), whether its receipt proves a commit.",
901
+ "A missing receipt is not a refusal. Looking history up authorizes nothing.",
902
+ ].join("\n");
903
+ function parseContinuationArgs(argv, valueFlags) {
904
+ const { values, bools } = tokenizeRecoveryFlags(argv, valueFlags, RECOVERY_BOOL_FLAGS);
905
+ if (bools.has("--help")) {
906
+ return { epicRunId: "", ticketKey: "", json: bools.has("--json"), help: true };
907
+ }
908
+ const epicRunId = requireFlag(values, "--epic-run-id");
909
+ const ticketKey = requireFlag(values, "--ticket-key");
910
+ const reason = values.get("--reason")?.trim();
911
+ if (reason !== undefined && reason.length > MAX_REASON_CHARS) {
912
+ throw new ConductorValidationError(`Flag "--reason" must be at most ${MAX_REASON_CHARS} characters.`);
913
+ }
914
+ const requestId = values.get("--request-id")?.trim();
915
+ if (requestId !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._:-]{7,63}$/.test(requestId)) {
916
+ throw new ConductorValidationError('Flag "--request-id" is not a valid request id.');
917
+ }
918
+ return {
919
+ epicRunId,
920
+ ticketKey,
921
+ ...(reason ? { reason } : {}),
922
+ ...(requestId ? { requestId } : {}),
923
+ json: bools.has("--json"),
924
+ help: false,
925
+ };
926
+ }
927
+ /** Exported for tests: parse `continue-child` arguments. */
928
+ export function parseContinueChildArgs(argv) {
929
+ return parseContinuationArgs(argv, CONTINUE_CHILD_VALUE_FLAGS);
930
+ }
931
+ /** Exported for tests: parse `continuation-receipt` arguments. */
932
+ export function parseContinuationReceiptArgs(argv) {
933
+ return parseContinuationArgs(argv, CONTINUATION_RECEIPT_VALUE_FLAGS);
934
+ }
935
+ function receiptSummary(receipt) {
936
+ return {
937
+ receipt_id: receipt.receipt_id,
938
+ request_id: receipt.request_id,
939
+ gate: receipt.gate,
940
+ job_type: receipt.job_type,
941
+ continuation_job_id: receipt.continuation_job_id,
942
+ original_job_id: receipt.original_job_id,
943
+ source_park_generation: receipt.source_park_generation,
944
+ resulting_park_generation: receipt.resulting_park_generation,
945
+ committed_at: receipt.committed_at,
946
+ runtime_outcome: receipt.runtime_outcome,
947
+ };
948
+ }
949
+ function currentSummary(current) {
950
+ if (current === null)
951
+ return null;
952
+ return {
953
+ status: current.status,
954
+ parked: current.parked,
955
+ parked_gate: current.parked_gate,
956
+ park_generation: current.park_generation,
957
+ parked_age_seconds: current.parked_age_seconds,
958
+ needs_human_reason: current.needs_human_reason,
959
+ unresolved_obligations: current.unresolved_obligations,
960
+ continuations_used: current.continuations_used,
961
+ continuation_limit: current.continuation_limit,
962
+ continuation_available: current.continuation_available,
963
+ continuation_unavailable_reason: current.continuation_unavailable_reason,
964
+ };
965
+ }
966
+ function currentLine(current) {
967
+ if (!current.parked) {
968
+ return `Current: status ${current.status} (not parked).`;
969
+ }
970
+ const age = current.parked_age_seconds === null ? "" : `, parked ${current.parked_age_seconds}s`;
971
+ const available = current.continuation_available
972
+ ? "continuation available"
973
+ : `continuation unavailable (${current.continuation_unavailable_reason ?? "unknown"})`;
974
+ return (`Current: parked at gate ${current.parked_gate} (episode ${current.park_generation}${age}); ` +
975
+ `${available}.`);
976
+ }
977
+ /** Exported for tests: render a `continue-child` result. Returns the exit code. */
978
+ export function renderContinueChildResult(result, target, json) {
979
+ const label = `Ticket ${target.ticketKey} (run ${target.epicRunId})`;
980
+ if (result.kind === "decided") {
981
+ const r = result.result;
982
+ const ok = r.outcome === "committed" || r.outcome === "already_continued";
983
+ if (json) {
984
+ console.log(JSON.stringify({
985
+ ok,
986
+ kind: r.outcome,
987
+ reason_code: r.reason_code,
988
+ explanation: r.explanation,
989
+ request_id: result.requestId,
990
+ replayed: r.replayed,
991
+ retryable: r.retryable,
992
+ reference_reused: result.reusedReference,
993
+ receipt: r.receipt === null ? null : receiptSummary(r.receipt),
994
+ current: currentSummary(r.current),
995
+ }));
996
+ return ok ? 0 : 1;
997
+ }
998
+ const receipt = r.receipt;
999
+ switch (r.outcome) {
1000
+ case "committed":
1001
+ console.log(`${label}: continuation COMMITTED${r.replayed ? " (replayed — already committed earlier)" : ""} — ` +
1002
+ `job #${receipt?.continuation_job_id} (${receipt?.job_type}) dispatched for park episode ` +
1003
+ `${receipt?.source_park_generation}; receipt ${receipt?.receipt_id}. This proves the dispatch, ` +
1004
+ "not that the work has finished.");
1005
+ return 0;
1006
+ case "already_continued":
1007
+ console.log(`${label}: this park episode was ALREADY CONTINUED by another request — job ` +
1008
+ `#${receipt?.continuation_job_id}, receipt ${receipt?.receipt_id}. Nothing was dispatched ` +
1009
+ "for this request.");
1010
+ return 0;
1011
+ case "stale_historical_episode":
1012
+ console.error(`${label}: STALE HISTORICAL EPISODE — receipt ${receipt?.receipt_id} (job ` +
1013
+ `#${receipt?.continuation_job_id}) belongs to park episode ${receipt?.source_park_generation}, ` +
1014
+ "which is history. The child has parked again and was NOT resumed by it.");
1015
+ if (r.current)
1016
+ console.error(currentLine(r.current));
1017
+ return 1;
1018
+ case "unsupported":
1019
+ console.error(`${label}: continuation UNSUPPORTED (${r.reason_code}) — ${r.explanation}`);
1020
+ return 1;
1021
+ default:
1022
+ console.error(`${label}: continuation REFUSED (${r.reason_code}) — ${r.explanation}`);
1023
+ if (r.retryable)
1024
+ console.error("This refusal is retryable; nothing was written.");
1025
+ return 1;
1026
+ }
1027
+ }
1028
+ if (result.kind === "refused_http") {
1029
+ if (json) {
1030
+ console.log(JSON.stringify({
1031
+ ok: false,
1032
+ kind: "refused",
1033
+ status: result.status,
1034
+ code: result.errorCode,
1035
+ request_id: result.requestId,
1036
+ }));
1037
+ return 1;
1038
+ }
1039
+ console.error(`${label}: request REJECTED before anything was written (status ${result.status}` +
1040
+ `${result.errorCode ? `, ${result.errorCode}` : ""}).`);
1041
+ return 1;
1042
+ }
1043
+ if (result.kind === "not_continuable") {
1044
+ if (json) {
1045
+ console.log(JSON.stringify({ ok: false, kind: "not_continuable", current: currentSummary(result.current) }));
1046
+ return 1;
1047
+ }
1048
+ console.error(`${label}: not continuable right now. Nothing was sent.`);
1049
+ console.error(currentLine(result.current));
1050
+ return 1;
1051
+ }
1052
+ if (result.kind === "unconfirmed") {
1053
+ if (json) {
1054
+ console.log(JSON.stringify({
1055
+ ok: false,
1056
+ kind: "unconfirmed",
1057
+ transport: result.transport,
1058
+ request_id: result.requestId,
1059
+ reference_reused: result.reusedReference,
1060
+ }));
1061
+ return 1;
1062
+ }
1063
+ console.error(`${label}: continuation UNCONFIRMED after ${result.transport} — the request may or may not have ` +
1064
+ `committed, and no receipt proves it yet (request ${result.requestId}).`);
1065
+ console.error(`Look it up: epic-implementer continuation-receipt --epic-run-id ${target.epicRunId} ` +
1066
+ `--ticket-key ${target.ticketKey}. Re-running continue-child reuses this same request; it ` +
1067
+ "never starts a new one.");
1068
+ return 1;
1069
+ }
1070
+ if (json) {
1071
+ console.log(JSON.stringify({ ok: false, kind: "unavailable", message: result.message }));
1072
+ return 1;
1073
+ }
1074
+ console.error(`${label}: could not continue — ${result.message}. Nothing was sent.`);
1075
+ return 1;
1076
+ }
1077
+ /** Exported for tests: render a `continuation-receipt` lookup. */
1078
+ export function renderContinuationReceiptResult(result, target, json) {
1079
+ const label = `Ticket ${target.ticketKey} (run ${target.epicRunId})`;
1080
+ if (result.kind === "unavailable") {
1081
+ if (json) {
1082
+ console.log(JSON.stringify({ ok: false, kind: "unavailable", message: result.message }));
1083
+ }
1084
+ else {
1085
+ console.error(`${label}: could not read — ${result.message}.`);
1086
+ }
1087
+ return 1;
1088
+ }
1089
+ const status = result.status;
1090
+ if (json) {
1091
+ console.log(JSON.stringify({
1092
+ ok: true,
1093
+ kind: result.kind,
1094
+ request_id: result.requestId,
1095
+ outcome: status.outcome,
1096
+ receipt: status.receipt === null ? null : receiptSummary(status.receipt),
1097
+ current: currentSummary(status.current),
1098
+ }));
1099
+ return 0;
1100
+ }
1101
+ if (result.kind === "found" && status.receipt !== null) {
1102
+ const r = status.receipt;
1103
+ console.log(`${label}: receipt ${r.receipt_id} — continuation COMMITTED at ${r.committed_at}: job ` +
1104
+ `#${r.continuation_job_id} (${r.job_type}, runtime ${r.runtime_outcome ?? "unknown"}) for park ` +
1105
+ `episode ${r.source_park_generation}.`);
1106
+ if (status.outcome === "stale_historical_episode") {
1107
+ console.log("That episode is history: the child has parked again since.");
1108
+ }
1109
+ }
1110
+ else if (result.requestId !== null) {
1111
+ console.log(`${label}: no receipt for request ${result.requestId} — its commit is NOT proven (this is not a refusal).`);
1112
+ }
1113
+ else {
1114
+ console.log(`${label}: no pending local continuation request.`);
1115
+ }
1116
+ console.log(currentLine(status.current));
1117
+ return 0;
1118
+ }
1119
+ export async function runContinueChildCommand(argv) {
1120
+ try {
1121
+ const parsed = parseContinueChildArgs(argv);
1122
+ if (parsed.help) {
1123
+ console.log(CONTINUE_CHILD_USAGE);
1124
+ return 0;
1125
+ }
1126
+ const access = await resolveRecoveryAccess(parsed.json);
1127
+ if (!access)
1128
+ return 1;
1129
+ const { continueChildOnce } = await import("./recovery-operations.js");
1130
+ const result = await continueChildOnce(access, {
1131
+ epicRunId: parsed.epicRunId,
1132
+ ticketKey: parsed.ticketKey,
1133
+ ...(parsed.reason ? { reason: parsed.reason } : {}),
1134
+ });
1135
+ return renderContinueChildResult(result, parsed, parsed.json);
1136
+ }
1137
+ catch (error) {
1138
+ console.error(`Error: ${toConductorErrorEnvelope(error).message}`);
1139
+ return 1;
1140
+ }
1141
+ }
1142
+ export async function runContinuationReceiptCommand(argv) {
1143
+ try {
1144
+ const parsed = parseContinuationReceiptArgs(argv);
1145
+ if (parsed.help) {
1146
+ console.log(CONTINUATION_RECEIPT_USAGE);
1147
+ return 0;
1148
+ }
1149
+ const access = await resolveRecoveryAccess(parsed.json);
1150
+ if (!access)
1151
+ return 1;
1152
+ const { lookupContinuationReceipt } = await import("./recovery-operations.js");
1153
+ const result = await lookupContinuationReceipt(access, {
1154
+ epicRunId: parsed.epicRunId,
1155
+ ticketKey: parsed.ticketKey,
1156
+ ...(parsed.requestId ? { requestId: parsed.requestId } : {}),
1157
+ });
1158
+ return renderContinuationReceiptResult(result, parsed, parsed.json);
1159
+ }
1160
+ catch (error) {
1161
+ console.error(`Error: ${toConductorErrorEnvelope(error).message}`);
1162
+ return 1;
1163
+ }
1164
+ }