@h-rig/cli 0.0.6-alpha.13 → 0.0.6-alpha.14

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.
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task-run-driver.ts
3
- import { copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
4
- import { resolve as resolve4 } from "path";
3
+ import { copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync2 } from "fs";
4
+ import { resolve as resolve6 } from "path";
5
5
  import { spawn, spawnSync } from "child_process";
6
6
  import { createInterface as createLineInterface } from "readline";
7
7
 
@@ -39,10 +39,9 @@ import {
39
39
  isCodexExecRecord
40
40
  } from "@rig/runtime/control-plane/provider/codex-exec-records";
41
41
  import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
42
- import { readAuthorityRun as readAuthorityRun3, readJsonFile, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
42
+ import { readAuthorityRun as readAuthorityRun3, readJsonFile as readJsonFile2, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
43
43
  import {
44
- buildTaskRunLifecycleComment,
45
- updateConfiguredTaskSourceTask
44
+ buildTaskRunLifecycleComment
46
45
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
47
46
  import {
48
47
  closeIssueAfterMergedPr,
@@ -388,6 +387,194 @@ function renderSourceScopeValidation(task, validation) {
388
387
  `);
389
388
  }
390
389
 
390
+ // packages/cli/src/commands/_server-client.ts
391
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
392
+ import { resolve as resolve5 } from "path";
393
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
394
+
395
+ // packages/cli/src/commands/_connection-state.ts
396
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
397
+ import { homedir } from "os";
398
+ import { dirname, resolve as resolve4 } from "path";
399
+ function resolveGlobalConnectionsPath(env = process.env) {
400
+ const explicit = env.RIG_CONNECTIONS_FILE?.trim();
401
+ if (explicit)
402
+ return resolve4(explicit);
403
+ const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
404
+ if (stateDir)
405
+ return resolve4(stateDir, "connections.json");
406
+ return resolve4(homedir(), ".rig", "connections.json");
407
+ }
408
+ function resolveRepoConnectionPath(projectRoot) {
409
+ return resolve4(projectRoot, ".rig", "state", "connection.json");
410
+ }
411
+ function readJsonFile(path) {
412
+ if (!existsSync2(path))
413
+ return null;
414
+ try {
415
+ return JSON.parse(readFileSync2(path, "utf8"));
416
+ } catch (error) {
417
+ throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
418
+ }
419
+ }
420
+ function normalizeConnection(value) {
421
+ if (!value || typeof value !== "object" || Array.isArray(value))
422
+ return null;
423
+ const record = value;
424
+ if (record.kind === "local")
425
+ return { kind: "local", mode: "auto" };
426
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
427
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
428
+ return { kind: "remote", baseUrl };
429
+ }
430
+ return null;
431
+ }
432
+ function readGlobalConnections(options = {}) {
433
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
434
+ const payload = readJsonFile(path);
435
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
436
+ return { connections: {} };
437
+ }
438
+ const rawConnections = payload.connections;
439
+ const connections = {};
440
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
441
+ for (const [alias, raw] of Object.entries(rawConnections)) {
442
+ const connection = normalizeConnection(raw);
443
+ if (connection)
444
+ connections[alias] = connection;
445
+ }
446
+ }
447
+ return { connections };
448
+ }
449
+ function readRepoConnection(projectRoot) {
450
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
451
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
452
+ return null;
453
+ const record = payload;
454
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
455
+ if (!selected)
456
+ return null;
457
+ return {
458
+ selected,
459
+ project: typeof record.project === "string" ? record.project : undefined,
460
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
461
+ };
462
+ }
463
+ function resolveSelectedConnection(projectRoot, options = {}) {
464
+ const repo = readRepoConnection(projectRoot);
465
+ if (!repo)
466
+ return null;
467
+ if (repo.selected === "local")
468
+ return { alias: "local", connection: { kind: "local", mode: "auto" } };
469
+ const global = readGlobalConnections(options);
470
+ const connection = global.connections[repo.selected];
471
+ if (!connection) {
472
+ throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
473
+ }
474
+ return { alias: repo.selected, connection };
475
+ }
476
+
477
+ // packages/cli/src/commands/_server-client.ts
478
+ var scopedGitHubBearerTokens = new Map;
479
+ function cleanToken(value) {
480
+ const trimmed = value?.trim();
481
+ return trimmed ? trimmed : null;
482
+ }
483
+ function readPrivateRemoteSessionToken(projectRoot) {
484
+ const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
485
+ if (!existsSync3(path))
486
+ return null;
487
+ try {
488
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
489
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
490
+ } catch {
491
+ return null;
492
+ }
493
+ }
494
+ function readGitHubBearerTokenForRemote(projectRoot) {
495
+ const scopedKey = resolve5(projectRoot);
496
+ if (scopedGitHubBearerTokens.has(scopedKey))
497
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
498
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
499
+ if (privateSession)
500
+ return privateSession;
501
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
502
+ }
503
+ async function ensureServerForCli(projectRoot) {
504
+ try {
505
+ const selected = resolveSelectedConnection(projectRoot);
506
+ if (selected?.connection.kind === "remote") {
507
+ return {
508
+ baseUrl: selected.connection.baseUrl,
509
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
510
+ connectionKind: "remote"
511
+ };
512
+ }
513
+ const connection = await ensureLocalRigServerConnection(projectRoot);
514
+ return {
515
+ baseUrl: connection.baseUrl,
516
+ authToken: connection.authToken,
517
+ connectionKind: "local"
518
+ };
519
+ } catch (error) {
520
+ if (error instanceof Error) {
521
+ throw new CliError2(error.message, 1);
522
+ }
523
+ throw error;
524
+ }
525
+ }
526
+ function mergeHeaders(headers, authToken) {
527
+ const merged = new Headers(headers);
528
+ if (authToken) {
529
+ merged.set("authorization", `Bearer ${authToken}`);
530
+ }
531
+ return merged;
532
+ }
533
+ function diagnosticMessage(payload) {
534
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
535
+ return null;
536
+ const record = payload;
537
+ const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
538
+ const messages = diagnostics.flatMap((entry) => {
539
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
540
+ return [];
541
+ const diagnostic = entry;
542
+ const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
543
+ const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
544
+ return message ? [`${kind}: ${message}`] : [];
545
+ });
546
+ return messages.length > 0 ? messages.join("; ") : null;
547
+ }
548
+ async function requestServerJson(context, pathname, init = {}) {
549
+ const server = await ensureServerForCli(context.projectRoot);
550
+ const response = await fetch(`${server.baseUrl}${pathname}`, {
551
+ ...init,
552
+ headers: mergeHeaders(init.headers, server.authToken)
553
+ });
554
+ const text = await response.text();
555
+ const payload = text.trim().length > 0 ? (() => {
556
+ try {
557
+ return JSON.parse(text);
558
+ } catch {
559
+ return null;
560
+ }
561
+ })() : null;
562
+ if (!response.ok) {
563
+ const diagnostics = diagnosticMessage(payload);
564
+ const detail = diagnostics ?? (text || response.statusText);
565
+ throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
566
+ }
567
+ return payload;
568
+ }
569
+ async function updateWorkspaceTaskViaServer(context, input) {
570
+ const payload = await requestServerJson(context, "/api/tasks/update", {
571
+ method: "POST",
572
+ headers: { "content-type": "application/json" },
573
+ body: JSON.stringify(input)
574
+ });
575
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
576
+ }
577
+
391
578
  // packages/cli/src/commands/task-run-driver.ts
392
579
  var PI_CANONICAL_RUN_STAGES = [
393
580
  "Connect",
@@ -455,12 +642,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
455
642
  return 0;
456
643
  let copied = 0;
457
644
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
458
- const sourcePath = resolve4(sourceRoot, relativePath);
459
- const targetPath = resolve4(targetRoot, relativePath);
645
+ const sourcePath = resolve6(sourceRoot, relativePath);
646
+ const targetPath = resolve6(targetRoot, relativePath);
460
647
  try {
461
648
  if (!statSync(sourcePath).isFile())
462
649
  continue;
463
- mkdirSync(resolve4(targetPath, ".."), { recursive: true });
650
+ mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
464
651
  copyFileSync(sourcePath, targetPath);
465
652
  copied += 1;
466
653
  } catch {}
@@ -499,7 +686,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
499
686
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
500
687
  return {
501
688
  RIG_BASELINE_MODE: "dirty-snapshot",
502
- RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
689
+ RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
503
690
  };
504
691
  }
505
692
  function positiveInt(value, fallback) {
@@ -617,9 +804,9 @@ function createCommandRunner(binary) {
617
804
  const stderrChunks = [];
618
805
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
619
806
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
620
- return await new Promise((resolve5) => {
621
- child.once("error", (error) => resolve5({ exitCode: 1, stderr: error.message }));
622
- child.once("close", (code) => resolve5({
807
+ return await new Promise((resolve7) => {
808
+ child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
809
+ child.once("close", (code) => resolve7({
623
810
  exitCode: code ?? 1,
624
811
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
625
812
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -643,8 +830,9 @@ async function runTaskRunPostValidationLifecycle(input) {
643
830
  if (!taskId) {
644
831
  return { status: "skipped" };
645
832
  }
646
- const config = input.config ?? {};
647
- const prMode = config.pr?.mode ?? "auto";
833
+ const configInput = input.config ?? null;
834
+ const config = configInput ?? {};
835
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
648
836
  if (prMode === "off" || prMode === "ask") {
649
837
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
650
838
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -660,7 +848,7 @@ async function runTaskRunPostValidationLifecycle(input) {
660
848
  gitCommand
661
849
  });
662
850
  const prAutomation = input.prAutomation ?? runPrAutomation;
663
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
851
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
664
852
  const stage = input.appendStage ?? (() => {
665
853
  return;
666
854
  });
@@ -692,8 +880,9 @@ async function runTaskRunPostValidationLifecycle(input) {
692
880
  config,
693
881
  sourceTask: input.sourceTask,
694
882
  uploadedSnapshot: input.uploadedSnapshot,
695
- artifactRoot: resolve4(input.projectRoot, "artifacts", taskId),
883
+ artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
696
884
  command: ghCommand,
885
+ gitCommand,
697
886
  steerPi,
698
887
  lifecycle: {
699
888
  onPrOpened: async ({ prUrl }) => {
@@ -742,8 +931,6 @@ async function runTaskRunPostValidationLifecycle(input) {
742
931
  runtimeWorkspace: input.runtimeWorkspace ?? null
743
932
  })
744
933
  }
745
- }).catch(() => {
746
- return;
747
934
  });
748
935
  }
749
936
  },
@@ -762,8 +949,6 @@ async function runTaskRunPostValidationLifecycle(input) {
762
949
  runtimeWorkspace: input.runtimeWorkspace ?? null
763
950
  })
764
951
  }
765
- }).catch(() => {
766
- return;
767
952
  });
768
953
  }
769
954
  },
@@ -792,8 +977,17 @@ async function runTaskRunPostValidationLifecycle(input) {
792
977
  errorText: detail
793
978
  })
794
979
  }
795
- }).catch(() => {
796
- return;
980
+ }).catch((error) => {
981
+ try {
982
+ appendRunLog(input.projectRoot, input.runId, {
983
+ id: `log:${input.runId}:task-source-needs-attention-update`,
984
+ title: "Task source needs-attention update failed",
985
+ detail: error instanceof Error ? error.message : String(error),
986
+ tone: "error",
987
+ status: "needs_attention",
988
+ createdAt: new Date().toISOString()
989
+ });
990
+ } catch {}
797
991
  });
798
992
  }
799
993
  return { status: "needs_attention", pr };
@@ -816,7 +1010,7 @@ function summarizeValidationFailure(projectRoot, taskId) {
816
1010
  return null;
817
1011
  }
818
1012
  for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
819
- const summary = readJsonFile(resolve4(artifactDir, "validation-summary.json"), null);
1013
+ const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
820
1014
  if (!summary || summary.status !== "fail") {
821
1015
  continue;
822
1016
  }
@@ -897,14 +1091,14 @@ function readTaskRunAcceptedArtifactState(input) {
897
1091
  if (!input.taskId || !input.workspaceDir) {
898
1092
  return { accepted: false, reason: null };
899
1093
  }
900
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
901
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
902
- const taskResultPath = resolve4(artifactDir, "task-result.json");
1094
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1095
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1096
+ const taskResultPath = resolve6(artifactDir, "task-result.json");
903
1097
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
904
1098
  if (reviewStatus !== "APPROVED") {
905
1099
  return { accepted: false, reason: null };
906
1100
  }
907
- const taskResult = readJsonFile(taskResultPath, null);
1101
+ const taskResult = readJsonFile2(taskResultPath, null);
908
1102
  if (taskResult?.status !== "completed") {
909
1103
  return { accepted: false, reason: null };
910
1104
  }
@@ -936,13 +1130,13 @@ function resolveTaskRunRetryContext(input) {
936
1130
  if (!input.taskId || !input.workspaceDir) {
937
1131
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
938
1132
  }
939
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
940
- const reviewStatePath = resolve4(artifactDir, "review-state.json");
941
- const reviewFeedbackPath = resolve4(artifactDir, "review-feedback.md");
942
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
943
- const failedApproachesPath = resolve4(input.workspaceDir, ".rig", "state", "failed_approaches.md");
944
- const validationSummaryPath = resolve4(artifactDir, "validation-summary.json");
945
- const reviewState = readJsonFile(reviewStatePath, null);
1133
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1134
+ const reviewStatePath = resolve6(artifactDir, "review-state.json");
1135
+ const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1136
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1137
+ const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
1138
+ const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
1139
+ const reviewState = readJsonFile2(reviewStatePath, null);
946
1140
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
947
1141
  const reviewRejected = isTaskRunReviewRejected(reviewState);
948
1142
  if (reviewStatus === "APPROVED") {
@@ -995,12 +1189,60 @@ function summarizeTaskRunReviewFailure(reviewState) {
995
1189
  }
996
1190
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
997
1191
  }
1192
+ function appendAssistantTimelineFromRecord(input) {
1193
+ let nextAssistantText = input.assistantText;
1194
+ if (input.record.type === "message_update") {
1195
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
1196
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
1197
+ nextAssistantText += assistantMessageEvent.delta;
1198
+ }
1199
+ } else if (input.record.type === "stream_event") {
1200
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
1201
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
1202
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
1203
+ nextAssistantText += delta.text;
1204
+ }
1205
+ } else if (input.record.type === "assistant") {
1206
+ const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
1207
+ const content = Array.isArray(message.content) ? message.content : [];
1208
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
1209
+ if (fullText.length > nextAssistantText.length)
1210
+ nextAssistantText = fullText;
1211
+ }
1212
+ if (nextAssistantText !== input.assistantText) {
1213
+ appendRunTimeline(input.projectRoot, input.runId, {
1214
+ id: input.messageId,
1215
+ type: "assistant_message",
1216
+ text: nextAssistantText,
1217
+ state: "streaming",
1218
+ createdAt: new Date().toISOString()
1219
+ });
1220
+ }
1221
+ return nextAssistantText;
1222
+ }
1223
+ function appendToolTimelineFromLog(input) {
1224
+ const title = typeof input.log.title === "string" ? input.log.title : "";
1225
+ if (title !== "Tool activity")
1226
+ return;
1227
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
1228
+ const toolName = typeof payload.toolName === "string" && payload.toolName.trim() ? payload.toolName.trim() : typeof payload.tool_name === "string" && payload.tool_name.trim() ? payload.tool_name.trim() : null;
1229
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
1230
+ appendRunTimeline(input.projectRoot, input.runId, {
1231
+ id: `tool:${logId}`,
1232
+ type: "tool_execution_update",
1233
+ toolName,
1234
+ status: typeof input.log.status === "string" ? input.log.status : "running",
1235
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
1236
+ payload,
1237
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
1238
+ });
1239
+ }
998
1240
  function readTaskRunReviewStatus(reviewStatusPath) {
999
- if (!existsSync2(reviewStatusPath)) {
1241
+ if (!existsSync4(reviewStatusPath)) {
1000
1242
  return null;
1001
1243
  }
1002
1244
  try {
1003
- const status = readFileSync2(reviewStatusPath, "utf8").trim().toUpperCase();
1245
+ const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
1004
1246
  return status === "APPROVED" || status === "REJECTED" ? status : null;
1005
1247
  } catch {
1006
1248
  return null;
@@ -1018,7 +1260,38 @@ function isTaskRunReviewRejected(reviewState) {
1018
1260
  function runSourceTaskIdentity(sourceTask) {
1019
1261
  return sourceTask;
1020
1262
  }
1021
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
1263
+ function sourceTaskIssueNodeId(sourceTask) {
1264
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
1265
+ return null;
1266
+ const record = sourceTask;
1267
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
1268
+ if (direct?.trim())
1269
+ return direct.trim();
1270
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
1271
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
1272
+ }
1273
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
1274
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
1275
+ id: input.taskId,
1276
+ ...input.update.status ? { status: input.update.status } : {},
1277
+ ...input.update.comment ? { comment: input.update.comment } : {},
1278
+ ...input.update.title ? { title: input.update.title } : {},
1279
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
1280
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
1281
+ });
1282
+ if (serverResult.ok === false) {
1283
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
1284
+ }
1285
+ return {
1286
+ updated: serverResult.ok !== false,
1287
+ taskId: input.taskId,
1288
+ status: input.update.status,
1289
+ source: "server",
1290
+ sourceKind: "server",
1291
+ projectSync: serverResult.projectSync
1292
+ };
1293
+ };
1294
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
1022
1295
  if (!taskId)
1023
1296
  return;
1024
1297
  const config = await loadTaskRunAutomationConfig(projectRoot);
@@ -1187,15 +1460,15 @@ async function executeRigOwnedTaskRun(context, input) {
1187
1460
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1188
1461
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1189
1462
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1190
- const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1463
+ const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
1191
1464
  const persistedPlanning = {
1192
1465
  ...planningClassification,
1193
1466
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1194
1467
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1195
1468
  classifiedAt: new Date().toISOString()
1196
1469
  };
1197
- mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1198
- writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1470
+ mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1471
+ writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1199
1472
  `, "utf8");
1200
1473
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1201
1474
  prompt = `${prompt}
@@ -1245,7 +1518,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1245
1518
  let verificationStarted = false;
1246
1519
  let reviewStarted = false;
1247
1520
  let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1248
- let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve4(existingRunRecord.sessionPath, "..") : null;
1521
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
1249
1522
  let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1250
1523
  let latestProviderCommand = null;
1251
1524
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
@@ -1328,10 +1601,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1328
1601
  patchAuthorityRun(context.projectRoot, input.runId, {
1329
1602
  status: "running",
1330
1603
  worktreePath: latestRuntimeWorkspace,
1331
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve4(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1604
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1332
1605
  logRoot: latestLogsDir,
1333
- sessionPath: latestSessionDir ? resolve4(latestSessionDir, "session.json") : null,
1334
- sessionLogPath: latestLogsDir ? resolve4(latestLogsDir, "agent-stdout.log") : null,
1606
+ sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
1607
+ sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
1335
1608
  branch: runtimeId
1336
1609
  });
1337
1610
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -1339,8 +1612,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1339
1612
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1340
1613
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1341
1614
  if (readyFile) {
1342
- mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1343
- writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1615
+ mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
1616
+ writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1344
1617
  `, "utf8");
1345
1618
  }
1346
1619
  appendRunLog(context.projectRoot, input.runId, {
@@ -1488,7 +1761,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1488
1761
  if (providerLogs.length > 0) {
1489
1762
  for (const providerLog of providerLogs) {
1490
1763
  appendRunLog(context.projectRoot, input.runId, providerLog);
1764
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
1491
1765
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
1766
+ if (providerLog.title === "Tool activity")
1767
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1492
1768
  }
1493
1769
  }
1494
1770
  if (input.runtimeAdapter === "codex") {
@@ -1646,7 +1922,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1646
1922
  let acceptedArtifactObservedAt = null;
1647
1923
  let acceptedArtifactPollTimer = null;
1648
1924
  let acceptedArtifactKillTimer = null;
1649
- const attemptExit = await new Promise((resolve5) => {
1925
+ const attemptExit = await new Promise((resolve7) => {
1650
1926
  let settled = false;
1651
1927
  const settle = (result) => {
1652
1928
  if (settled)
@@ -1654,7 +1930,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1654
1930
  settled = true;
1655
1931
  if (acceptedArtifactPollTimer)
1656
1932
  clearInterval(acceptedArtifactPollTimer);
1657
- resolve5(result);
1933
+ resolve7(result);
1658
1934
  };
1659
1935
  const pollAcceptedArtifacts = () => {
1660
1936
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -1727,7 +2003,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1727
2003
  });
1728
2004
  for (const pendingLog of pendingLogs) {
1729
2005
  appendRunLog(context.projectRoot, input.runId, pendingLog);
2006
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
1730
2007
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2008
+ if (pendingLog.title === "Tool activity")
2009
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1731
2010
  }
1732
2011
  process.off("SIGTERM", forwardSigterm);
1733
2012
  if (attemptExit.error) {
@@ -1853,8 +2132,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1853
2132
  }
1854
2133
  if (planningClassification.planningRequired) {
1855
2134
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1856
- const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1857
- if (!existsSync2(expectedPlanPath)) {
2135
+ const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
2136
+ if (!existsSync4(expectedPlanPath)) {
1858
2137
  const failedAt = new Date().toISOString();
1859
2138
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1860
2139
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -1874,6 +2153,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1874
2153
  throw new CliError2(failureDetail, 1);
1875
2154
  }
1876
2155
  }
2156
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
2157
+ appendPiStageLog({
2158
+ projectRoot: context.projectRoot,
2159
+ runId: input.runId,
2160
+ stage: "Validate",
2161
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
2162
+ status: "completed"
2163
+ });
2164
+ if (verificationAction && !reviewStarted) {
2165
+ verificationAction.complete("Completion verification checks finished.");
2166
+ }
2167
+ if (!reviewAction) {
2168
+ promoteToReviewing("Server-owned closeout is queued.");
2169
+ }
2170
+ if (reviewAction) {
2171
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
2172
+ }
2173
+ const requestedAt = new Date().toISOString();
2174
+ patchAuthorityRun(context.projectRoot, input.runId, {
2175
+ status: "reviewing",
2176
+ completedAt: null,
2177
+ errorText: null,
2178
+ serverCloseout: {
2179
+ status: "pending",
2180
+ phase: "queued",
2181
+ requestedAt,
2182
+ updatedAt: requestedAt,
2183
+ runtimeWorkspace: latestRuntimeWorkspace,
2184
+ branch: latestRuntimeBranch,
2185
+ taskId: input.taskId ?? runtimeTaskId
2186
+ }
2187
+ });
2188
+ appendRunLog(context.projectRoot, input.runId, {
2189
+ id: `log:${input.runId}:server-closeout-requested`,
2190
+ title: "Server-owned closeout requested",
2191
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
2192
+ tone: "info",
2193
+ status: "reviewing",
2194
+ createdAt: requestedAt,
2195
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
2196
+ });
2197
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
2198
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
2199
+ await context.emitEvent("command.finished", {
2200
+ command: [
2201
+ "rig",
2202
+ "server",
2203
+ "task-run",
2204
+ ...input.taskId ? ["--task", input.taskId] : [],
2205
+ ...input.title ? ["--title", input.title] : []
2206
+ ],
2207
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2208
+ exitCode: 0,
2209
+ durationMs: 0,
2210
+ startedAt,
2211
+ finishedAt: requestedAt
2212
+ });
2213
+ return;
2214
+ }
1877
2215
  const runPiPrFeedbackFix = async (message) => {
1878
2216
  appendPiStageLog({
1879
2217
  projectRoot: context.projectRoot,
@@ -1901,11 +2239,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1901
2239
  child.stdin.write(message);
1902
2240
  }
1903
2241
  child.stdin.end();
2242
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
2243
+ let feedbackAssistantText = "";
2244
+ const feedbackPendingToolUses = new Map;
1904
2245
  const stdout = createLineInterface({ input: child.stdout });
1905
2246
  stdout.on("line", (line) => {
1906
2247
  const trimmed = line.trim();
1907
2248
  if (!trimmed)
1908
2249
  return;
2250
+ try {
2251
+ const record = JSON.parse(trimmed);
2252
+ const providerLogs = buildClaudeLogsFromRecord({
2253
+ runId: input.runId,
2254
+ record,
2255
+ createdAtFallback: new Date().toISOString(),
2256
+ status: "reviewing",
2257
+ pendingToolUses: feedbackPendingToolUses
2258
+ });
2259
+ for (const providerLog of providerLogs) {
2260
+ appendRunLog(context.projectRoot, input.runId, providerLog);
2261
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2262
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2263
+ if (providerLog.title === "Tool activity")
2264
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2265
+ }
2266
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
2267
+ projectRoot: context.projectRoot,
2268
+ runId: input.runId,
2269
+ messageId: feedbackAssistantMessageId,
2270
+ record,
2271
+ assistantText: feedbackAssistantText
2272
+ });
2273
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
2274
+ if (hadAssistantDelta) {
2275
+ feedbackAssistantText = nextFeedbackAssistantText;
2276
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2277
+ }
2278
+ if (providerLogs.length > 0 || hadAssistantDelta)
2279
+ return;
2280
+ } catch {}
1909
2281
  appendRunLog(context.projectRoot, input.runId, {
1910
2282
  id: nextRunLogId(),
1911
2283
  title: "Pi PR feedback fix output",
@@ -1931,10 +2303,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1931
2303
  });
1932
2304
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
1933
2305
  });
1934
- const exitCode = await new Promise((resolve5) => {
1935
- child.once("error", () => resolve5(1));
1936
- child.once("close", (code) => resolve5(code ?? 1));
2306
+ const exitCode = await new Promise((resolve7) => {
2307
+ child.once("error", () => resolve7(1));
2308
+ child.once("close", (code) => resolve7(code ?? 1));
1937
2309
  });
2310
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
2311
+ runId: input.runId,
2312
+ status: exitCode === 0 ? "completed" : "failed",
2313
+ pendingToolUses: feedbackPendingToolUses
2314
+ })) {
2315
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
2316
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2317
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2318
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2319
+ }
2320
+ if (feedbackAssistantText.trim()) {
2321
+ appendRunTimeline(context.projectRoot, input.runId, {
2322
+ id: feedbackAssistantMessageId,
2323
+ type: "assistant_message",
2324
+ text: feedbackAssistantText,
2325
+ state: "completed",
2326
+ createdAt: new Date().toISOString(),
2327
+ completedAt: new Date().toISOString()
2328
+ });
2329
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2330
+ }
1938
2331
  if (exitCode !== 0) {
1939
2332
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
1940
2333
  }