@h-rig/cli 0.0.6-alpha.60 → 0.0.6-alpha.61
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.
- package/dist/bin/rig.js +316 -134
- package/dist/src/commands/_authority-runs.js +2 -3
- package/dist/src/commands/_help-catalog.js +1 -0
- package/dist/src/commands/_run-driver-helpers.js +12 -17
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/agent.js +2 -3
- package/dist/src/commands/run.js +216 -38
- package/dist/src/commands/server.js +4 -5
- package/dist/src/commands/task-run-driver.js +29 -26
- package/dist/src/commands/task.js +4 -4
- package/dist/src/commands.js +308 -126
- package/dist/src/index.js +316 -134
- package/package.json +7 -7
|
@@ -5,8 +5,7 @@ import { resolve } from "path";
|
|
|
5
5
|
import {
|
|
6
6
|
readAuthorityRun,
|
|
7
7
|
readJsonlFile,
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
19
|
-
writeJsonFile
|
|
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
|
-
|
|
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
|
|
68
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
appendRunTimelineEntry(projectRoot, runId, value);
|
|
91
86
|
touchAuthorityRun(projectRoot, runId);
|
|
92
87
|
}
|
|
93
88
|
function appendRunLog(projectRoot, runId, value) {
|
|
94
|
-
|
|
89
|
+
appendRunLogEntry(projectRoot, runId, value);
|
|
95
90
|
touchAuthorityRun(projectRoot, runId);
|
|
96
91
|
}
|
|
97
92
|
function appendRunAction(projectRoot, runId, value) {
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
+
writeAuthorityRunRecord(projectRoot, input.runId, next);
|
|
161
160
|
return next;
|
|
162
161
|
}
|
|
163
162
|
|
package/dist/src/commands/run.js
CHANGED
|
@@ -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
|
|
667
|
+
const text2 = entry.text;
|
|
530
668
|
const previousText = assistantTextById.get(id) ?? "";
|
|
531
|
-
if (!previousText &&
|
|
669
|
+
if (!previousText && text2.trim()) {
|
|
532
670
|
writeLine("[Pi assistant]");
|
|
533
671
|
}
|
|
534
|
-
if (
|
|
535
|
-
const delta =
|
|
672
|
+
if (text2.startsWith(previousText)) {
|
|
673
|
+
const delta = text2.slice(previousText.length);
|
|
536
674
|
if (delta)
|
|
537
675
|
output.write(delta);
|
|
538
|
-
} else if (
|
|
676
|
+
} else if (text2.trim() && text2 !== previousText) {
|
|
539
677
|
if (previousText)
|
|
540
678
|
writeLine(`
|
|
541
679
|
[Pi assistant]`);
|
|
542
|
-
output.write(
|
|
680
|
+
output.write(text2);
|
|
543
681
|
}
|
|
544
|
-
assistantTextById.set(id,
|
|
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
|
|
560
|
-
if (
|
|
561
|
-
writeLine(`[Rig action] ${
|
|
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
|
|
566
|
-
if (
|
|
567
|
-
writeLine(`[Operator] ${
|
|
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(
|
|
760
|
-
await this.transport.sendPrompt(this.context, this.runId,
|
|
897
|
+
async sendPrompt(text2, streamingBehavior) {
|
|
898
|
+
await this.transport.sendPrompt(this.context, this.runId, text2, streamingBehavior);
|
|
761
899
|
}
|
|
762
|
-
async sendCommand(
|
|
763
|
-
const result = await this.transport.runCommand(this.context, this.runId,
|
|
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(
|
|
767
|
-
await this.transport.sendShell(this.context, this.runId,
|
|
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(
|
|
920
|
-
let current =
|
|
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(
|
|
934
|
-
const trimmed =
|
|
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(
|
|
964
|
-
const trimmed =
|
|
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(
|
|
973
|
-
const trimmed =
|
|
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((
|
|
986
|
-
this.remote.registerPendingCompaction({ resolve:
|
|
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((
|
|
1056
|
-
const pending = { onData: execOptions.onData, resolve:
|
|
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,
|
|
1221
|
-
statusText =
|
|
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: (
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
155
154
|
}
|
|
156
155
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
157
156
|
const repo = readRepoConnection(projectRoot);
|