runpane 2.4.186 → 2.4.187

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.
@@ -37,6 +37,8 @@ exports.runPanelsInput = runPanelsInput;
37
37
  exports.runPanelsScreen = runPanelsScreen;
38
38
  exports.runPanelsLastMessage = runPanelsLastMessage;
39
39
  exports.runReport = runReport;
40
+ exports.runInbox = runInbox;
41
+ exports.runInboxAck = runInboxAck;
40
42
  exports.runPanelsSubmit = runPanelsSubmit;
41
43
  exports.markSuggestionLine = markSuggestionLine;
42
44
  exports.printPromptNotes = printPromptNotes;
@@ -54,6 +56,7 @@ const node_process_1 = require("node:process");
54
56
  const promises_1 = require("node:readline/promises");
55
57
  const boundaryDecoder_1 = require("./boundaryDecoder");
56
58
  const daemonClient_1 = require("./daemonClient");
59
+ const errors_1 = require("./errors");
57
60
  const contract_1 = require("./generated/contract");
58
61
  const commands_1 = require("./commands");
59
62
  const sessionPayload_1 = require("./sessionPayload");
@@ -182,6 +185,7 @@ const agentReportFields = {
182
185
  reportedAt: boundaryDecoder_1.boundary.string,
183
186
  };
184
187
  const agentReportSchema = boundaryDecoder_1.boundary.object(agentReportFields);
188
+ const expectedReportSchema = boundaryDecoder_1.boundary.object({ since: boundaryDecoder_1.boundary.string });
185
189
  const reportResultSchema = boundaryDecoder_1.boundary.object({
186
190
  ok: boundaryDecoder_1.boundary.literal(true),
187
191
  generation: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.number),
@@ -326,6 +330,7 @@ const orchestrationPanelOverviewSchema = boundaryDecoder_1.boundary.object({
326
330
  initialized: boundaryDecoder_1.boundary.boolean,
327
331
  lastActivityAt: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.nonEmptyString),
328
332
  missing: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.boolean),
333
+ report: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.object({ ...agentReportFields, data: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.jsonObject) })),
329
334
  });
330
335
  const orchestrationPaneOverviewSchema = boundaryDecoder_1.boundary.object({
331
336
  paneId: boundaryDecoder_1.boundary.nonEmptyString,
@@ -489,7 +494,6 @@ const paneCostResultSchema = boundaryDecoder_1.boundary.object({
489
494
  });
490
495
  exports.paneCreateResultSchema = boundaryDecoder_1.boundary.object({
491
496
  ok: boundaryDecoder_1.boundary.boolean,
492
- generation: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.number),
493
497
  repo: repoSummarySchema,
494
498
  items: boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.union(boundaryDecoder_1.boundary.object({
495
499
  ok: boundaryDecoder_1.boundary.boolean,
@@ -516,7 +520,9 @@ exports.paneCreateResultSchema = boundaryDecoder_1.boundary.object({
516
520
  error: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
517
521
  })),
518
522
  promptFile: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
523
+ attachments: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string)),
519
524
  warnings: promptWarningsSchema,
525
+ expectedReport: boundaryDecoder_1.boundary.optional(expectedReportSchema),
520
526
  setupQueue: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.object({
521
527
  position: boundaryDecoder_1.boundary.number,
522
528
  message: boundaryDecoder_1.boundary.string,
@@ -540,6 +546,8 @@ exports.paneCreateResultSchema = boundaryDecoder_1.boundary.object({
540
546
  code: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
541
547
  }),
542
548
  }))),
549
+ epoch: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
550
+ generation: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.number),
543
551
  });
544
552
  const paneArchiveResultSchema = boundaryDecoder_1.boundary.union(boundaryDecoder_1.boundary.object({
545
553
  ok: boundaryDecoder_1.boundary.literal(false),
@@ -681,7 +689,6 @@ const panelResumeResultSchema = boundaryDecoder_1.boundary.object({
681
689
  });
682
690
  exports.panelCreateResultSchema = boundaryDecoder_1.boundary.object({
683
691
  ok: boundaryDecoder_1.boundary.boolean,
684
- generation: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.number),
685
692
  paneId: boundaryDecoder_1.boundary.string,
686
693
  panelId: boundaryDecoder_1.boundary.string,
687
694
  title: boundaryDecoder_1.boundary.string,
@@ -695,8 +702,12 @@ exports.panelCreateResultSchema = boundaryDecoder_1.boundary.object({
695
702
  readiness: boundaryDecoder_1.boundary.optional(panelReadinessSchema),
696
703
  initialInput: boundaryDecoder_1.boundary.optional(initialInputSchema),
697
704
  promptFile: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
705
+ attachments: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string)),
698
706
  warnings: promptWarningsSchema,
707
+ expectedReport: boundaryDecoder_1.boundary.optional(expectedReportSchema),
699
708
  nextCommand: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
709
+ epoch: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
710
+ generation: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.number),
700
711
  });
701
712
  const panelOutputResultSchema = boundaryDecoder_1.boundary.object({
702
713
  ok: boundaryDecoder_1.boundary.literal(true),
@@ -740,7 +751,6 @@ exports.panelScreenResultSchema = boundaryDecoder_1.boundary.object({
740
751
  });
741
752
  exports.panelSubmitResultSchema = boundaryDecoder_1.boundary.object({
742
753
  ok: boundaryDecoder_1.boundary.boolean,
743
- generation: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.number),
744
754
  panelId: boundaryDecoder_1.boundary.string,
745
755
  paneId: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
746
756
  inputBytes: boundaryDecoder_1.boundary.number,
@@ -752,8 +762,12 @@ exports.panelSubmitResultSchema = boundaryDecoder_1.boundary.object({
752
762
  sentAt: boundaryDecoder_1.boundary.string,
753
763
  blocked: boundaryDecoder_1.boundary.optional(panelBlockedSchema),
754
764
  promptFile: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
765
+ attachments: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string)),
755
766
  warnings: promptWarningsSchema,
767
+ expectedReport: boundaryDecoder_1.boundary.optional(expectedReportSchema),
756
768
  nextCommand: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
769
+ epoch: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
770
+ generation: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.number),
757
771
  });
758
772
  const panelSubmitComposerResultSchema = boundaryDecoder_1.boundary.object({
759
773
  ok: boundaryDecoder_1.boundary.boolean,
@@ -805,7 +819,7 @@ const agentDoctorResultSchema = boundaryDecoder_1.boundary.object({
805
819
  })),
806
820
  warnings: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string)),
807
821
  });
808
- const workspaceEntryKindSchema = boundaryDecoder_1.boundary.enumeration('agent.ready', 'agent.busy', 'agent.blocked', 'agent.unknown', 'agent.idle', 'pane.created', 'pane.gone', 'panel.exited', 'panel.stopped', 'pane.associated', 'pane.detached', 'pr.conflicted', 'pr.checks', 'pr.merged', 'pr.closed', 'pr.head', 'pr.review', 'pr.comment', 'pr.stopped', 'github.unavailable', 'github.recovered', 'agent.report');
822
+ const workspaceEntryKindSchema = boundaryDecoder_1.boundary.enumeration('agent.ready', 'agent.busy', 'agent.blocked', 'agent.unknown', 'agent.idle', 'pane.created', 'pane.gone', 'panel.exited', 'panel.stopped', 'pane.associated', 'pane.detached', 'pr.conflicted', 'pr.checks', 'pr.merged', 'pr.closed', 'pr.head', 'pr.base_changed', 'pr.base_deleted', 'pr.review', 'pr.comment', 'pr.stopped', 'github.unavailable', 'github.recovered', 'agent.report', 'agent.report.missing');
809
823
  const agentStateSchema = boundaryDecoder_1.boundary.enumeration('blocked', 'working', 'idle', 'unknown');
810
824
  const workspacePanelSummarySchema = boundaryDecoder_1.boundary.object({
811
825
  panelId: boundaryDecoder_1.boundary.string,
@@ -843,10 +857,15 @@ const workspaceEntrySchema = boundaryDecoder_1.boundary.object({
843
857
  sessionId: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
844
858
  sessionName: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
845
859
  pr: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.object({ number: boundaryDecoder_1.boundary.number, url: boundaryDecoder_1.boundary.string, headOid: boundaryDecoder_1.boundary.string })),
860
+ base: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.object({ from: boundaryDecoder_1.boundary.string, to: boundaryDecoder_1.boundary.string })),
846
861
  checks: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.enumeration('passed', 'failed')),
847
862
  failingChecks: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string)),
848
863
  report: boundaryDecoder_1.boundary.optional(agentReportSchema),
849
864
  panels: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(workspacePanelSummarySchema)),
865
+ lastOutput: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
866
+ lastOutputSource: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.enumeration('transcript', 'screen')),
867
+ expectedSince: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
868
+ screenLines: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string)),
850
869
  });
851
870
  exports.workspaceStateResultSchema = boundaryDecoder_1.boundary.object({
852
871
  ok: boundaryDecoder_1.boundary.literal(true),
@@ -1048,7 +1067,11 @@ async function runSessionsOverview(parsed) {
1048
1067
  for (const pane of result.panes) {
1049
1068
  const details = pane.missing ? 'missing' : pane.archived ? 'archived' : pane.panels.map(panel => `${panel.title}=${panel.state}`).join(', ') || 'no terminal panels';
1050
1069
  console.log(` ${pane.name}: ${details}`);
1051
- if (pane.report) {
1070
+ if (pane.panels.some(panel => panel.report)) {
1071
+ for (const panel of pane.panels)
1072
+ console.log(` ${describePanelOverview(panel)}`);
1073
+ }
1074
+ else if (pane.report) {
1052
1075
  console.log(` report ${(0, watchLines_1.describeReport)(pane.report)} (panel ${pane.report.panelId}, ${pane.report.reportedAt})`);
1053
1076
  }
1054
1077
  }
@@ -1057,6 +1080,11 @@ async function runSessionsOverview(parsed) {
1057
1080
  }
1058
1081
  return 0;
1059
1082
  }
1083
+ /** `Reviewer (panel <id>): idle, report ready pr#747 fc5dce9 at <reportedAt>`; a panel without a report shows only its state. */
1084
+ function describePanelOverview(panel) {
1085
+ const line = `${panel.title} (panel ${panel.panelId}): ${panel.state}`;
1086
+ return panel.report ? `${line}, report ${(0, watchLines_1.describeReport)(panel.report)} at ${panel.report.reportedAt}` : line;
1087
+ }
1060
1088
  /** One daemon call waits at most this long; longer waits chain calls, each blocking in the daemon. */
1061
1089
  const LOCK_WAIT_PER_CALL_MS = 120_000;
1062
1090
  async function runLockAcquire(parsed) {
@@ -1220,7 +1248,7 @@ async function runPrWatchCommand(parsed) {
1220
1248
  printJson(result);
1221
1249
  return 0;
1222
1250
  }
1223
- async function runWatch(parsed) {
1251
+ async function runWatch(parsed, dispatch) {
1224
1252
  if (parsed.watchAs && parsed.watchSince !== undefined) {
1225
1253
  throw new Error('runpane watch accepts either --as or --since, not both.');
1226
1254
  }
@@ -1299,6 +1327,10 @@ async function runWatch(parsed) {
1299
1327
  return 0;
1300
1328
  }
1301
1329
  if (deadlineAt !== undefined && Date.now() >= deadlineAt) {
1330
+ if (dispatch) {
1331
+ printDispatchResult({ ok: false, outcome: 'timeout', ...dispatch, generation: request.since ?? 0 });
1332
+ return 124;
1333
+ }
1302
1334
  emitControlLine((0, watchLines_1.formatNonEntry)('_timeout', { timeoutMs: parsed.timeoutMs }, format));
1303
1335
  return 124;
1304
1336
  }
@@ -1329,7 +1361,8 @@ async function runWatch(parsed) {
1329
1361
  throw new Error('This Pane daemon does not support runpane watch --session; update Pane, or pass one --pane per Session Pane.');
1330
1362
  }
1331
1363
  if (failingCode) {
1332
- emitControlLine((0, watchLines_1.formatNonEntry)('_reconnected', { generation: result.generation }, format));
1364
+ if (!dispatch)
1365
+ emitControlLine((0, watchLines_1.formatNonEntry)('_reconnected', { generation: result.generation }, format));
1333
1366
  failingCode = undefined;
1334
1367
  }
1335
1368
  if (!armed && (keepsWaiting || parsed.selfTest)) {
@@ -1344,11 +1377,27 @@ async function runWatch(parsed) {
1344
1377
  if (parsed.untilEvent) {
1345
1378
  // A reset restates current state; it never ends the wait. Nor does the caller's own panel.
1346
1379
  if (result.reset || result.dropped !== undefined) {
1380
+ if (dispatch) {
1381
+ printDispatchResult({ ok: false, outcome: 'indeterminate', ...dispatch, generation: result.generation,
1382
+ reason: result.reset?.reason ?? 'history-dropped',
1383
+ nextCommand: `runpane panels screen --panel ${dispatch.panelId ?? '<panel-id>'} --json; runpane inbox --json` });
1384
+ return 6;
1385
+ }
1347
1386
  for (const line of (0, watchLines_1.formatWaitResult)(result, format))
1348
1387
  emitControlLine(line);
1349
1388
  }
1350
1389
  else {
1351
- const entries = ownPanelId ? result.entries.filter(entry => entry.panelId !== ownPanelId) : result.entries;
1390
+ if (dispatch) {
1391
+ const entry = result.entries.find(candidate => !candidate.replay && !candidate.baseline
1392
+ && isDispatchOutcomeKind(candidate.kind)
1393
+ && (candidate.kind === 'pane.gone' || !dispatch.panelId || candidate.panelId === dispatch.panelId));
1394
+ if (entry && isDispatchOutcomeKind(entry.kind)) {
1395
+ const { outcome, exitCode } = DISPATCH_OUTCOMES[entry.kind];
1396
+ printDispatchResult(compactDispatchResult(outcome, entry));
1397
+ return exitCode;
1398
+ }
1399
+ }
1400
+ const entries = dispatch ? [] : ownPanelId ? result.entries.filter(entry => entry.panelId !== ownPanelId) : result.entries;
1352
1401
  if (entries.length > 0) {
1353
1402
  for (const line of (0, watchLines_1.formatWaitResult)({ ...result, entries }, format))
1354
1403
  emitWatchLine(line);
@@ -1383,7 +1432,8 @@ async function runWatch(parsed) {
1383
1432
  }
1384
1433
  const code = watchErrorCode(watchError);
1385
1434
  if (failingCode !== code || heartbeatMs > 0 && Date.now() - lastFailureAt >= heartbeatMs) {
1386
- emitWatchLine((0, watchLines_1.formatNonEntry)('_error', { code, message: watchError.message }, format));
1435
+ if (!dispatch)
1436
+ emitWatchLine((0, watchLines_1.formatNonEntry)('_error', { code, message: watchError.message }, format));
1387
1437
  lastFailureAt = Date.now();
1388
1438
  }
1389
1439
  failingCode = code;
@@ -1446,11 +1496,26 @@ function isRetryableWatchError(error) {
1446
1496
  }
1447
1497
  async function runPanesCreate(parsed) {
1448
1498
  const request = await buildPaneCreateRequest(parsed);
1499
+ if (parsed.waitForResult && request.panes.length !== 1)
1500
+ throw new errors_1.RunpaneError('ERR_RUNPANE_WAIT_MULTI_PANE', 'panes create --wait accepts one Pane, but this request has several. No workers were created.', 'Create several without --wait, then dispatch each with panels submit --wait.');
1501
+ if (parsed.expectReport !== undefined)
1502
+ request.expectReport = parsed.expectReport;
1449
1503
  await confirmPaneCreate(parsed, request);
1450
1504
  const result = await (0, daemonClient_1.invokeDaemon)('runpane:panes:create', [request], exports.paneCreateResultSchema, {
1451
1505
  paneDir: parsed.paneDir,
1452
1506
  timeoutMs: (parsed.timeoutMs ?? 120_000) + (parsed.readyTimeoutMs ?? 30_000) + 10_000,
1453
1507
  });
1508
+ if (parsed.waitForResult) {
1509
+ const [item] = result.items;
1510
+ if (result.items.length !== 1) {
1511
+ printJson(result);
1512
+ throw new errors_1.RunpaneError('ERR_RUNPANE_WAIT_CREATE_RESULTS', 'Pane returned an unexpected number of creation results; the creation results are printed above.', 'Inspect the returned ids before retrying.');
1513
+ }
1514
+ // A Pane queued behind setup has no panel yet; its first agent panel's events still name the Pane.
1515
+ if (result.ok && item && 'paneId' in item && item.paneId) {
1516
+ return waitForDispatchResult(parsed, { paneId: item.paneId, panelId: item.panelId, since: result.generation, epoch: result.epoch });
1517
+ }
1518
+ }
1454
1519
  if (parsed.json) {
1455
1520
  printJson(result);
1456
1521
  }
@@ -1661,11 +1726,16 @@ async function runPanelsList(parsed) {
1661
1726
  }
1662
1727
  async function runPanelsCreate(parsed) {
1663
1728
  const request = await buildPanelCreateRequest(parsed);
1729
+ if (parsed.expectReport !== undefined)
1730
+ request.expectReport = parsed.expectReport;
1664
1731
  await confirmPanelCreate(parsed, request);
1665
1732
  const result = await (0, daemonClient_1.invokeDaemon)('runpane:panels:create', [request], exports.panelCreateResultSchema, {
1666
1733
  paneDir: parsed.paneDir,
1667
1734
  timeoutMs: (parsed.readyTimeoutMs ?? 30_000) + 10_000,
1668
1735
  });
1736
+ if (parsed.waitForResult && result.ok) {
1737
+ return waitForDispatchResult(parsed, { paneId: result.paneId, panelId: result.panelId, since: result.generation, epoch: result.epoch });
1738
+ }
1669
1739
  if (parsed.json) {
1670
1740
  printJson(result);
1671
1741
  }
@@ -1852,12 +1922,115 @@ function describeReportDelivery(result) {
1852
1922
  + 'and a watch with a named cursor (runpane watch --as <name> --kinds agent.report) still receives it when it next waits.');
1853
1923
  return lines;
1854
1924
  }
1925
+ const inboxItemSchema = boundaryDecoder_1.boundary.object({
1926
+ id: boundaryDecoder_1.boundary.string,
1927
+ sessionId: boundaryDecoder_1.boundary.string,
1928
+ kind: boundaryDecoder_1.boundary.enumeration('report', 'report.missing', 'blocked'),
1929
+ at: boundaryDecoder_1.boundary.string,
1930
+ paneId: boundaryDecoder_1.boundary.string,
1931
+ paneName: boundaryDecoder_1.boundary.string,
1932
+ panelId: boundaryDecoder_1.boundary.string,
1933
+ panelTitle: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
1934
+ agentType: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
1935
+ report: boundaryDecoder_1.boundary.optional(agentReportSchema),
1936
+ lastOutput: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
1937
+ lastOutputSource: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.enumeration('transcript', 'screen')),
1938
+ reason: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
1939
+ screenLines: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string)),
1940
+ });
1941
+ const inboxListResultSchema = boundaryDecoder_1.boundary.object({
1942
+ ok: boundaryDecoder_1.boundary.literal(true),
1943
+ session: boundaryDecoder_1.boundary.object({ id: boundaryDecoder_1.boundary.string, name: boundaryDecoder_1.boundary.string }),
1944
+ items: boundaryDecoder_1.boundary.array(inboxItemSchema),
1945
+ });
1946
+ const inboxAckResultSchema = boundaryDecoder_1.boundary.object({
1947
+ ok: boundaryDecoder_1.boundary.literal(true),
1948
+ acknowledged: boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string),
1949
+ unknown: boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string),
1950
+ });
1951
+ const MAX_INBOX_DETAIL_LENGTH = 300;
1952
+ /** `--session`, else the Session whose orchestrator this terminal is. */
1953
+ function inboxSession(parsed, env) {
1954
+ return parsed.sessionId?.trim() || env.PANE_ORCHESTRATION_SESSION_ID?.trim() || undefined;
1955
+ }
1956
+ async function runInbox(parsed, env = process.env) {
1957
+ const session = inboxSession(parsed, env);
1958
+ if (!session) {
1959
+ throw new Error('runpane inbox needs --session <id|name> outside a Session orchestrator (PANE_ORCHESTRATION_SESSION_ID).');
1960
+ }
1961
+ const result = await (0, daemonClient_1.invokeDaemon)('runpane:inbox:list', [{ session }], inboxListResultSchema, { paneDir: parsed.paneDir });
1962
+ if (parsed.json) {
1963
+ printJson(result);
1964
+ return 0;
1965
+ }
1966
+ if (result.items.length === 0) {
1967
+ console.log(`Inbox for ${result.session.name}: nothing unacknowledged.`);
1968
+ return 0;
1969
+ }
1970
+ console.log(`Inbox for ${result.session.name}: ${result.items.length} unacknowledged.`);
1971
+ for (const item of result.items)
1972
+ console.log(formatInboxItem(item));
1973
+ console.log(`Clear handled items with: runpane inbox ack ${result.items.map(item => item.id).join(' ')}`);
1974
+ return 0;
1975
+ }
1976
+ async function runInboxAck(parsed, env = process.env) {
1977
+ const session = parsed.all ? inboxSession(parsed, env) : parsed.sessionId;
1978
+ if (parsed.all && !session) {
1979
+ throw new Error('runpane inbox ack --all needs --session <id|name> outside a Session orchestrator.');
1980
+ }
1981
+ const result = await (0, daemonClient_1.invokeDaemon)('runpane:inbox:ack', [{
1982
+ ids: parsed.all ? undefined : parsed.inboxIds,
1983
+ session,
1984
+ all: parsed.all || undefined,
1985
+ }], inboxAckResultSchema, { paneDir: parsed.paneDir });
1986
+ if (parsed.json) {
1987
+ printJson(result);
1988
+ }
1989
+ else {
1990
+ console.log(`Acknowledged ${result.acknowledged.length} inbox item${result.acknowledged.length === 1 ? '' : 's'}.`);
1991
+ if (result.unknown.length > 0)
1992
+ console.log(`Not found (already acknowledged?): ${result.unknown.join(', ')}`);
1993
+ }
1994
+ return 0;
1995
+ }
1996
+ /** `<id> REPORT <pane> pane <id> panel <id> ready pr#12 abc1234`, then indented detail lines. */
1997
+ function formatInboxItem(item) {
1998
+ const where = `${oneLine(item.paneName)} pane ${item.paneId} panel ${item.panelId}`;
1999
+ if (item.kind === 'report') {
2000
+ const lines = [`${item.id} REPORT ${where} ${item.report ? (0, watchLines_1.describeReport)(item.report) : 'unknown'}`];
2001
+ if (item.report?.summary)
2002
+ lines.push(` ${clip(oneLine(item.report.summary))}`);
2003
+ return lines.join('\n');
2004
+ }
2005
+ if (item.kind === 'report.missing') {
2006
+ const lines = [`${item.id} MISSING REPORT ${where} ${item.reason ?? 'ready'} without runpane report`];
2007
+ if (item.lastOutput)
2008
+ lines.push(` last ${item.lastOutputSource ?? 'output'}: ${clip(oneLine(item.lastOutput), true)}`);
2009
+ return lines.join('\n');
2010
+ }
2011
+ const lines = [`${item.id} BLOCKED ${where} waiting on a prompt`];
2012
+ for (const line of (item.screenLines ?? []).slice(-8))
2013
+ lines.push(` | ${line}`);
2014
+ return lines.join('\n');
2015
+ }
2016
+ function oneLine(text) {
2017
+ return text.replace(/\s+/gu, ' ').trim();
2018
+ }
2019
+ /** Cut to the detail length, keeping the start, or the end when `tail` (an agent's conclusion is last). */
2020
+ function clip(text, tail = false) {
2021
+ if (text.length <= MAX_INBOX_DETAIL_LENGTH)
2022
+ return text;
2023
+ return tail ? `…${text.slice(text.length - MAX_INBOX_DETAIL_LENGTH + 1)}` : `${text.slice(0, MAX_INBOX_DETAIL_LENGTH - 1)}…`;
2024
+ }
1855
2025
  async function runPanelsSubmit(parsed) {
1856
2026
  const request = buildPanelInputRequest(parsed, 'submit');
1857
2027
  await confirmPanelInput(parsed, request, 'submit');
1858
2028
  const result = await (0, daemonClient_1.invokeDaemon)('runpane:panels:submit', [request], exports.panelSubmitResultSchema, {
1859
2029
  paneDir: parsed.paneDir,
1860
2030
  });
2031
+ if (parsed.waitForResult && result.ok) {
2032
+ return waitForDispatchResult(parsed, { paneId: result.paneId, panelId: result.panelId, since: result.generation, epoch: result.epoch });
2033
+ }
1861
2034
  if (parsed.json) {
1862
2035
  printJson(result);
1863
2036
  }
@@ -1876,6 +2049,64 @@ async function runPanelsSubmit(parsed) {
1876
2049
  }
1877
2050
  return result.ok ? 0 : 1;
1878
2051
  }
2052
+ /** Each way a `--wait` dispatch ends, the journal kind behind it, and the exit code it returns. */
2053
+ const DISPATCH_OUTCOMES = {
2054
+ 'agent.report': { outcome: 'report', exitCode: 0 },
2055
+ 'agent.report.missing': { outcome: 'missing', exitCode: 3 },
2056
+ 'agent.blocked': { outcome: 'blocked', exitCode: 4 },
2057
+ 'panel.exited': { outcome: 'exited', exitCode: 5 },
2058
+ 'pane.gone': { outcome: 'gone', exitCode: 5 },
2059
+ };
2060
+ const DISPATCH_WAIT_KINDS = ['agent.report', 'agent.report.missing', 'agent.blocked', 'panel.exited', 'pane.gone'];
2061
+ function isDispatchOutcomeKind(kind) {
2062
+ return Object.hasOwn(DISPATCH_OUTCOMES, kind);
2063
+ }
2064
+ /**
2065
+ * `--wait`: block until the dispatched panel reports, misses its report, waits on a prompt, or
2066
+ * exits, then print the result as one line of JSON. `since` is the journal generation the daemon took
2067
+ * before delivering the work, so an event right after delivery is never missed.
2068
+ */
2069
+ async function waitForDispatchResult(parsed, target) {
2070
+ if (!target.paneId || target.since === undefined) {
2071
+ throw new Error('This Pane daemon does not return a journal position, so --wait cannot follow the work. Update Pane, or use runpane watch --until-event.');
2072
+ }
2073
+ return runWatch({ ...parsed, follow: false, untilEvent: true,
2074
+ watchAs: undefined, watchFrom: undefined, watchSince: target.since, watchEpoch: target.epoch,
2075
+ watchKinds: DISPATCH_WAIT_KINDS, watchPaneIds: [target.paneId],
2076
+ sessionId: undefined, repo: undefined, includeShells: true, noHeldInput: true,
2077
+ timeoutMs: parsed.waitTimeoutMs || undefined,
2078
+ }, { paneId: target.paneId, panelId: target.panelId });
2079
+ }
2080
+ function printDispatchResult(result) {
2081
+ console.log(JSON.stringify(result));
2082
+ }
2083
+ /** The `--wait` result: what the orchestrator needs to act, never the transcript. */
2084
+ function compactDispatchResult(outcome, entry) {
2085
+ const { report } = entry;
2086
+ return {
2087
+ ok: outcome === 'report',
2088
+ outcome,
2089
+ paneId: entry.paneId,
2090
+ paneName: entry.paneName,
2091
+ generation: entry.gen,
2092
+ at: entry.at,
2093
+ panelId: entry.panelId,
2094
+ report: report && {
2095
+ state: report.state,
2096
+ pr: report.pr,
2097
+ head: report.head,
2098
+ summary: report.summary,
2099
+ summaryTruncated: report.summaryTruncated,
2100
+ summaryPath: report.summaryPath,
2101
+ question: report.question,
2102
+ },
2103
+ screenLines: entry.screenLines?.length ? entry.screenLines : undefined,
2104
+ lastOutput: entry.lastOutput || undefined,
2105
+ lastOutputSource: entry.lastOutput ? entry.lastOutputSource : undefined,
2106
+ reason: entry.reason || undefined,
2107
+ exitCode: entry.exitCode,
2108
+ };
2109
+ }
1879
2110
  /** Where the prompt went, for human output: `Delivery: queued (transcript)`. */
1880
2111
  function printDelivery(delivery, prefix = '') {
1881
2112
  if (delivery) {
@@ -1907,6 +2138,12 @@ function printPromptNotes(result, prefix = '') {
1907
2138
  if (result.promptFile) {
1908
2139
  console.log(`${prefix}Prompt file: ${result.promptFile}`);
1909
2140
  }
2141
+ for (const attachment of result.attachments ?? []) {
2142
+ console.log(`${prefix}Attached: ${attachment}`);
2143
+ }
2144
+ if (result.expectedReport) {
2145
+ console.log(`${prefix}Expecting a runpane report; without one Pane emits agent.report.missing.`);
2146
+ }
1910
2147
  for (const warning of result.warnings ?? []) {
1911
2148
  console.log(`${prefix}Warning (${warning.code}): ${warning.message}`);
1912
2149
  }
@@ -2038,8 +2275,10 @@ function buildPanelInputRequest(parsed, command = 'input') {
2038
2275
  panelId: parsed.panelId,
2039
2276
  input: parsed.keys ? keysToBytes(parsed.keys) : parsed.panelInputFile ? readInputSource(parsed.panelInputFile) : parsed.panelInput ?? '',
2040
2277
  asFilePointer: parsed.asFilePointer || undefined,
2278
+ attachments: resolveAttachments(parsed, `panels ${command}`),
2041
2279
  interrupt: parsed.interrupt || undefined,
2042
2280
  source: parsed.source === 'user' || parsed.source === 'agent' ? parsed.source : undefined,
2281
+ expectReport: command === 'submit' ? parsed.expectReport : undefined,
2043
2282
  };
2044
2283
  }
2045
2284
  /** `--keys down,enter`: named keys from the contract, or a single literal character each. */
@@ -2187,6 +2426,7 @@ async function buildToolSpec(parsed, command = 'panes create') {
2187
2426
  throw new Error(`--as-file-pointer needs a prompt: pass --prompt or --initial-input-file to runpane ${command}.`);
2188
2427
  }
2189
2428
  const initialInputAsFilePointer = parsed.asFilePointer || undefined;
2429
+ const initialInputAttachments = resolveAttachments(parsed, command);
2190
2430
  // With --tool-command, --agent names the agent the command runs (a wrapper
2191
2431
  // such as `agent-farm run`); Pane launches the command unchanged.
2192
2432
  if (parsed.toolCommand && parsed.agent) {
@@ -2196,6 +2436,7 @@ async function buildToolSpec(parsed, command = 'panes create') {
2196
2436
  title: parsed.title,
2197
2437
  initialInput,
2198
2438
  initialInputAsFilePointer,
2439
+ initialInputAttachments,
2199
2440
  };
2200
2441
  }
2201
2442
  let agent = parsed.agent;
@@ -2211,6 +2452,7 @@ async function buildToolSpec(parsed, command = 'panes create') {
2211
2452
  title: parsed.title,
2212
2453
  initialInput,
2213
2454
  initialInputAsFilePointer,
2455
+ initialInputAttachments,
2214
2456
  };
2215
2457
  }
2216
2458
  if (!parsed.toolCommand) {
@@ -2221,8 +2463,30 @@ async function buildToolSpec(parsed, command = 'panes create') {
2221
2463
  title: parsed.title,
2222
2464
  initialInput,
2223
2465
  initialInputAsFilePointer,
2466
+ initialInputAttachments,
2224
2467
  };
2225
2468
  }
2469
+ /**
2470
+ * `--attach` files as absolute paths. Pane copies them beside the prompt file
2471
+ * (inside the worktree when it can), so they need `--as-file-pointer`.
2472
+ */
2473
+ function resolveAttachments(parsed, command) {
2474
+ if (!parsed.attachments?.length)
2475
+ return undefined;
2476
+ if (!['panes create', 'panels create', 'panels submit'].includes(command)) {
2477
+ throw new errors_1.RunpaneError('ERR_ATTACH_UNSUPPORTED', `--attach is not supported by runpane ${command}. Nothing was sent.`, 'Use --attach with panes create, panels create or panels submit.');
2478
+ }
2479
+ if (!parsed.asFilePointer) {
2480
+ throw new errors_1.RunpaneError('ERR_ATTACH_NEEDS_FILE_POINTER', `--attach needs --as-file-pointer: Pane lists the copied files beside the prompt file. Nothing was sent.`, `Add --as-file-pointer to runpane ${command}.`);
2481
+ }
2482
+ return parsed.attachments.map((file) => {
2483
+ const absolute = node_path_1.default.resolve(file);
2484
+ if (!node_fs_1.default.statSync(absolute, { throwIfNoEntry: false })?.isFile()) {
2485
+ throw new errors_1.RunpaneError('ERR_ATTACH_NOT_FOUND', `--attach ${file}: no such file. Nothing was sent.`, 'Pass an existing file path, then retry.');
2486
+ }
2487
+ return absolute;
2488
+ });
2489
+ }
2226
2490
  function resolveInitialInput(parsed) {
2227
2491
  if (parsed.initialInput && parsed.initialInputFile) {
2228
2492
  throw new Error('Use either --initial-input/--prompt or --initial-input-file/--prompt-file, not both.');
@@ -2499,12 +2763,15 @@ function workspaceLabel(kind) {
2499
2763
  'pr.merged': 'PR MERGED',
2500
2764
  'pr.closed': 'PR.CLOSED',
2501
2765
  'pr.head': 'PR.HEAD',
2766
+ 'pr.base_changed': 'PR.BASE_CHANGED',
2767
+ 'pr.base_deleted': 'PR.BASE_DELETED',
2502
2768
  'pr.review': 'PR.REVIEW',
2503
2769
  'pr.comment': 'PR.COMMENT',
2504
2770
  'pr.stopped': 'PR.STOPPED',
2505
2771
  'github.unavailable': 'GITHUB.UNAVAILABLE',
2506
2772
  'github.recovered': 'GITHUB.RECOVERED',
2507
2773
  'agent.report': 'REPORT',
2774
+ 'agent.report.missing': 'MISSING REPORT',
2508
2775
  };
2509
2776
  return labels[kind];
2510
2777
  }