@h-rig/cli 0.0.6-alpha.60 → 0.0.6-alpha.62

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.
@@ -5,8 +5,7 @@ import { resolve } from "path";
5
5
  import {
6
6
  readAuthorityRun,
7
7
  readJsonlFile,
8
- resolveAuthorityRunDir,
9
- writeJsonFile
8
+ writeAuthorityRunRecord
10
9
  } from "@rig/runtime/control-plane/authority-files";
11
10
 
12
11
  // packages/cli/src/commands/_paths.ts
@@ -100,7 +99,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
100
99
  } else if ("errorText" in next) {
101
100
  delete next.errorText;
102
101
  }
103
- writeJsonFile(resolve(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
102
+ writeAuthorityRunRecord(projectRoot, input.runId, next);
104
103
  return next;
105
104
  }
106
105
  export {
@@ -112,6 +112,7 @@ var PRIMARY_GROUPS = [
112
112
  { command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
113
113
  { command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
114
114
  { command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events." },
115
+ { command: "replay <run-id>|--run <id> [--with-session]", description: "Print the run's consolidated run.jsonl as a merged timeline; --with-session interleaves the Pi session log." },
115
116
  { command: "resume", description: "Resume the most recent interrupted local run." },
116
117
  { command: "restart", description: "Restart the most recent local run from a clean runtime." },
117
118
  { command: "delete|cleanup", description: "Remove completed run records/artifacts." }
@@ -12,11 +12,13 @@ import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
12
12
 
13
13
  // packages/cli/src/commands/_run-driver-helpers.ts
14
14
  import {
15
- appendJsonlRecord,
16
15
  appendRunLifecycleEvent,
16
+ appendRunLogEntry,
17
+ appendRunTimelineEntry,
18
+ patchAuthorityRunRecord,
17
19
  readAuthorityRun as readAuthorityRun2,
18
- resolveAuthorityRunDir as resolveAuthorityRunDir2,
19
- writeJsonFile as writeJsonFile2
20
+ resolveAuthorityRunDir,
21
+ writeJsonFile
20
22
  } from "@rig/runtime/control-plane/authority-files";
21
23
  import { taskLookup } from "@rig/runtime/control-plane/native/task-ops";
22
24
  import { readPriorPrProgressPromptSection } from "@rig/runtime/control-plane/prompt-context";
@@ -29,8 +31,7 @@ import { resolve as resolve2 } from "path";
29
31
  import {
30
32
  readAuthorityRun,
31
33
  readJsonlFile,
32
- resolveAuthorityRunDir,
33
- writeJsonFile
34
+ writeAuthorityRunRecord
34
35
  } from "@rig/runtime/control-plane/authority-files";
35
36
 
36
37
  // packages/cli/src/commands/_paths.ts
@@ -64,16 +65,10 @@ function readLatestBeadRecord(projectRoot, taskId) {
64
65
 
65
66
  // packages/cli/src/commands/_run-driver-helpers.ts
66
67
  function patchAuthorityRun(projectRoot, runId, patch) {
67
- const current = readAuthorityRun2(projectRoot, runId);
68
- if (!current) {
68
+ const next = patchAuthorityRunRecord(projectRoot, runId, patch);
69
+ if (!next) {
69
70
  throw new CliError2(`Run not found: ${runId}`, 2);
70
71
  }
71
- const next = {
72
- ...current,
73
- ...patch,
74
- updatedAt: new Date().toISOString()
75
- };
76
- writeJsonFile2(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
77
72
  return next;
78
73
  }
79
74
  function touchAuthorityRun(projectRoot, runId) {
@@ -81,21 +76,21 @@ function touchAuthorityRun(projectRoot, runId) {
81
76
  if (!current) {
82
77
  return;
83
78
  }
84
- writeJsonFile2(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), {
79
+ writeJsonFile(resolve3(resolveAuthorityRunDir(projectRoot, runId), "run.json"), {
85
80
  ...current,
86
81
  updatedAt: new Date().toISOString()
87
82
  });
88
83
  }
89
84
  function appendRunTimeline(projectRoot, runId, value) {
90
- appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "timeline.jsonl"), value);
85
+ appendRunTimelineEntry(projectRoot, runId, value);
91
86
  touchAuthorityRun(projectRoot, runId);
92
87
  }
93
88
  function appendRunLog(projectRoot, runId, value) {
94
- appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "logs.jsonl"), value);
89
+ appendRunLogEntry(projectRoot, runId, value);
95
90
  touchAuthorityRun(projectRoot, runId);
96
91
  }
97
92
  function appendRunAction(projectRoot, runId, value) {
98
- appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "timeline.jsonl"), {
93
+ appendRunTimelineEntry(projectRoot, runId, {
99
94
  id: value.id,
100
95
  type: "action",
101
96
  actionType: value.actionType,
@@ -0,0 +1,142 @@
1
+ // @bun
2
+ // packages/cli/src/commands/_run-replay.ts
3
+ import { existsSync, readdirSync } from "fs";
4
+ import { join, resolve } from "path";
5
+ import {
6
+ readAuthorityRun,
7
+ readJsonlFile,
8
+ runLifecycleLogPath
9
+ } from "@rig/runtime/control-plane/authority-files";
10
+ function asRecord(value) {
11
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
12
+ }
13
+ function text(value) {
14
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
15
+ }
16
+ function snippet(value, max = 120) {
17
+ const raw = text(value);
18
+ if (!raw)
19
+ return null;
20
+ const flat = raw.replace(/\s+/g, " ");
21
+ return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
22
+ }
23
+ function summarizeLifecycleEntry(entry) {
24
+ const type = text(entry.type) ?? "event";
25
+ switch (type) {
26
+ case "status": {
27
+ const anchor = text(entry.sessionId);
28
+ return [
29
+ `status \u2192 ${text(entry.status) ?? "(unknown)"}`,
30
+ snippet(entry.detail) ? `\u2014 ${snippet(entry.detail)}` : null,
31
+ anchor ? `[session ${anchor}]` : null
32
+ ].filter(Boolean).join(" ");
33
+ }
34
+ case "timeline-entry": {
35
+ const payload = asRecord(entry.payload) ?? {};
36
+ const kind = text(payload.type) ?? "entry";
37
+ const body = snippet(payload.title) ?? snippet(payload.text) ?? snippet(payload.detail);
38
+ const state = text(payload.state);
39
+ return [`timeline ${kind}`, body ? `\u2014 ${body}` : null, state ? `(${state})` : null].filter(Boolean).join(" ");
40
+ }
41
+ case "log-entry": {
42
+ const payload = asRecord(entry.payload) ?? {};
43
+ const title = snippet(payload.title) ?? "log";
44
+ const detail = snippet(payload.detail);
45
+ return [`log ${title}`, detail ? `\u2014 ${detail}` : null].filter(Boolean).join(" ");
46
+ }
47
+ case "record-patch": {
48
+ const patch = asRecord(entry.patch) ?? {};
49
+ const keys = Object.keys(patch);
50
+ const shown = keys.slice(0, 8).join(", ");
51
+ return `record-patch {${shown}${keys.length > 8 ? `, +${keys.length - 8} more` : ""}}`;
52
+ }
53
+ case "timeline":
54
+ return "timeline updated (signal)";
55
+ case "log":
56
+ return `log signal${snippet(entry.title) ? ` \u2014 ${snippet(entry.title)}` : ""}`;
57
+ case "completed":
58
+ return "run completed";
59
+ case "failed":
60
+ return `run failed${snippet(entry.error) ? ` \u2014 ${snippet(entry.error)}` : ""}`;
61
+ default:
62
+ return type;
63
+ }
64
+ }
65
+ function summarizeSessionEntry(entry) {
66
+ const type = text(entry.type) ?? "entry";
67
+ const message = asRecord(entry.message);
68
+ const role = text(message?.role);
69
+ const content = message?.content;
70
+ let body = null;
71
+ if (typeof content === "string") {
72
+ body = snippet(content);
73
+ } else if (Array.isArray(content)) {
74
+ body = snippet(content.map((part) => text(asRecord(part)?.text) ?? "").filter(Boolean).join(" "));
75
+ }
76
+ return [
77
+ `pi ${type}`,
78
+ role ? `(${role})` : null,
79
+ body ? `\u2014 ${body}` : null
80
+ ].filter(Boolean).join(" ");
81
+ }
82
+ function sessionEntryTimestamp(entry) {
83
+ return text(entry.timestamp) ?? text(entry.at) ?? text(entry.createdAt) ?? null;
84
+ }
85
+ function resolveRunSessionFile(record) {
86
+ const piSession = record?.piSession ?? null;
87
+ if (!piSession)
88
+ return null;
89
+ const recorded = text(piSession.sessionFile);
90
+ if (recorded && existsSync(recorded)) {
91
+ return recorded;
92
+ }
93
+ const cwd = text(piSession.cwd);
94
+ const sessionId = text(piSession.sessionId);
95
+ if (!cwd || !sessionId)
96
+ return null;
97
+ const sessionDir = resolve(cwd, ".rig", "session");
98
+ try {
99
+ const match = readdirSync(sessionDir).find((name) => name.endsWith(`_${sessionId}.jsonl`));
100
+ return match ? join(sessionDir, match) : null;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+ function buildRunReplay(projectRoot, runId, options = {}) {
106
+ const logPath = runLifecycleLogPath(projectRoot, runId);
107
+ const record = readAuthorityRun(projectRoot, runId);
108
+ const lifecycleEntries = readJsonlFile(logPath).map((entry) => asRecord(entry)).filter((entry) => entry !== null);
109
+ const merged = lifecycleEntries.map((entry) => ({
110
+ at: text(entry.at),
111
+ source: "run",
112
+ summary: summarizeLifecycleEntry(entry)
113
+ }));
114
+ let sessionFile = null;
115
+ let sessionEntryCount = 0;
116
+ if (options.withSession) {
117
+ sessionFile = resolveRunSessionFile(record);
118
+ if (sessionFile) {
119
+ let lastSeenAt = null;
120
+ const sessionLines = readJsonlFile(sessionFile).map((entry) => asRecord(entry)).filter((entry) => entry !== null).map((entry) => {
121
+ lastSeenAt = sessionEntryTimestamp(entry) ?? lastSeenAt;
122
+ return { at: lastSeenAt, source: "session", summary: summarizeSessionEntry(entry) };
123
+ });
124
+ sessionEntryCount = sessionLines.length;
125
+ merged.push(...sessionLines);
126
+ merged.sort((left, right) => (left.at ?? "").localeCompare(right.at ?? ""));
127
+ }
128
+ }
129
+ const lines = merged.map((line) => `${line.at ?? "(no timestamp) "} ${line.source === "run" ? "run " : "session"} ${line.summary}`);
130
+ return {
131
+ runId,
132
+ logPath,
133
+ sessionFile,
134
+ entryCount: lifecycleEntries.length,
135
+ sessionEntryCount,
136
+ lines
137
+ };
138
+ }
139
+ export {
140
+ resolveRunSessionFile,
141
+ buildRunReplay
142
+ };
@@ -62,8 +62,7 @@ import { resolve } from "path";
62
62
  import {
63
63
  readAuthorityRun,
64
64
  readJsonlFile,
65
- resolveAuthorityRunDir,
66
- writeJsonFile
65
+ writeAuthorityRunRecord
67
66
  } from "@rig/runtime/control-plane/authority-files";
68
67
 
69
68
  // packages/cli/src/commands/_paths.ts
@@ -157,7 +156,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
157
156
  } else if ("errorText" in next) {
158
157
  delete next.errorText;
159
158
  }
160
- writeJsonFile(resolve(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
159
+ writeAuthorityRunRecord(projectRoot, input.runId, next);
161
160
  return next;
162
161
  }
163
162
 
@@ -50,7 +50,7 @@ Usage: ${usage}`);
50
50
  // packages/cli/src/commands/run.ts
51
51
  import {
52
52
  listAuthorityRuns,
53
- readAuthorityRun
53
+ readAuthorityRun as readAuthorityRun2
54
54
  } from "@rig/runtime/control-plane/authority-files";
55
55
  import {
56
56
  cleanupRunState,
@@ -429,6 +429,144 @@ async function buildRunPiEventsWebSocketUrl(context, runId) {
429
429
  return url.toString();
430
430
  }
431
431
 
432
+ // packages/cli/src/commands/_run-replay.ts
433
+ import { existsSync as existsSync3, readdirSync } from "fs";
434
+ import { join, resolve as resolve3 } from "path";
435
+ import {
436
+ readAuthorityRun,
437
+ readJsonlFile,
438
+ runLifecycleLogPath
439
+ } from "@rig/runtime/control-plane/authority-files";
440
+ function asRecord(value) {
441
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
442
+ }
443
+ function text(value) {
444
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
445
+ }
446
+ function snippet(value, max = 120) {
447
+ const raw = text(value);
448
+ if (!raw)
449
+ return null;
450
+ const flat = raw.replace(/\s+/g, " ");
451
+ return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
452
+ }
453
+ function summarizeLifecycleEntry(entry) {
454
+ const type = text(entry.type) ?? "event";
455
+ switch (type) {
456
+ case "status": {
457
+ const anchor = text(entry.sessionId);
458
+ return [
459
+ `status \u2192 ${text(entry.status) ?? "(unknown)"}`,
460
+ snippet(entry.detail) ? `\u2014 ${snippet(entry.detail)}` : null,
461
+ anchor ? `[session ${anchor}]` : null
462
+ ].filter(Boolean).join(" ");
463
+ }
464
+ case "timeline-entry": {
465
+ const payload = asRecord(entry.payload) ?? {};
466
+ const kind = text(payload.type) ?? "entry";
467
+ const body = snippet(payload.title) ?? snippet(payload.text) ?? snippet(payload.detail);
468
+ const state = text(payload.state);
469
+ return [`timeline ${kind}`, body ? `\u2014 ${body}` : null, state ? `(${state})` : null].filter(Boolean).join(" ");
470
+ }
471
+ case "log-entry": {
472
+ const payload = asRecord(entry.payload) ?? {};
473
+ const title = snippet(payload.title) ?? "log";
474
+ const detail = snippet(payload.detail);
475
+ return [`log ${title}`, detail ? `\u2014 ${detail}` : null].filter(Boolean).join(" ");
476
+ }
477
+ case "record-patch": {
478
+ const patch = asRecord(entry.patch) ?? {};
479
+ const keys = Object.keys(patch);
480
+ const shown = keys.slice(0, 8).join(", ");
481
+ return `record-patch {${shown}${keys.length > 8 ? `, +${keys.length - 8} more` : ""}}`;
482
+ }
483
+ case "timeline":
484
+ return "timeline updated (signal)";
485
+ case "log":
486
+ return `log signal${snippet(entry.title) ? ` \u2014 ${snippet(entry.title)}` : ""}`;
487
+ case "completed":
488
+ return "run completed";
489
+ case "failed":
490
+ return `run failed${snippet(entry.error) ? ` \u2014 ${snippet(entry.error)}` : ""}`;
491
+ default:
492
+ return type;
493
+ }
494
+ }
495
+ function summarizeSessionEntry(entry) {
496
+ const type = text(entry.type) ?? "entry";
497
+ const message = asRecord(entry.message);
498
+ const role = text(message?.role);
499
+ const content = message?.content;
500
+ let body = null;
501
+ if (typeof content === "string") {
502
+ body = snippet(content);
503
+ } else if (Array.isArray(content)) {
504
+ body = snippet(content.map((part) => text(asRecord(part)?.text) ?? "").filter(Boolean).join(" "));
505
+ }
506
+ return [
507
+ `pi ${type}`,
508
+ role ? `(${role})` : null,
509
+ body ? `\u2014 ${body}` : null
510
+ ].filter(Boolean).join(" ");
511
+ }
512
+ function sessionEntryTimestamp(entry) {
513
+ return text(entry.timestamp) ?? text(entry.at) ?? text(entry.createdAt) ?? null;
514
+ }
515
+ function resolveRunSessionFile(record) {
516
+ const piSession = record?.piSession ?? null;
517
+ if (!piSession)
518
+ return null;
519
+ const recorded = text(piSession.sessionFile);
520
+ if (recorded && existsSync3(recorded)) {
521
+ return recorded;
522
+ }
523
+ const cwd = text(piSession.cwd);
524
+ const sessionId = text(piSession.sessionId);
525
+ if (!cwd || !sessionId)
526
+ return null;
527
+ const sessionDir = resolve3(cwd, ".rig", "session");
528
+ try {
529
+ const match = readdirSync(sessionDir).find((name) => name.endsWith(`_${sessionId}.jsonl`));
530
+ return match ? join(sessionDir, match) : null;
531
+ } catch {
532
+ return null;
533
+ }
534
+ }
535
+ function buildRunReplay(projectRoot, runId, options = {}) {
536
+ const logPath = runLifecycleLogPath(projectRoot, runId);
537
+ const record = readAuthorityRun(projectRoot, runId);
538
+ const lifecycleEntries = readJsonlFile(logPath).map((entry) => asRecord(entry)).filter((entry) => entry !== null);
539
+ const merged = lifecycleEntries.map((entry) => ({
540
+ at: text(entry.at),
541
+ source: "run",
542
+ summary: summarizeLifecycleEntry(entry)
543
+ }));
544
+ let sessionFile = null;
545
+ let sessionEntryCount = 0;
546
+ if (options.withSession) {
547
+ sessionFile = resolveRunSessionFile(record);
548
+ if (sessionFile) {
549
+ let lastSeenAt = null;
550
+ const sessionLines = readJsonlFile(sessionFile).map((entry) => asRecord(entry)).filter((entry) => entry !== null).map((entry) => {
551
+ lastSeenAt = sessionEntryTimestamp(entry) ?? lastSeenAt;
552
+ return { at: lastSeenAt, source: "session", summary: summarizeSessionEntry(entry) };
553
+ });
554
+ sessionEntryCount = sessionLines.length;
555
+ merged.push(...sessionLines);
556
+ merged.sort((left, right) => (left.at ?? "").localeCompare(right.at ?? ""));
557
+ }
558
+ }
559
+ const lines = merged.map((line) => `${line.at ?? "(no timestamp) "} ${line.source === "run" ? "run " : "session"} ${line.summary}`);
560
+ return {
561
+ runId,
562
+ logPath,
563
+ sessionFile,
564
+ entryCount: lifecycleEntries.length,
565
+ sessionEntryCount,
566
+ lines
567
+ };
568
+ }
569
+
432
570
  // packages/cli/src/commands/_operator-surface.ts
433
571
  import { createInterface } from "readline";
434
572
  var CANONICAL_STAGES = [
@@ -526,22 +664,22 @@ function createPiRunStreamRenderer(output = process.stdout) {
526
664
  for (const [index, entry] of entries.entries()) {
527
665
  const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
528
666
  if (entry.type === "assistant_message" && typeof entry.text === "string") {
529
- const text = entry.text;
667
+ const text2 = entry.text;
530
668
  const previousText = assistantTextById.get(id) ?? "";
531
- if (!previousText && text.trim()) {
669
+ if (!previousText && text2.trim()) {
532
670
  writeLine("[Pi assistant]");
533
671
  }
534
- if (text.startsWith(previousText)) {
535
- const delta = text.slice(previousText.length);
672
+ if (text2.startsWith(previousText)) {
673
+ const delta = text2.slice(previousText.length);
536
674
  if (delta)
537
675
  output.write(delta);
538
- } else if (text.trim() && text !== previousText) {
676
+ } else if (text2.trim() && text2 !== previousText) {
539
677
  if (previousText)
540
678
  writeLine(`
541
679
  [Pi assistant]`);
542
- output.write(text);
680
+ output.write(text2);
543
681
  }
544
- assistantTextById.set(id, text);
682
+ assistantTextById.set(id, text2);
545
683
  continue;
546
684
  }
547
685
  if (seenTimeline.has(id))
@@ -556,15 +694,15 @@ function createPiRunStreamRenderer(output = process.stdout) {
556
694
  continue;
557
695
  }
558
696
  if (entry.type === "action") {
559
- const text = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
560
- if (text)
561
- writeLine(`[Rig action] ${text}`);
697
+ const text2 = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
698
+ if (text2)
699
+ writeLine(`[Rig action] ${text2}`);
562
700
  continue;
563
701
  }
564
702
  if (entry.type === "user_message") {
565
- const text = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
566
- if (text)
567
- writeLine(`[Operator] ${text}`);
703
+ const text2 = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
704
+ if (text2)
705
+ writeLine(`[Operator] ${text2}`);
568
706
  continue;
569
707
  }
570
708
  const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
@@ -624,7 +762,7 @@ function createOperatorSurface(options = {}) {
624
762
  // packages/cli/src/commands/_pi-frontend.ts
625
763
  import { mkdtempSync, rmSync } from "fs";
626
764
  import { tmpdir } from "os";
627
- import { join } from "path";
765
+ import { join as join2 } from "path";
628
766
  import {
629
767
  createAgentSessionFromServices,
630
768
  createAgentSessionServices,
@@ -756,15 +894,15 @@ class RigRemoteSessionController {
756
894
  for (const compaction of this.pendingCompactions.splice(0))
757
895
  compaction.reject(new Error("Remote session closed."));
758
896
  }
759
- async sendPrompt(text, streamingBehavior) {
760
- await this.transport.sendPrompt(this.context, this.runId, text, streamingBehavior);
897
+ async sendPrompt(text2, streamingBehavior) {
898
+ await this.transport.sendPrompt(this.context, this.runId, text2, streamingBehavior);
761
899
  }
762
- async sendCommand(text) {
763
- const result = await this.transport.runCommand(this.context, this.runId, text);
900
+ async sendCommand(text2) {
901
+ const result = await this.transport.runCommand(this.context, this.runId, text2);
764
902
  return typeof result.message === "string" ? result.message : "worker command accepted";
765
903
  }
766
- async sendShell(text) {
767
- await this.transport.sendShell(this.context, this.runId, text);
904
+ async sendShell(text2) {
905
+ await this.transport.sendShell(this.context, this.runId, text2);
768
906
  }
769
907
  async abort() {
770
908
  await this.transport.abort(this.context, this.runId);
@@ -916,8 +1054,8 @@ class RigRemoteAgentSession extends PiAgentSession {
916
1054
  replaceRemoteMessages(messages) {
917
1055
  this.agent.state.messages = messages;
918
1056
  }
919
- async expandLocalInput(text) {
920
- let current = text;
1057
+ async expandLocalInput(text2) {
1058
+ let current = text2;
921
1059
  const runner = this.extensionRunner;
922
1060
  if (runner.hasHandlers("input")) {
923
1061
  const result = await runner.emitInput(current, undefined, "interactive");
@@ -930,8 +1068,8 @@ class RigRemoteAgentSession extends PiAgentSession {
930
1068
  current = expandPromptTemplate(current, [...this.promptTemplates]);
931
1069
  return current;
932
1070
  }
933
- async prompt(text, options) {
934
- const trimmed = text.trim();
1071
+ async prompt(text2, options) {
1072
+ const trimmed = text2.trim();
935
1073
  if (!trimmed)
936
1074
  return;
937
1075
  if (trimmed.startsWith("/")) {
@@ -960,8 +1098,8 @@ class RigRemoteAgentSession extends PiAgentSession {
960
1098
  options?.preflightResult?.(true);
961
1099
  await this.remote.sendPrompt(expanded, behavior);
962
1100
  }
963
- async steer(text) {
964
- const trimmed = text.trim();
1101
+ async steer(text2) {
1102
+ const trimmed = text2.trim();
965
1103
  if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
966
1104
  return;
967
1105
  const expanded = await this.expandLocalInput(trimmed);
@@ -969,8 +1107,8 @@ class RigRemoteAgentSession extends PiAgentSession {
969
1107
  return;
970
1108
  await this.remote.sendPrompt(expanded, "steer");
971
1109
  }
972
- async followUp(text) {
973
- const trimmed = text.trim();
1110
+ async followUp(text2) {
1111
+ const trimmed = text2.trim();
974
1112
  if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
975
1113
  return;
976
1114
  const expanded = await this.expandLocalInput(trimmed);
@@ -982,8 +1120,8 @@ class RigRemoteAgentSession extends PiAgentSession {
982
1120
  await this.remote.abort();
983
1121
  }
984
1122
  async compact(customInstructions) {
985
- const pending = new Promise((resolve3, reject) => {
986
- this.remote.registerPendingCompaction({ resolve: resolve3, reject });
1123
+ const pending = new Promise((resolve4, reject) => {
1124
+ this.remote.registerPendingCompaction({ resolve: resolve4, reject });
987
1125
  });
988
1126
  await this.remote.sendCommand(`/compact${customInstructions ? ` ${customInstructions}` : ""}`);
989
1127
  return pending;
@@ -1052,8 +1190,8 @@ class RigRemoteAgentSession extends PiAgentSession {
1052
1190
  function createRemoteBashOperations(controller, excludeFromContext) {
1053
1191
  return {
1054
1192
  exec(command, _cwd, execOptions) {
1055
- return new Promise((resolve3, reject) => {
1056
- const pending = { onData: execOptions.onData, resolve: resolve3, reject, sawChunk: false };
1193
+ return new Promise((resolve4, reject) => {
1194
+ const pending = { onData: execOptions.onData, resolve: resolve4, reject, sawChunk: false };
1057
1195
  const cleanup = () => {
1058
1196
  execOptions.signal?.removeEventListener("abort", onAbort);
1059
1197
  if (timer)
@@ -1217,8 +1355,8 @@ function createRigWorkerPiBridgeExtension(options) {
1217
1355
  const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
1218
1356
  ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
1219
1357
  };
1220
- const setStatus = (ctx, text, isBusy) => {
1221
- statusText = text;
1358
+ const setStatus = (ctx, text2, isBusy) => {
1359
+ statusText = text2;
1222
1360
  busy = isBusy;
1223
1361
  renderStatus(ctx);
1224
1362
  };
@@ -1262,7 +1400,7 @@ function createRigWorkerPiBridgeExtension(options) {
1262
1400
  ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
1263
1401
  const nativeUi = ctx.ui;
1264
1402
  options.controller.setUiHooks({
1265
- onStatusText: (text) => setStatus(ctx, text, !connected),
1403
+ onStatusText: (text2) => setStatus(ctx, text2, !connected),
1266
1404
  onActivity: (label, detail) => {
1267
1405
  const active = label !== "idle" && !/complete|ready/i.test(label);
1268
1406
  setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
@@ -1319,7 +1457,7 @@ function setTemporaryEnv(updates) {
1319
1457
  };
1320
1458
  }
1321
1459
  async function attachRunBundledPiFrontend(context, input) {
1322
- const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
1460
+ const tempSessionDir = mkdtempSync(join2(tmpdir(), "rig-pi-frontend-sessions-"));
1323
1461
  const restoreEnv = setTemporaryEnv({
1324
1462
  PI_CODING_AGENT_SESSION_DIR: tempSessionDir,
1325
1463
  PI_SKIP_VERSION_CHECK: "1",
@@ -1833,7 +1971,7 @@ async function executeRun(context, args) {
1833
1971
  if (!runId) {
1834
1972
  throw new CliError2("run show requires a run id.");
1835
1973
  }
1836
- const record = readAuthorityRun(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, runId).catch(() => ({})));
1974
+ const record = readAuthorityRun2(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, runId).catch(() => ({})));
1837
1975
  if (!record) {
1838
1976
  throw new CliError2(`Run not found: ${runId}`, 2);
1839
1977
  }
@@ -1871,6 +2009,46 @@ async function executeRun(context, args) {
1871
2009
  }
1872
2010
  return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
1873
2011
  }
2012
+ case "replay": {
2013
+ let pending = rest;
2014
+ const run = takeOption(pending, "--run");
2015
+ pending = run.rest;
2016
+ const withSession = takeFlag(pending, "--with-session");
2017
+ pending = withSession.rest;
2018
+ const positionalRunId = pending.length > 0 && pending[0] && !pending[0].startsWith("-") ? pending[0] : undefined;
2019
+ const extra = positionalRunId ? pending.slice(1) : pending;
2020
+ requireNoExtraArgs(extra, "rig run replay <id>|--run <id> [--with-session]");
2021
+ const runId = run.value ?? positionalRunId;
2022
+ if (!runId) {
2023
+ throw new CliError2("run replay requires a run id.", 2);
2024
+ }
2025
+ const replay = buildRunReplay(context.projectRoot, runId, { withSession: withSession.value });
2026
+ if (replay.entryCount === 0 && replay.sessionEntryCount === 0) {
2027
+ throw new CliError2(`No run.jsonl lifecycle log found for ${runId} (looked at ${replay.logPath}).`, 2);
2028
+ }
2029
+ if (context.outputMode === "text") {
2030
+ console.log(`Replay of ${runId} (${replay.entryCount} lifecycle entries${withSession.value ? `, ${replay.sessionEntryCount} session entries` : ""}):`);
2031
+ for (const line of replay.lines) {
2032
+ console.log(line);
2033
+ }
2034
+ if (withSession.value && !replay.sessionFile) {
2035
+ console.log("(no Pi session file resolvable from the run record; showing run.jsonl only)");
2036
+ }
2037
+ }
2038
+ return {
2039
+ ok: true,
2040
+ group: "run",
2041
+ command,
2042
+ details: {
2043
+ runId,
2044
+ logPath: replay.logPath,
2045
+ sessionFile: replay.sessionFile,
2046
+ entryCount: replay.entryCount,
2047
+ sessionEntryCount: replay.sessionEntryCount,
2048
+ lines: replay.lines
2049
+ }
2050
+ };
2051
+ }
1874
2052
  case "attach": {
1875
2053
  let pending = rest;
1876
2054
  const runOption = takeOption(pending, "--run");
@@ -39,8 +39,7 @@ import { resolveRigServerCommand } from "@rig/runtime/local-server";
39
39
  import {
40
40
  readAuthorityRun,
41
41
  readJsonlFile,
42
- resolveAuthorityRunDir,
43
- writeJsonFile
42
+ writeAuthorityRunRecord
44
43
  } from "@rig/runtime/control-plane/authority-files";
45
44
 
46
45
  // packages/cli/src/commands/_paths.ts
@@ -89,7 +88,7 @@ function readJsonFile(path) {
89
88
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
90
89
  }
91
90
  }
92
- function writeJsonFile2(path, value) {
91
+ function writeJsonFile(path, value) {
93
92
  mkdirSync(dirname(path), { recursive: true });
94
93
  writeFileSync(path, `${JSON.stringify(value, null, 2)}
95
94
  `, "utf8");
@@ -124,7 +123,7 @@ function readGlobalConnections(options = {}) {
124
123
  return { connections };
125
124
  }
126
125
  function writeGlobalConnections(state, options = {}) {
127
- writeJsonFile2(resolveGlobalConnectionsPath(options.env ?? process.env), state);
126
+ writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
128
127
  }
129
128
  function upsertGlobalConnection(alias, connection, options = {}) {
130
129
  const cleanAlias = alias.trim();
@@ -151,7 +150,7 @@ function readRepoConnection(projectRoot) {
151
150
  };
152
151
  }
153
152
  function writeRepoConnection(projectRoot, state) {
154
- writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
153
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
155
154
  }
156
155
  function resolveSelectedConnection(projectRoot, options = {}) {
157
156
  const repo = readRepoConnection(projectRoot);