@h-rig/cli 0.0.6-alpha.17 → 0.0.6-alpha.19
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 +192 -35
- package/dist/src/commands/_operator-surface.js +48 -1
- package/dist/src/commands/_operator-view.js +48 -1
- package/dist/src/commands/_server-client.js +9 -0
- package/dist/src/commands/init.js +74 -23
- package/dist/src/commands/run.js +91 -12
- package/dist/src/commands/task-run-driver.js +27 -0
- package/dist/src/commands/task.js +48 -1
- package/dist/src/commands.js +192 -35
- package/dist/src/index.js +192 -35
- package/package.json +5 -5
|
@@ -832,6 +832,7 @@ function countDoctorFailures(checks) {
|
|
|
832
832
|
|
|
833
833
|
// packages/cli/src/commands/init.ts
|
|
834
834
|
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
835
|
+
var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
|
|
835
836
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
836
837
|
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
837
838
|
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
@@ -936,6 +937,19 @@ function readGhAuthToken() {
|
|
|
936
937
|
}
|
|
937
938
|
return result.stdout.trim();
|
|
938
939
|
}
|
|
940
|
+
function refreshGhProjectScopesAndReadToken() {
|
|
941
|
+
const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
|
|
942
|
+
encoding: "utf8",
|
|
943
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
944
|
+
});
|
|
945
|
+
if (result.status !== 0)
|
|
946
|
+
return null;
|
|
947
|
+
try {
|
|
948
|
+
return readGhAuthToken();
|
|
949
|
+
} catch {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
939
953
|
async function loadClackPrompts() {
|
|
940
954
|
return await import("@clack/prompts");
|
|
941
955
|
}
|
|
@@ -1031,12 +1045,27 @@ async function promptManualProjectStatusMapping(prompts) {
|
|
|
1031
1045
|
}
|
|
1032
1046
|
return statuses;
|
|
1033
1047
|
}
|
|
1034
|
-
|
|
1048
|
+
function projectScopeError(value) {
|
|
1049
|
+
const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
1050
|
+
return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
|
|
1051
|
+
}
|
|
1052
|
+
function optionName(option) {
|
|
1053
|
+
return String(option.name ?? option.label ?? option.id ?? "").trim();
|
|
1054
|
+
}
|
|
1055
|
+
function autoProjectStatusValue(options, key, label) {
|
|
1056
|
+
const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
|
|
1057
|
+
const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
|
|
1058
|
+
if (!match)
|
|
1059
|
+
return null;
|
|
1060
|
+
return String(match.id ?? match.name);
|
|
1061
|
+
}
|
|
1062
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
1035
1063
|
const projectChoice = await promptSelect(prompts, {
|
|
1036
1064
|
message: "GitHub Projects status sync",
|
|
1065
|
+
initialValue: "select",
|
|
1037
1066
|
options: [
|
|
1038
|
-
{ value: "off", label: "Off" },
|
|
1039
1067
|
{ value: "select", label: "Select accessible ProjectV2" },
|
|
1068
|
+
{ value: "off", label: "Off" },
|
|
1040
1069
|
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
1041
1070
|
]
|
|
1042
1071
|
});
|
|
@@ -1052,16 +1081,22 @@ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken
|
|
|
1052
1081
|
const owner = repoOwnerFromSlug(repoSlug);
|
|
1053
1082
|
if (!owner)
|
|
1054
1083
|
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
1055
|
-
|
|
1056
|
-
|
|
1084
|
+
let activeToken = githubToken?.trim() || null;
|
|
1085
|
+
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1086
|
+
let projects = recordArray(projectsPayload, "projects");
|
|
1087
|
+
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
1088
|
+
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
1089
|
+
const refreshedToken = refreshProjectToken();
|
|
1090
|
+
if (refreshedToken) {
|
|
1091
|
+
activeToken = refreshedToken;
|
|
1092
|
+
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1093
|
+
projects = recordArray(projectsPayload, "projects");
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1057
1096
|
if (projects.length === 0) {
|
|
1058
|
-
const error = typeof projectsPayload.error === "string" ? ` (${projectsPayload.error})` : "";
|
|
1059
|
-
prompts.outro?.(`No accessible GitHub Projects were returned${error};
|
|
1060
|
-
return {
|
|
1061
|
-
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
1062
|
-
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
1063
|
-
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
1064
|
-
};
|
|
1097
|
+
const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
|
|
1098
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
|
|
1099
|
+
return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
|
|
1065
1100
|
}
|
|
1066
1101
|
const selectedProjectId = await promptSelect(prompts, {
|
|
1067
1102
|
message: "GitHub ProjectV2 project",
|
|
@@ -1075,23 +1110,34 @@ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken
|
|
|
1075
1110
|
]
|
|
1076
1111
|
});
|
|
1077
1112
|
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
1078
|
-
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId,
|
|
1113
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
1079
1114
|
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
1080
1115
|
const rawField = fieldPayloadRecord.field;
|
|
1081
1116
|
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
1082
1117
|
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
1083
1118
|
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
1084
1119
|
if (options.length === 0) {
|
|
1085
|
-
return {
|
|
1120
|
+
return {
|
|
1121
|
+
githubProject: projectId,
|
|
1122
|
+
githubProjectStatusField: fieldId,
|
|
1123
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
|
|
1124
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
1125
|
+
};
|
|
1086
1126
|
}
|
|
1087
1127
|
const statuses = {};
|
|
1088
1128
|
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
1089
|
-
|
|
1129
|
+
const auto = autoProjectStatusValue(options, key, label);
|
|
1130
|
+
statuses[key] = auto ?? await promptSelect(prompts, {
|
|
1090
1131
|
message: `Project status option for ${label}`,
|
|
1091
|
-
options: options.map((option) => ({ value: String(option.id ?? option.name), label:
|
|
1132
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
|
|
1092
1133
|
});
|
|
1093
1134
|
}
|
|
1094
|
-
return {
|
|
1135
|
+
return {
|
|
1136
|
+
githubProject: projectId,
|
|
1137
|
+
githubProjectStatusField: fieldId,
|
|
1138
|
+
githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
|
|
1139
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
1140
|
+
};
|
|
1095
1141
|
}
|
|
1096
1142
|
function sleep2(ms) {
|
|
1097
1143
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
@@ -1422,12 +1468,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
1422
1468
|
});
|
|
1423
1469
|
const serverChoice = await promptSelect(prompts, {
|
|
1424
1470
|
message: "Rig server",
|
|
1471
|
+
initialValue: "remote",
|
|
1425
1472
|
options: [
|
|
1426
|
-
{ value: "
|
|
1427
|
-
{ value: "
|
|
1473
|
+
{ value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
|
|
1474
|
+
{ value: "local", label: "Local server", hint: "run on this machine" }
|
|
1428
1475
|
]
|
|
1429
1476
|
});
|
|
1430
|
-
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder:
|
|
1477
|
+
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
|
|
1431
1478
|
let remoteCheckout;
|
|
1432
1479
|
if (serverChoice === "remote") {
|
|
1433
1480
|
const checkout = await promptSelect(prompts, {
|
|
@@ -1459,31 +1506,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
1459
1506
|
{ value: "skip", label: "Skip for now" }
|
|
1460
1507
|
]
|
|
1461
1508
|
});
|
|
1509
|
+
let remoteGhTokenConfirmed = false;
|
|
1462
1510
|
if (serverChoice === "remote" && authMethod === "gh") {
|
|
1463
1511
|
if (!prompts.confirm)
|
|
1464
1512
|
throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
|
|
1465
1513
|
const confirmed = await prompts.confirm({
|
|
1466
1514
|
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
1467
|
-
initialValue:
|
|
1515
|
+
initialValue: true
|
|
1468
1516
|
});
|
|
1469
1517
|
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
1470
1518
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
1471
1519
|
}
|
|
1520
|
+
remoteGhTokenConfirmed = true;
|
|
1472
1521
|
}
|
|
1473
1522
|
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
1474
|
-
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken);
|
|
1523
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
1524
|
+
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
1475
1525
|
const result = await runControlPlaneInit(context, {
|
|
1476
1526
|
server: serverChoice,
|
|
1477
1527
|
remoteUrl,
|
|
1478
1528
|
repoSlug,
|
|
1479
|
-
githubToken,
|
|
1529
|
+
githubToken: effectiveGithubToken,
|
|
1480
1530
|
githubAuthMethod: authMethod,
|
|
1481
1531
|
githubProject: projectConfig.githubProject,
|
|
1482
1532
|
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
1483
1533
|
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
1484
1534
|
remoteCheckout,
|
|
1485
1535
|
repair,
|
|
1486
|
-
privateStateOnly
|
|
1536
|
+
privateStateOnly,
|
|
1537
|
+
yes: remoteGhTokenConfirmed || undefined
|
|
1487
1538
|
});
|
|
1488
1539
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
1489
1540
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
package/dist/src/commands/run.js
CHANGED
|
@@ -259,6 +259,14 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
259
259
|
}
|
|
260
260
|
return payload;
|
|
261
261
|
}
|
|
262
|
+
async function listRunsViaServer(context, options = {}) {
|
|
263
|
+
const url = new URL("http://rig.local/api/runs");
|
|
264
|
+
if (options.limit !== undefined)
|
|
265
|
+
url.searchParams.set("limit", String(options.limit));
|
|
266
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
267
|
+
const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
|
|
268
|
+
return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
269
|
+
}
|
|
262
270
|
async function getRunDetailsViaServer(context, runId) {
|
|
263
271
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
|
|
264
272
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
@@ -317,6 +325,42 @@ var CANONICAL_STAGES = [
|
|
|
317
325
|
function logDetail(log) {
|
|
318
326
|
return typeof log.detail === "string" ? log.detail.trim() : "";
|
|
319
327
|
}
|
|
328
|
+
function parseProviderProtocolLog(title, detail) {
|
|
329
|
+
if (title.trim().toLowerCase() !== "agent output")
|
|
330
|
+
return null;
|
|
331
|
+
if (!detail.startsWith("{") || !detail.endsWith("}"))
|
|
332
|
+
return null;
|
|
333
|
+
try {
|
|
334
|
+
const record = JSON.parse(detail);
|
|
335
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
336
|
+
return null;
|
|
337
|
+
const type = record.type;
|
|
338
|
+
return typeof type === "string" && [
|
|
339
|
+
"assistant",
|
|
340
|
+
"message_start",
|
|
341
|
+
"message_update",
|
|
342
|
+
"message_end",
|
|
343
|
+
"stream_event",
|
|
344
|
+
"tool_result",
|
|
345
|
+
"tool_execution_start",
|
|
346
|
+
"tool_execution_update",
|
|
347
|
+
"tool_execution_end",
|
|
348
|
+
"turn_start",
|
|
349
|
+
"turn_end"
|
|
350
|
+
].includes(type) ? record : null;
|
|
351
|
+
} catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function renderProviderProtocolLog(record) {
|
|
356
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
357
|
+
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
|
|
358
|
+
const toolName = String(record.toolName ?? record.name ?? "tool");
|
|
359
|
+
const status = type === "tool_execution_start" ? "started" : type === "tool_execution_end" ? record.isError === true || record.result && typeof record.result === "object" && !Array.isArray(record.result) && record.result.isError === true ? "failed" : "completed" : "running";
|
|
360
|
+
return `[Pi tool] ${toolName} ${status}`;
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
320
364
|
function entryId(entry, fallback) {
|
|
321
365
|
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
322
366
|
}
|
|
@@ -361,12 +405,16 @@ function createPiRunStreamRenderer(output = process.stdout) {
|
|
|
361
405
|
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
362
406
|
const text = entry.text;
|
|
363
407
|
const previousText = assistantTextById.get(id) ?? "";
|
|
408
|
+
if (!previousText && text.trim()) {
|
|
409
|
+
writeLine("[Pi assistant]");
|
|
410
|
+
}
|
|
364
411
|
if (text.startsWith(previousText)) {
|
|
365
412
|
const delta = text.slice(previousText.length);
|
|
366
413
|
if (delta)
|
|
367
414
|
output.write(delta);
|
|
368
415
|
} else if (text.trim() && text !== previousText) {
|
|
369
|
-
|
|
416
|
+
if (previousText)
|
|
417
|
+
writeLine(`
|
|
370
418
|
[Pi assistant]`);
|
|
371
419
|
output.write(text);
|
|
372
420
|
}
|
|
@@ -397,6 +445,13 @@ function createPiRunStreamRenderer(output = process.stdout) {
|
|
|
397
445
|
const detail = logDetail(entry);
|
|
398
446
|
if (!detail)
|
|
399
447
|
continue;
|
|
448
|
+
const protocolRecord = parseProviderProtocolLog(title, detail);
|
|
449
|
+
if (protocolRecord) {
|
|
450
|
+
const protocolLine = renderProviderProtocolLog(protocolRecord);
|
|
451
|
+
if (protocolLine)
|
|
452
|
+
writeLine(protocolLine);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
400
455
|
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
401
456
|
}
|
|
402
457
|
}
|
|
@@ -524,6 +579,28 @@ function normalizeRemoteRunDetails(payload) {
|
|
|
524
579
|
...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
|
|
525
580
|
};
|
|
526
581
|
}
|
|
582
|
+
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
583
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
584
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
585
|
+
}
|
|
586
|
+
async function listRunsForSelectedConnection(context, options = {}) {
|
|
587
|
+
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
588
|
+
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
589
|
+
}
|
|
590
|
+
return { runs: listAuthorityRuns(context.projectRoot), source: "local" };
|
|
591
|
+
}
|
|
592
|
+
function runStringField(run, key, fallback = "") {
|
|
593
|
+
const value = run[key];
|
|
594
|
+
return typeof value === "string" && value.trim() ? value : fallback;
|
|
595
|
+
}
|
|
596
|
+
function runDisplayTitle(run) {
|
|
597
|
+
return runStringField(run, "title", runStringField(run, "taskId", "(untitled)"));
|
|
598
|
+
}
|
|
599
|
+
function buildServerRunStatus(runs) {
|
|
600
|
+
const activeRuns = runs.filter((run) => !REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
601
|
+
const recentRuns = runs.filter((run) => REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
602
|
+
return { activeRuns, recentRuns, runs };
|
|
603
|
+
}
|
|
527
604
|
function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
|
|
528
605
|
if (noEpicPrompt) {
|
|
529
606
|
return false;
|
|
@@ -589,17 +666,17 @@ async function executeRun(context, args) {
|
|
|
589
666
|
switch (command) {
|
|
590
667
|
case "list": {
|
|
591
668
|
requireNoExtraArgs(rest, "bun run rig run list");
|
|
592
|
-
const runs =
|
|
669
|
+
const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
|
|
593
670
|
if (context.outputMode === "text") {
|
|
594
671
|
if (runs.length === 0) {
|
|
595
|
-
console.log("No runs recorded in .rig/runs.");
|
|
672
|
+
console.log(source === "server" ? "No runs recorded on the selected Rig server." : "No runs recorded in .rig/runs.");
|
|
596
673
|
} else {
|
|
597
674
|
for (const run of runs) {
|
|
598
|
-
console.log(`- ${run
|
|
675
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runDisplayTitle(run)}`);
|
|
599
676
|
}
|
|
600
677
|
}
|
|
601
678
|
}
|
|
602
|
-
return { ok: true, group: "run", command, details: { runs } };
|
|
679
|
+
return { ok: true, group: "run", command, details: { runs, source } };
|
|
603
680
|
}
|
|
604
681
|
case "delete": {
|
|
605
682
|
let pending = rest;
|
|
@@ -736,17 +813,19 @@ async function executeRun(context, args) {
|
|
|
736
813
|
}
|
|
737
814
|
return { ok: true, group: "run", command };
|
|
738
815
|
}
|
|
739
|
-
const summary = runStatus(context.projectRoot, runtimeContext);
|
|
816
|
+
const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
|
|
817
|
+
const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
818
|
+
const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
740
819
|
if (context.outputMode === "text") {
|
|
741
|
-
console.log(`Active runs: ${
|
|
742
|
-
for (const run of
|
|
743
|
-
console.log(`- ${run
|
|
820
|
+
console.log(`Active runs: ${activeRuns.length}`);
|
|
821
|
+
for (const run of activeRuns) {
|
|
822
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
744
823
|
}
|
|
745
|
-
if (
|
|
824
|
+
if (recentRuns.length > 0) {
|
|
746
825
|
console.log("");
|
|
747
826
|
console.log("Recent runs:");
|
|
748
|
-
for (const run of
|
|
749
|
-
console.log(`- ${run
|
|
827
|
+
for (const run of recentRuns) {
|
|
828
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
750
829
|
}
|
|
751
830
|
}
|
|
752
831
|
}
|
|
@@ -1220,6 +1220,26 @@ function appendAssistantTimelineFromRecord(input) {
|
|
|
1220
1220
|
}
|
|
1221
1221
|
return nextAssistantText;
|
|
1222
1222
|
}
|
|
1223
|
+
function appendPiToolTimelineFromRecord(input) {
|
|
1224
|
+
const type = typeof input.record.type === "string" ? input.record.type : "";
|
|
1225
|
+
if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
|
|
1226
|
+
return false;
|
|
1227
|
+
const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
|
|
1228
|
+
const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
|
|
1229
|
+
const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
|
|
1230
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
1231
|
+
id: `tool:${toolCallId}:${type}`,
|
|
1232
|
+
type,
|
|
1233
|
+
toolName,
|
|
1234
|
+
status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
|
|
1235
|
+
createdAt: new Date().toISOString()
|
|
1236
|
+
});
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
function isNonRenderablePiProtocolRecord(record) {
|
|
1240
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
1241
|
+
return type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
|
|
1242
|
+
}
|
|
1223
1243
|
function appendToolTimelineFromLog(input) {
|
|
1224
1244
|
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
1225
1245
|
if (title !== "Tool activity")
|
|
@@ -1745,6 +1765,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1745
1765
|
try {
|
|
1746
1766
|
const record = JSON.parse(trimmed);
|
|
1747
1767
|
const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
|
|
1768
|
+
if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
|
|
1769
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1748
1772
|
const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
|
|
1749
1773
|
runId: input.runId,
|
|
1750
1774
|
record,
|
|
@@ -1816,6 +1840,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1816
1840
|
return;
|
|
1817
1841
|
}
|
|
1818
1842
|
}
|
|
1843
|
+
if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1819
1846
|
if (record.type === "assistant") {
|
|
1820
1847
|
const message = record.message && typeof record.message === "object" ? record.message : record;
|
|
1821
1848
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
@@ -738,6 +738,42 @@ var CANONICAL_STAGES = [
|
|
|
738
738
|
function logDetail(log) {
|
|
739
739
|
return typeof log.detail === "string" ? log.detail.trim() : "";
|
|
740
740
|
}
|
|
741
|
+
function parseProviderProtocolLog(title, detail) {
|
|
742
|
+
if (title.trim().toLowerCase() !== "agent output")
|
|
743
|
+
return null;
|
|
744
|
+
if (!detail.startsWith("{") || !detail.endsWith("}"))
|
|
745
|
+
return null;
|
|
746
|
+
try {
|
|
747
|
+
const record = JSON.parse(detail);
|
|
748
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
749
|
+
return null;
|
|
750
|
+
const type = record.type;
|
|
751
|
+
return typeof type === "string" && [
|
|
752
|
+
"assistant",
|
|
753
|
+
"message_start",
|
|
754
|
+
"message_update",
|
|
755
|
+
"message_end",
|
|
756
|
+
"stream_event",
|
|
757
|
+
"tool_result",
|
|
758
|
+
"tool_execution_start",
|
|
759
|
+
"tool_execution_update",
|
|
760
|
+
"tool_execution_end",
|
|
761
|
+
"turn_start",
|
|
762
|
+
"turn_end"
|
|
763
|
+
].includes(type) ? record : null;
|
|
764
|
+
} catch {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function renderProviderProtocolLog(record) {
|
|
769
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
770
|
+
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
|
|
771
|
+
const toolName = String(record.toolName ?? record.name ?? "tool");
|
|
772
|
+
const status = type === "tool_execution_start" ? "started" : type === "tool_execution_end" ? record.isError === true || record.result && typeof record.result === "object" && !Array.isArray(record.result) && record.result.isError === true ? "failed" : "completed" : "running";
|
|
773
|
+
return `[Pi tool] ${toolName} ${status}`;
|
|
774
|
+
}
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
741
777
|
function entryId(entry, fallback) {
|
|
742
778
|
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
743
779
|
}
|
|
@@ -782,12 +818,16 @@ function createPiRunStreamRenderer(output = process.stdout) {
|
|
|
782
818
|
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
783
819
|
const text = entry.text;
|
|
784
820
|
const previousText = assistantTextById.get(id) ?? "";
|
|
821
|
+
if (!previousText && text.trim()) {
|
|
822
|
+
writeLine("[Pi assistant]");
|
|
823
|
+
}
|
|
785
824
|
if (text.startsWith(previousText)) {
|
|
786
825
|
const delta = text.slice(previousText.length);
|
|
787
826
|
if (delta)
|
|
788
827
|
output.write(delta);
|
|
789
828
|
} else if (text.trim() && text !== previousText) {
|
|
790
|
-
|
|
829
|
+
if (previousText)
|
|
830
|
+
writeLine(`
|
|
791
831
|
[Pi assistant]`);
|
|
792
832
|
output.write(text);
|
|
793
833
|
}
|
|
@@ -818,6 +858,13 @@ function createPiRunStreamRenderer(output = process.stdout) {
|
|
|
818
858
|
const detail = logDetail(entry);
|
|
819
859
|
if (!detail)
|
|
820
860
|
continue;
|
|
861
|
+
const protocolRecord = parseProviderProtocolLog(title, detail);
|
|
862
|
+
if (protocolRecord) {
|
|
863
|
+
const protocolLine = renderProviderProtocolLog(protocolRecord);
|
|
864
|
+
if (protocolLine)
|
|
865
|
+
writeLine(protocolLine);
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
821
868
|
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
822
869
|
}
|
|
823
870
|
}
|