@openscout/scout 0.2.68 → 0.2.70

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.
@@ -715,6 +715,59 @@ function renderToolResultContent(content) {
715
715
  }
716
716
  return stringifyUnknown(content);
717
717
  }
718
+ function renderContentPartsText(content) {
719
+ if (typeof content === "string") {
720
+ return content;
721
+ }
722
+ if (!Array.isArray(content)) {
723
+ return "";
724
+ }
725
+ return content.map((entry) => {
726
+ if (typeof entry === "string") {
727
+ return entry;
728
+ }
729
+ if (!isRecord(entry)) {
730
+ return "";
731
+ }
732
+ if (typeof entry.text === "string") {
733
+ return entry.text;
734
+ }
735
+ if (typeof entry.content === "string") {
736
+ return entry.content;
737
+ }
738
+ return "";
739
+ }).filter(Boolean).join(`
740
+ `);
741
+ }
742
+ function extractReasoningText(item) {
743
+ const summary = Array.isArray(item.summary) ? item.summary : [];
744
+ const content = Array.isArray(item.content) ? item.content : [];
745
+ const summaryText = summary.map((entry) => {
746
+ if (typeof entry === "string") {
747
+ return entry;
748
+ }
749
+ const record = entry;
750
+ if (typeof record.text === "string") {
751
+ return record.text;
752
+ }
753
+ if (typeof record.summary === "string") {
754
+ return record.summary;
755
+ }
756
+ return "";
757
+ }).filter(Boolean).join(`
758
+ `);
759
+ const contentText = content.map((entry) => {
760
+ if (typeof entry === "string") {
761
+ return entry;
762
+ }
763
+ const record = entry;
764
+ return typeof record.text === "string" ? record.text : "";
765
+ }).filter(Boolean).join(`
766
+ `);
767
+ return [summaryText, contentText].filter(Boolean).join(`
768
+
769
+ `).trim();
770
+ }
718
771
  function extractQuestionOptions(firstQuestion) {
719
772
  const options = Array.isArray(firstQuestion.options) ? firstQuestion.options : [];
720
773
  return options.map((option) => {
@@ -1424,20 +1477,520 @@ class ClaudeCodeHistoryParser {
1424
1477
  });
1425
1478
  }
1426
1479
  }
1480
+
1481
+ class CodexHistoryParser {
1482
+ session;
1483
+ baseTimestampMs;
1484
+ events = [];
1485
+ currentTurn = null;
1486
+ turnCounter = 0;
1487
+ blockIndex = 0;
1488
+ blockById = new Map;
1489
+ toolBlockMap = new Map;
1490
+ assistantMessageTextThisTurn = new Set;
1491
+ inputTokens = 0;
1492
+ outputTokens = 0;
1493
+ reasoningOutputTokens = 0;
1494
+ cachedInputTokens = 0;
1495
+ tokenEventCount = 0;
1496
+ constructor(session, baseTimestampMs) {
1497
+ this.session = session;
1498
+ this.baseTimestampMs = baseTimestampMs;
1499
+ }
1500
+ parse(content) {
1501
+ const lines = content.split(/\r?\n/u);
1502
+ let parsedLineCount = 0;
1503
+ let skippedLineCount = 0;
1504
+ let lineCount = 0;
1505
+ let lastCapturedAt = this.baseTimestampMs;
1506
+ for (let index = 0;index < lines.length; index += 1) {
1507
+ const rawLine = lines[index];
1508
+ const trimmed = rawLine.trim();
1509
+ if (!trimmed) {
1510
+ continue;
1511
+ }
1512
+ lineCount += 1;
1513
+ let record;
1514
+ try {
1515
+ const parsed = JSON.parse(trimmed);
1516
+ if (!isRecord(parsed)) {
1517
+ skippedLineCount += 1;
1518
+ continue;
1519
+ }
1520
+ record = parsed;
1521
+ } catch {
1522
+ skippedLineCount += 1;
1523
+ continue;
1524
+ }
1525
+ const capturedAt = extractRecordTimestamp(record) ?? this.baseTimestampMs + index;
1526
+ lastCapturedAt = capturedAt;
1527
+ if (this.handleRecord(record, capturedAt)) {
1528
+ parsedLineCount += 1;
1529
+ } else {
1530
+ skippedLineCount += 1;
1531
+ }
1532
+ }
1533
+ if (this.persistUsageMetadata()) {
1534
+ this.emitSessionUpdate(lastCapturedAt);
1535
+ }
1536
+ return {
1537
+ events: this.events,
1538
+ lineCount,
1539
+ parsedLineCount,
1540
+ skippedLineCount
1541
+ };
1542
+ }
1543
+ handleRecord(record, capturedAt) {
1544
+ const type = maybeString(record.type);
1545
+ if (!type) {
1546
+ return false;
1547
+ }
1548
+ switch (type) {
1549
+ case "session_meta":
1550
+ this.handleSessionMeta(record, capturedAt);
1551
+ return true;
1552
+ case "turn_context":
1553
+ this.handleTurnContext(record, capturedAt);
1554
+ return true;
1555
+ case "event_msg":
1556
+ this.handleEventMessage(record, capturedAt);
1557
+ return true;
1558
+ case "response_item":
1559
+ this.handleResponseItem(record, capturedAt);
1560
+ return true;
1561
+ case "compacted":
1562
+ return true;
1563
+ default:
1564
+ return false;
1565
+ }
1566
+ }
1567
+ handleSessionMeta(record, capturedAt) {
1568
+ const payload = isRecord(record.payload) ? record.payload : {};
1569
+ let changed = false;
1570
+ const providerMeta = this.ensureProviderMeta();
1571
+ const externalSessionId = maybeString(payload.id);
1572
+ if (externalSessionId && providerMeta.externalSessionId !== externalSessionId) {
1573
+ providerMeta.externalSessionId = externalSessionId;
1574
+ changed = true;
1575
+ }
1576
+ const cwd = maybeString(payload.cwd);
1577
+ if (cwd && this.session.cwd !== cwd) {
1578
+ this.session.cwd = cwd;
1579
+ if (!this.session.name || /^\d+$/u.test(this.session.name)) {
1580
+ this.session.name = basename(cwd) || "Codex Session";
1581
+ }
1582
+ changed = true;
1583
+ }
1584
+ const git = isRecord(payload.git) ? payload.git : null;
1585
+ const runtime = this.ensureObserveMetaRecord("observeRuntime");
1586
+ changed = this.assignObserveString(runtime, "originator", payload.originator) || changed;
1587
+ changed = this.assignObserveString(runtime, "cliVersion", payload.cli_version) || changed;
1588
+ changed = this.assignObserveString(runtime, "source", payload.source) || changed;
1589
+ changed = this.assignObserveString(runtime, "threadSource", payload.thread_source) || changed;
1590
+ changed = this.assignObserveString(runtime, "modelProvider", payload.model_provider) || changed;
1591
+ changed = this.assignObserveString(runtime, "gitBranch", git?.branch) || changed;
1592
+ changed = this.assignObserveString(runtime, "gitCommitHash", git?.commit_hash) || changed;
1593
+ changed = this.assignObserveString(runtime, "repositoryUrl", git?.repository_url) || changed;
1594
+ if (changed) {
1595
+ this.emitSessionUpdate(capturedAt);
1596
+ }
1597
+ }
1598
+ handleTurnContext(record, capturedAt) {
1599
+ const payload = isRecord(record.payload) ? record.payload : {};
1600
+ let changed = false;
1601
+ const cwd = maybeString(payload.cwd);
1602
+ if (cwd && this.session.cwd !== cwd) {
1603
+ this.session.cwd = cwd;
1604
+ changed = true;
1605
+ }
1606
+ const model = maybeString(payload.model);
1607
+ if (model && this.session.model !== model) {
1608
+ this.session.model = model;
1609
+ changed = true;
1610
+ }
1611
+ const runtime = this.ensureObserveMetaRecord("observeRuntime");
1612
+ changed = this.assignObserveString(runtime, "approvalPolicy", payload.approval_policy) || changed;
1613
+ changed = this.assignObserveString(runtime, "currentDate", payload.current_date) || changed;
1614
+ changed = this.assignObserveString(runtime, "timezone", payload.timezone) || changed;
1615
+ changed = this.assignObserveString(runtime, "effort", payload.effort) || changed;
1616
+ changed = this.assignObserveString(runtime, "personality", payload.personality) || changed;
1617
+ if (isRecord(payload.sandbox_policy) && runtime.sandboxPolicy !== payload.sandbox_policy) {
1618
+ runtime.sandboxPolicy = payload.sandbox_policy;
1619
+ changed = true;
1620
+ }
1621
+ if (changed) {
1622
+ this.emitSessionUpdate(capturedAt);
1623
+ }
1624
+ }
1625
+ handleEventMessage(record, capturedAt) {
1626
+ const payload = isRecord(record.payload) ? record.payload : {};
1627
+ const eventType = maybeString(payload.type);
1628
+ if (!eventType) {
1629
+ return;
1630
+ }
1631
+ switch (eventType) {
1632
+ case "task_started": {
1633
+ const startedAt = normalizeTimestamp(payload.started_at) ?? capturedAt;
1634
+ this.startTurn(startedAt, maybeString(payload.turn_id));
1635
+ break;
1636
+ }
1637
+ case "user_message":
1638
+ this.ensureTurn(capturedAt);
1639
+ break;
1640
+ case "agent_message": {
1641
+ const message = maybeString(payload.message);
1642
+ if (message) {
1643
+ this.appendCompletedText(capturedAt, message);
1644
+ this.assistantMessageTextThisTurn.add(message);
1645
+ }
1646
+ break;
1647
+ }
1648
+ case "patch_apply_end":
1649
+ this.handleToolOutput(capturedAt, maybeString(payload.call_id), [maybeString(payload.stdout), maybeString(payload.stderr)].filter(Boolean).join(`
1650
+ `), payload.success === false);
1651
+ break;
1652
+ case "token_count":
1653
+ this.captureTokenUsage(payload.info);
1654
+ break;
1655
+ case "task_complete":
1656
+ this.endTurn("completed", normalizeTimestamp(payload.completed_at) ?? capturedAt);
1657
+ break;
1658
+ case "context_compacted":
1659
+ case "web_search_end":
1660
+ break;
1661
+ default:
1662
+ break;
1663
+ }
1664
+ }
1665
+ handleResponseItem(record, capturedAt) {
1666
+ const payload = isRecord(record.payload) ? record.payload : {};
1667
+ const itemType = maybeString(payload.type);
1668
+ if (!itemType) {
1669
+ return;
1670
+ }
1671
+ switch (itemType) {
1672
+ case "message":
1673
+ this.handleResponseMessage(payload, capturedAt);
1674
+ break;
1675
+ case "reasoning":
1676
+ this.handleReasoning(payload, capturedAt);
1677
+ break;
1678
+ case "function_call":
1679
+ case "custom_tool_call":
1680
+ this.handleToolCall(payload, capturedAt);
1681
+ break;
1682
+ case "function_call_output":
1683
+ case "custom_tool_call_output":
1684
+ this.handleToolOutput(capturedAt, maybeString(payload.call_id), renderToolResultContent(payload.output), false);
1685
+ break;
1686
+ case "web_search_call":
1687
+ this.handleWebSearchCall(payload, capturedAt);
1688
+ break;
1689
+ default:
1690
+ break;
1691
+ }
1692
+ }
1693
+ handleResponseMessage(payload, capturedAt) {
1694
+ const role = maybeString(payload.role);
1695
+ if (role !== "assistant") {
1696
+ return;
1697
+ }
1698
+ const text = renderContentPartsText(payload.content).trim();
1699
+ if (!text || this.assistantMessageTextThisTurn.has(text)) {
1700
+ return;
1701
+ }
1702
+ this.appendCompletedText(capturedAt, text);
1703
+ this.assistantMessageTextThisTurn.add(text);
1704
+ }
1705
+ handleReasoning(payload, capturedAt) {
1706
+ const text = extractReasoningText(payload);
1707
+ if (!text) {
1708
+ return;
1709
+ }
1710
+ const turn = this.ensureTurn(capturedAt);
1711
+ const block = this.startBlock(turn, capturedAt, {
1712
+ type: "reasoning",
1713
+ text,
1714
+ status: "completed"
1715
+ });
1716
+ this.emitBlockEnd(capturedAt, turn, block, "completed");
1717
+ }
1718
+ handleToolCall(payload, capturedAt) {
1719
+ const turn = this.ensureTurn(capturedAt);
1720
+ const toolName = maybeString(payload.name) ?? "unknown";
1721
+ const toolCallId = maybeString(payload.call_id) ?? `${turn.id}:tool:${this.blockIndex}`;
1722
+ const input = this.parseToolInput(payload.arguments ?? payload.input);
1723
+ const action = this.buildAction(toolName, toolCallId, input);
1724
+ const block = this.startBlock(turn, capturedAt, {
1725
+ id: `${turn.id}:action:${toolCallId}`,
1726
+ type: "action",
1727
+ action,
1728
+ status: "streaming"
1729
+ });
1730
+ this.toolBlockMap.set(toolCallId, block.id);
1731
+ }
1732
+ handleWebSearchCall(payload, capturedAt) {
1733
+ const turn = this.ensureTurn(capturedAt);
1734
+ const toolCallId = `${turn.id}:web-search:${this.blockIndex}`;
1735
+ const action = {
1736
+ kind: "tool_call",
1737
+ toolName: "web_search",
1738
+ toolCallId,
1739
+ input: payload.action,
1740
+ result: payload.status,
1741
+ status: maybeString(payload.status) === "failed" ? "failed" : "completed",
1742
+ output: ""
1743
+ };
1744
+ const block = this.startBlock(turn, capturedAt, {
1745
+ id: `${turn.id}:action:${toolCallId}`,
1746
+ type: "action",
1747
+ action,
1748
+ status: "completed"
1749
+ });
1750
+ this.emitBlockEnd(capturedAt, turn, block, "completed");
1751
+ }
1752
+ parseToolInput(value) {
1753
+ if (typeof value !== "string") {
1754
+ return value;
1755
+ }
1756
+ const trimmed = value.trim();
1757
+ if (!trimmed) {
1758
+ return "";
1759
+ }
1760
+ try {
1761
+ return JSON.parse(trimmed);
1762
+ } catch {
1763
+ return value;
1764
+ }
1765
+ }
1766
+ buildAction(toolName, toolCallId, input) {
1767
+ const inputRecord = isRecord(input) ? input : {};
1768
+ if (toolName === "exec_command") {
1769
+ return {
1770
+ kind: "command",
1771
+ command: maybeString(inputRecord.cmd) ?? "",
1772
+ status: "running",
1773
+ output: ""
1774
+ };
1775
+ }
1776
+ return {
1777
+ kind: "tool_call",
1778
+ toolName,
1779
+ toolCallId,
1780
+ input,
1781
+ status: "running",
1782
+ output: ""
1783
+ };
1784
+ }
1785
+ handleToolOutput(capturedAt, toolCallId, output, isError) {
1786
+ if (!toolCallId) {
1787
+ return;
1788
+ }
1789
+ const blockId = this.toolBlockMap.get(toolCallId);
1790
+ const turn = this.currentTurn;
1791
+ if (!blockId || !turn) {
1792
+ return;
1793
+ }
1794
+ if (output) {
1795
+ this.emitEvent(capturedAt, {
1796
+ event: "block:action:output",
1797
+ sessionId: this.session.id,
1798
+ turnId: turn.id,
1799
+ blockId,
1800
+ output
1801
+ });
1802
+ }
1803
+ const exitCode = this.extractExitCode(output);
1804
+ const status = isError || exitCode != null && exitCode !== 0 ? "failed" : "completed";
1805
+ this.emitEvent(capturedAt, {
1806
+ event: "block:action:status",
1807
+ sessionId: this.session.id,
1808
+ turnId: turn.id,
1809
+ blockId,
1810
+ status,
1811
+ ...exitCode != null ? { meta: { exitCode } } : {}
1812
+ });
1813
+ const block = this.blockById.get(blockId);
1814
+ if (block) {
1815
+ this.emitBlockEnd(capturedAt, turn, block, status === "failed" ? "failed" : "completed");
1816
+ }
1817
+ this.toolBlockMap.delete(toolCallId);
1818
+ }
1819
+ extractExitCode(output) {
1820
+ const match = output.match(/(?:exit code|exited with code):\s*(-?\d+)/iu);
1821
+ if (!match) {
1822
+ return null;
1823
+ }
1824
+ const exitCode = Number(match[1]);
1825
+ return Number.isFinite(exitCode) ? exitCode : null;
1826
+ }
1827
+ captureTokenUsage(info) {
1828
+ if (!isRecord(info)) {
1829
+ return;
1830
+ }
1831
+ const total = isRecord(info.total_token_usage) ? info.total_token_usage : {};
1832
+ this.inputTokens = maybeNumber(total.input_tokens) ?? this.inputTokens;
1833
+ this.outputTokens = maybeNumber(total.output_tokens) ?? this.outputTokens;
1834
+ this.reasoningOutputTokens = maybeNumber(total.reasoning_output_tokens) ?? this.reasoningOutputTokens;
1835
+ this.cachedInputTokens = maybeNumber(total.cached_input_tokens) ?? this.cachedInputTokens;
1836
+ this.tokenEventCount += 1;
1837
+ }
1838
+ persistUsageMetadata() {
1839
+ if (this.tokenEventCount === 0) {
1840
+ return false;
1841
+ }
1842
+ const usage = this.ensureObserveMetaRecord("observeUsage");
1843
+ let changed = false;
1844
+ const assignNumber = (key, value) => {
1845
+ if (value > 0 && usage[key] !== value) {
1846
+ usage[key] = value;
1847
+ changed = true;
1848
+ }
1849
+ };
1850
+ assignNumber("inputTokens", this.inputTokens);
1851
+ assignNumber("outputTokens", this.outputTokens);
1852
+ assignNumber("reasoningOutputTokens", this.reasoningOutputTokens);
1853
+ assignNumber("cacheReadInputTokens", this.cachedInputTokens);
1854
+ assignNumber("tokenEvents", this.tokenEventCount);
1855
+ return changed;
1856
+ }
1857
+ startTurn(capturedAt, turnId) {
1858
+ if (this.currentTurn) {
1859
+ this.endTurn("stopped", capturedAt);
1860
+ }
1861
+ this.turnCounter += 1;
1862
+ this.blockIndex = 0;
1863
+ this.blockById.clear();
1864
+ this.toolBlockMap.clear();
1865
+ this.assistantMessageTextThisTurn.clear();
1866
+ this.session.status = "active";
1867
+ this.emitSessionUpdate(capturedAt);
1868
+ const turn = {
1869
+ id: turnId || `history-turn-${this.turnCounter}`,
1870
+ sessionId: this.session.id,
1871
+ status: "started",
1872
+ startedAt: new Date(capturedAt).toISOString(),
1873
+ blocks: []
1874
+ };
1875
+ this.currentTurn = turn;
1876
+ this.emitEvent(capturedAt, {
1877
+ event: "turn:start",
1878
+ sessionId: this.session.id,
1879
+ turn
1880
+ });
1881
+ return turn;
1882
+ }
1883
+ ensureTurn(capturedAt) {
1884
+ return this.currentTurn ?? this.startTurn(capturedAt);
1885
+ }
1886
+ endTurn(status, capturedAt) {
1887
+ const turn = this.currentTurn;
1888
+ if (!turn) {
1889
+ return;
1890
+ }
1891
+ turn.status = status;
1892
+ turn.endedAt = new Date(capturedAt).toISOString();
1893
+ this.emitEvent(capturedAt, {
1894
+ event: "turn:end",
1895
+ sessionId: this.session.id,
1896
+ turnId: turn.id,
1897
+ status
1898
+ });
1899
+ this.currentTurn = null;
1900
+ this.blockById.clear();
1901
+ this.toolBlockMap.clear();
1902
+ this.assistantMessageTextThisTurn.clear();
1903
+ this.session.status = "idle";
1904
+ this.emitSessionUpdate(capturedAt);
1905
+ }
1906
+ appendCompletedText(capturedAt, text) {
1907
+ const turn = this.ensureTurn(capturedAt);
1908
+ const block = this.startBlock(turn, capturedAt, {
1909
+ type: "text",
1910
+ text,
1911
+ status: "completed"
1912
+ });
1913
+ this.emitBlockEnd(capturedAt, turn, block, "completed");
1914
+ }
1915
+ startBlock(turn, capturedAt, partial) {
1916
+ const block = {
1917
+ ...partial,
1918
+ id: partial.id || `${turn.id}:block:${this.blockIndex}`,
1919
+ turnId: turn.id,
1920
+ index: this.blockIndex
1921
+ };
1922
+ this.blockIndex += 1;
1923
+ turn.blocks.push(block);
1924
+ this.blockById.set(block.id, block);
1925
+ this.emitEvent(capturedAt, {
1926
+ event: "block:start",
1927
+ sessionId: this.session.id,
1928
+ turnId: turn.id,
1929
+ block
1930
+ });
1931
+ return block;
1932
+ }
1933
+ emitBlockEnd(capturedAt, turn, block, status) {
1934
+ block.status = status;
1935
+ this.emitEvent(capturedAt, {
1936
+ event: "block:end",
1937
+ sessionId: this.session.id,
1938
+ turnId: turn.id,
1939
+ blockId: block.id,
1940
+ status
1941
+ });
1942
+ }
1943
+ ensureProviderMeta() {
1944
+ const providerMeta = isRecord(this.session.providerMeta) ? this.session.providerMeta : {};
1945
+ this.session.providerMeta = providerMeta;
1946
+ return providerMeta;
1947
+ }
1948
+ ensureObserveMetaRecord(key) {
1949
+ const providerMeta = this.ensureProviderMeta();
1950
+ const existing = providerMeta[key];
1951
+ if (isRecord(existing)) {
1952
+ return existing;
1953
+ }
1954
+ const next = {};
1955
+ providerMeta[key] = next;
1956
+ return next;
1957
+ }
1958
+ assignObserveString(target, key, value) {
1959
+ const next = maybeString(value);
1960
+ if (!next || target[key] === next) {
1961
+ return false;
1962
+ }
1963
+ target[key] = next;
1964
+ return true;
1965
+ }
1966
+ emitSessionUpdate(capturedAt) {
1967
+ this.emitEvent(capturedAt, {
1968
+ event: "session:update",
1969
+ session: { ...this.session }
1970
+ });
1971
+ }
1972
+ emitEvent(capturedAt, event) {
1973
+ this.events.push({
1974
+ capturedAt,
1975
+ event: structuredClone(event)
1976
+ });
1977
+ }
1978
+ }
1427
1979
  function inferHistorySessionAdapterType(path2, adapterType) {
1428
1980
  return inferHistoryAdapterType(path2, adapterType);
1429
1981
  }
1430
1982
  function supportsHistorySessionSnapshotForPath(path2, adapterType) {
1431
- return inferHistoryAdapterType(path2, adapterType) === "claude-code";
1983
+ const inferred = inferHistoryAdapterType(path2, adapterType);
1984
+ return inferred === "claude-code" || inferred === "codex";
1432
1985
  }
1433
1986
  function createHistorySessionSnapshot(input) {
1434
1987
  const adapterType = inferHistoryAdapterType(input.path, input.adapterType);
1435
- if (adapterType !== "claude-code") {
1988
+ if (adapterType !== "claude-code" && adapterType !== "codex") {
1436
1989
  throw new Error(`History snapshot is not supported for adapter type "${adapterType}".`);
1437
1990
  }
1438
1991
  const session = buildBaseHistorySession(input, adapterType);
1439
1992
  const baseTimestampMs = normalizeTimestamp(input.baseTimestampMs) ?? Date.now();
1440
- const parser = new ClaudeCodeHistoryParser(session, baseTimestampMs);
1993
+ const parser = adapterType === "codex" ? new CodexHistoryParser(session, baseTimestampMs) : new ClaudeCodeHistoryParser(session, baseTimestampMs);
1441
1994
  const replay = parser.parse(input.content);
1442
1995
  const tracker = new StateTracker;
1443
1996
  tracker.createSession(session.id, session);
@@ -3334,7 +3887,7 @@ function stringifyValue(value) {
3334
3887
  return String(value);
3335
3888
  }
3336
3889
  }
3337
- function extractReasoningText(item) {
3890
+ function extractReasoningText2(item) {
3338
3891
  const summary = Array.isArray(item.summary) ? item.summary : [];
3339
3892
  const content = Array.isArray(item.content) ? item.content : [];
3340
3893
  const summaryText = summary.map((entry) => {
@@ -3810,7 +4363,7 @@ var init_codex = __esm(() => {
3810
4363
  this.ensureTextBlock(turnState, itemId, typeof item.text === "string" ? item.text : "");
3811
4364
  return;
3812
4365
  case "reasoning": {
3813
- const text = extractReasoningText(item);
4366
+ const text = extractReasoningText2(item);
3814
4367
  if (text) {
3815
4368
  this.ensureReasoningBlock(turnState, itemId, text);
3816
4369
  }
@@ -3889,7 +4442,7 @@ var init_codex = __esm(() => {
3889
4442
  return;
3890
4443
  }
3891
4444
  case "reasoning": {
3892
- const finalText = extractReasoningText(item);
4445
+ const finalText = extractReasoningText2(item);
3893
4446
  if (!finalText && !turnState.blocksByItemId.has(itemId)) {
3894
4447
  return;
3895
4448
  }
@@ -7813,6 +8366,25 @@ function buildScoutReturnAddress(input) {
7813
8366
  }
7814
8367
  return next;
7815
8368
  }
8369
+ // ../protocol/src/channel-identity.ts
8370
+ function mintChannelId(randomUuid) {
8371
+ return `${CHANNEL_ID_PREFIX}${randomUuid().toLowerCase()}`;
8372
+ }
8373
+ function directChannelNaturalKey(participantIds) {
8374
+ return `direct:${stableIdentityParts(participantIds).join(",")}`;
8375
+ }
8376
+ function channelNaturalKeyFromMetadata(metadata) {
8377
+ const value = metadata?.[CHANNEL_NATURAL_KEY_METADATA];
8378
+ return typeof value === "string" && value.trim() ? value.trim() : null;
8379
+ }
8380
+ function stableIdentityParts(values) {
8381
+ return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean))).sort().map(encodeIdentityPart);
8382
+ }
8383
+ function encodeIdentityPart(value) {
8384
+ return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
8385
+ }
8386
+ var CHANNEL_ID_PREFIX = "c.", CHANNEL_NATURAL_KEY_METADATA = "naturalKey";
8387
+
7816
8388
  // ../protocol/src/collaboration.ts
7817
8389
  function isQuestionTerminalState(state) {
7818
8390
  return state === "closed" || state === "declined";
@@ -7996,6 +8568,17 @@ function assertValidUnblockRequestEvent(event, record) {
7996
8568
  throw new Error(errors.join("; "));
7997
8569
  }
7998
8570
  }
8571
+ // ../protocol/src/lifecycle.ts
8572
+ var TERMINAL_INVOCATION_STATES;
8573
+ var init_lifecycle = __esm(() => {
8574
+ TERMINAL_INVOCATION_STATES = new Set([
8575
+ "completed",
8576
+ "failed",
8577
+ "cancelled",
8578
+ "expired"
8579
+ ]);
8580
+ });
8581
+
7999
8582
  // ../protocol/src/permission-policy.ts
8000
8583
  function normalizeScoutPermissionProfile(value) {
8001
8584
  const normalized = value?.trim().replaceAll("-", "_");
@@ -8048,15 +8631,20 @@ var init_scout_composer = __esm(() => {
8048
8631
  });
8049
8632
  // ../protocol/src/inbox.ts
8050
8633
  var init_inbox = () => {};
8634
+ // ../protocol/src/cursor-transport.ts
8635
+ var init_cursor_transport = () => {};
8636
+
8051
8637
  // ../protocol/src/index.ts
8052
8638
  var init_src2 = __esm(() => {
8053
8639
  init_agent_identity();
8054
8640
  init_agent_address();
8055
8641
  init_agent_selectors();
8642
+ init_lifecycle();
8056
8643
  init_agent_runs();
8057
8644
  init_scout_composer();
8058
8645
  init_inbox();
8059
8646
  init_permission_policy();
8647
+ init_cursor_transport();
8060
8648
  });
8061
8649
 
8062
8650
  // ../runtime/src/user-project-hints.ts
@@ -10231,6 +10819,9 @@ var init_setup = __esm(() => {
10231
10819
  };
10232
10820
  });
10233
10821
 
10822
+ // ../runtime/src/dispatch-stalled.ts
10823
+ var init_dispatch_stalled = () => {};
10824
+
10234
10825
  // ../runtime/src/managed-agent-environment.ts
10235
10826
  function managedAgentEnvironmentEntries(options) {
10236
10827
  const agentName = options.agentName.trim();
@@ -11023,6 +11614,15 @@ function readCodexAppServerReasoningEffortFromLaunchArgs(launchArgs) {
11023
11614
  }
11024
11615
  return null;
11025
11616
  }
11617
+ function codexAppServerExitMessage(input) {
11618
+ if (input.exitKind === "proactive_shutdown") {
11619
+ return `Codex app-server session for ${input.agentName} was stopped by OpenScout` + (input.reason ? `: ${input.reason}` : "") + ".";
11620
+ }
11621
+ if (input.exitKind === "external_sigterm") {
11622
+ return `Codex app-server for ${input.agentName} was interrupted by SIGTERM.`;
11623
+ }
11624
+ return `Codex app-server exited for ${input.agentName}` + (input.exitCode !== null ? ` with code ${input.exitCode}` : "") + (input.signal ? ` (${input.signal})` : "");
11625
+ }
11026
11626
  function resolveRequesterTimeoutMs2(timeoutMs) {
11027
11627
  if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
11028
11628
  return timeoutMs;
@@ -11194,6 +11794,7 @@ class CodexAppServerSession {
11194
11794
  activeTurn = null;
11195
11795
  threadId = null;
11196
11796
  threadPath = null;
11797
+ proactiveShutdown = null;
11197
11798
  lastConfigSignature;
11198
11799
  constructor(options) {
11199
11800
  this.options = options;
@@ -11324,13 +11925,23 @@ class CodexAppServerSession {
11324
11925
  });
11325
11926
  }
11326
11927
  async shutdown(options = {}) {
11928
+ this.proactiveShutdown = {
11929
+ reason: options.reason ?? (options.resetThread ? "OpenScout reset the app-server session" : "OpenScout stopped the app-server session")
11930
+ };
11931
+ const stoppedError = new CodexAppServerExitError({
11932
+ agentName: this.options.agentName,
11933
+ exitKind: "proactive_shutdown",
11934
+ exitCode: null,
11935
+ signal: null,
11936
+ reason: this.proactiveShutdown.reason
11937
+ });
11327
11938
  const activeTurn = this.activeTurn;
11328
11939
  this.activeTurn = null;
11329
11940
  if (activeTurn) {
11330
- activeTurn.reject(new Error(`Codex app-server session for ${this.options.agentName} was shut down.`));
11941
+ activeTurn.reject(stoppedError);
11331
11942
  }
11332
11943
  for (const pending of this.pendingRequests.values()) {
11333
- pending.reject(new Error(`Codex app-server session for ${this.options.agentName} was shut down.`));
11944
+ pending.reject(stoppedError);
11334
11945
  }
11335
11946
  this.pendingRequests.clear();
11336
11947
  const child = this.process;
@@ -11340,7 +11951,7 @@ class CodexAppServerSession {
11340
11951
  if (child && child.exitCode === null && !child.killed) {
11341
11952
  child.kill("SIGTERM");
11342
11953
  await new Promise((resolve5) => setTimeout(resolve5, 250));
11343
- if (child.exitCode === null && !child.killed) {
11954
+ if (child.exitCode === null && child.signalCode === null) {
11344
11955
  child.kill("SIGKILL");
11345
11956
  }
11346
11957
  }
@@ -11391,6 +12002,7 @@ class CodexAppServerSession {
11391
12002
  await mkdir5(this.options.runtimeDirectory, { recursive: true });
11392
12003
  await mkdir5(this.options.logsDirectory, { recursive: true });
11393
12004
  await writeFile5(join17(this.options.runtimeDirectory, "prompt.txt"), this.options.systemPrompt);
12005
+ this.proactiveShutdown = null;
11394
12006
  const codexExecutable = resolveCodexExecutable();
11395
12007
  const launchArgs = normalizeCodexAppServerLaunchArgs(this.options.launchArgs);
11396
12008
  const env = buildManagedAgentEnvironment({
@@ -11430,7 +12042,16 @@ class CodexAppServerSession {
11430
12042
  this.failSession(new Error(`Codex app-server failed for ${this.options.agentName}: ${errorMessage3(error)}`));
11431
12043
  });
11432
12044
  child.once("exit", (code, signal) => {
11433
- this.failSession(new Error(`Codex app-server exited for ${this.options.agentName}` + (code !== null ? ` with code ${code}` : "") + (signal ? ` (${signal})` : "")));
12045
+ const proactiveShutdown = this.proactiveShutdown;
12046
+ if (proactiveShutdown) {
12047
+ this.handleProactiveProcessExit(child, proactiveShutdown, code, signal);
12048
+ return;
12049
+ }
12050
+ this.failSession(new CodexAppServerExitError({
12051
+ agentName: this.options.agentName,
12052
+ exitCode: code,
12053
+ signal
12054
+ }));
11434
12055
  });
11435
12056
  await this.request("initialize", {
11436
12057
  clientInfo: {
@@ -11765,6 +12386,23 @@ class CodexAppServerSession {
11765
12386
  }
11766
12387
  this.pendingRequests.clear();
11767
12388
  appendFile3(this.stderrLogPath, `[openscout] ${error.message}
12389
+ `).catch(() => {
12390
+ return;
12391
+ });
12392
+ this.persistState();
12393
+ }
12394
+ handleProactiveProcessExit(child, shutdown, code, signal) {
12395
+ if (this.process === child) {
12396
+ this.process = null;
12397
+ }
12398
+ this.proactiveShutdown = null;
12399
+ this.starting = null;
12400
+ const exitDetail = [
12401
+ code !== null ? `code ${code}` : null,
12402
+ signal
12403
+ ].filter(Boolean).join(", ");
12404
+ const detail = exitDetail ? ` (${exitDetail})` : "";
12405
+ appendFile3(this.stderrLogPath, `[openscout] Codex app-server stopped for ${this.options.agentName}: ${shutdown.reason}${detail}
11768
12406
  `).catch(() => {
11769
12407
  return;
11770
12408
  });
@@ -11835,7 +12473,10 @@ function getOrCreateSession2(options) {
11835
12473
  if (existing.matches(options)) {
11836
12474
  return existing;
11837
12475
  }
11838
- existing.shutdown({ resetThread: true });
12476
+ existing.shutdown({
12477
+ resetThread: true,
12478
+ reason: "OpenScout replaced the app-server session after its launch options changed"
12479
+ });
11839
12480
  sessions2.delete(key);
11840
12481
  }
11841
12482
  const session = new CodexAppServerSession(options);
@@ -11866,13 +12507,36 @@ async function shutdownCodexAppServerAgent(options, shutdownOptions = {}) {
11866
12507
  sessions2.delete(key);
11867
12508
  await session.shutdown(shutdownOptions);
11868
12509
  }
11869
- var SESSION_CATALOG_FILENAME2 = "session-catalog.json", SESSION_CATALOG_MAX_ENTRIES2 = 64, CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS, sessions2;
12510
+ var CodexAppServerExitError, SESSION_CATALOG_FILENAME2 = "session-catalog.json", SESSION_CATALOG_MAX_ENTRIES2 = 64, CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS, sessions2;
11870
12511
  var init_codex_app_server = __esm(() => {
11871
12512
  init_src();
11872
12513
  init_topology();
11873
12514
  init_codex_executable();
11874
12515
  init_requester_timeout();
11875
12516
  init_codex_executable();
12517
+ CodexAppServerExitError = class CodexAppServerExitError extends Error {
12518
+ code = "CODEX_APP_SERVER_EXIT";
12519
+ exitKind;
12520
+ agentName;
12521
+ exitCode;
12522
+ signal;
12523
+ reason;
12524
+ noteworthy;
12525
+ constructor(input) {
12526
+ const exitKind = input.exitKind ?? (input.signal === "SIGTERM" ? "external_sigterm" : "unexpected_exit");
12527
+ super(codexAppServerExitMessage({
12528
+ ...input,
12529
+ exitKind
12530
+ }));
12531
+ this.name = "CodexAppServerExitError";
12532
+ this.exitKind = exitKind;
12533
+ this.agentName = input.agentName;
12534
+ this.exitCode = input.exitCode;
12535
+ this.signal = input.signal;
12536
+ this.reason = input.reason ?? null;
12537
+ this.noteworthy = exitKind !== "unexpected_exit";
12538
+ }
12539
+ };
11876
12540
  CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS = 10 * 60 * 1000;
11877
12541
  sessions2 = new Map;
11878
12542
  });
@@ -12499,6 +13163,17 @@ async function fetchHealthSnapshot(config) {
12499
13163
  nodes: payload.counts.nodes ?? 0,
12500
13164
  actors: payload.counts.actors ?? 0,
12501
13165
  agents: payload.counts.agents ?? 0,
13166
+ agentRecords: payload.counts.agentRecords,
13167
+ rawAgentRecords: payload.counts.rawAgentRecords,
13168
+ configuredAgents: payload.counts.configuredAgents,
13169
+ scoutManagedAgents: payload.counts.scoutManagedAgents,
13170
+ currentAgentRegistrations: payload.counts.currentAgentRegistrations,
13171
+ localAgentRegistrations: payload.counts.localAgentRegistrations,
13172
+ remoteAgentRegistrations: payload.counts.remoteAgentRegistrations,
13173
+ staleAgentRegistrations: payload.counts.staleAgentRegistrations,
13174
+ retiredAgentRegistrations: payload.counts.retiredAgentRegistrations,
13175
+ oneTimeAgentCards: payload.counts.oneTimeAgentCards,
13176
+ persistentAgentCards: payload.counts.persistentAgentCards,
12502
13177
  conversations: payload.counts.conversations ?? 0,
12503
13178
  messages: payload.counts.messages ?? 0,
12504
13179
  flights: payload.counts.flights ?? 0,
@@ -12736,11 +13411,37 @@ var init_permission_policy2 = __esm(() => {
12736
13411
  init_src2();
12737
13412
  });
12738
13413
 
13414
+ // ../runtime/src/user-config.ts
13415
+ import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
13416
+ import { homedir as homedir18 } from "os";
13417
+ import { dirname as dirname9, join as join19 } from "path";
13418
+ function userConfigPath() {
13419
+ return join19(process.env.OPENSCOUT_HOME ?? join19(homedir18(), ".openscout"), "user.json");
13420
+ }
13421
+ function loadUserConfig() {
13422
+ const configPath = userConfigPath();
13423
+ if (!existsSync16(configPath))
13424
+ return {};
13425
+ try {
13426
+ return JSON.parse(readFileSync10(configPath, "utf8"));
13427
+ } catch {
13428
+ return {};
13429
+ }
13430
+ }
13431
+ function normalizeHandle(value) {
13432
+ return value?.trim().replace(/^@+/, "") ?? "";
13433
+ }
13434
+ function resolveOperatorHandle() {
13435
+ const config = loadUserConfig();
13436
+ return normalizeHandle(config.handle) || normalizeHandle(process.env.OPENSCOUT_OPERATOR_HANDLE) || normalizeHandle(config.name) || normalizeHandle(process.env.OPENSCOUT_OPERATOR_NAME) || normalizeHandle(process.env.USER) || "operator";
13437
+ }
13438
+ var init_user_config = () => {};
13439
+
12739
13440
  // ../runtime/src/local-agents.ts
12740
13441
  import { execFileSync as execFileSync4, execSync as execSync2 } from "child_process";
12741
- import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
13442
+ import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
12742
13443
  import { mkdir as mkdir6, rm as rm5, stat as stat3, writeFile as writeFile6 } from "fs/promises";
12743
- import { basename as basename8, dirname as dirname9, join as join19, resolve as resolve6 } from "path";
13444
+ import { basename as basename8, dirname as dirname10, join as join20, resolve as resolve6 } from "path";
12744
13445
  import { fileURLToPath as fileURLToPath5 } from "url";
12745
13446
  function resolveRelayHub() {
12746
13447
  return resolveOpenScoutSupportPaths().relayHubDirectory;
@@ -12751,14 +13452,14 @@ function resolveProjectsRoot(projectPath) {
12751
13452
  }
12752
13453
  try {
12753
13454
  const supportPaths = resolveOpenScoutSupportPaths();
12754
- if (!existsSync16(supportPaths.settingsPath)) {
12755
- return dirname9(projectPath);
13455
+ if (!existsSync17(supportPaths.settingsPath)) {
13456
+ return dirname10(projectPath);
12756
13457
  }
12757
- const raw = JSON.parse(readFileSync10(supportPaths.settingsPath, "utf8"));
13458
+ const raw = JSON.parse(readFileSync11(supportPaths.settingsPath, "utf8"));
12758
13459
  const workspaceRoot = raw.discovery?.workspaceRoots?.find((entry) => typeof entry === "string" && entry.trim().length > 0);
12759
- return workspaceRoot ? resolve6(workspaceRoot) : dirname9(projectPath);
13460
+ return workspaceRoot ? resolve6(workspaceRoot) : dirname10(projectPath);
12760
13461
  } catch {
12761
- return dirname9(projectPath);
13462
+ return dirname10(projectPath);
12762
13463
  }
12763
13464
  }
12764
13465
  function resolveBrokerUrl() {
@@ -12768,7 +13469,7 @@ function nowSeconds2() {
12768
13469
  return Math.floor(Date.now() / 1000);
12769
13470
  }
12770
13471
  function scoutCliPath() {
12771
- return join19(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs");
13472
+ return join20(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs");
12772
13473
  }
12773
13474
  function legacyNodeBrokerRelayCommand() {
12774
13475
  return `node ${JSON.stringify(scoutCliPath())}`;
@@ -12781,13 +13482,13 @@ function titleCaseLocalAgentName(value) {
12781
13482
  }
12782
13483
  function resolveScoutSkillPath() {
12783
13484
  const candidatePaths = [
12784
- join19(OPENSCOUT_REPO_ROOT, ".agents", "skills", "scout", "SKILL.md"),
12785
- join19(process.env.HOME ?? "", ".agents", "skills", "scout", "SKILL.md"),
12786
- join19(OPENSCOUT_REPO_ROOT, ".agents", "skills", "relay-agent-comms", "SKILL.md"),
12787
- join19(process.env.HOME ?? "", ".agents", "skills", "relay-agent-comms", "SKILL.md")
13485
+ join20(OPENSCOUT_REPO_ROOT, ".agents", "skills", "scout", "SKILL.md"),
13486
+ join20(process.env.HOME ?? "", ".agents", "skills", "scout", "SKILL.md"),
13487
+ join20(OPENSCOUT_REPO_ROOT, ".agents", "skills", "relay-agent-comms", "SKILL.md"),
13488
+ join20(process.env.HOME ?? "", ".agents", "skills", "relay-agent-comms", "SKILL.md")
12788
13489
  ];
12789
13490
  for (const path2 of candidatePaths) {
12790
- if (existsSync16(path2)) {
13491
+ if (existsSync17(path2)) {
12791
13492
  return path2;
12792
13493
  }
12793
13494
  }
@@ -12895,6 +13596,74 @@ function buildLocalAgentSystemPromptTemplate() {
12895
13596
  ].join(`
12896
13597
  `);
12897
13598
  }
13599
+ function normalizeOperatorHandleSegment(value) {
13600
+ return normalizeAgentSelectorSegment(value?.trim().replace(/^@+/, "") ?? "") || "operator";
13601
+ }
13602
+ function resolveOperatorAugmentAgentName() {
13603
+ return `${normalizeOperatorHandleSegment(resolveOperatorHandle())}-ai`;
13604
+ }
13605
+ function operatorAugmentAgentNameCandidates() {
13606
+ return new Set([
13607
+ resolveOperatorAugmentAgentName(),
13608
+ "operator-ai"
13609
+ ]);
13610
+ }
13611
+ function buildOperatorAugmentSystemPromptTemplate(input = {}) {
13612
+ const operatorHandle = normalizeOperatorHandleSegment(input.operatorHandle ?? resolveOperatorHandle());
13613
+ const augmentHandle = normalizeOperatorHandleSegment(input.augmentHandle ?? `${operatorHandle}-ai`);
13614
+ const humanLabel = `@${operatorHandle}`;
13615
+ const augmentLabel = `@${augmentHandle}`;
13616
+ return [
13617
+ "{{base_prompt}}",
13618
+ "",
13619
+ `You are the augmented counterpart to the human Scout operator ${humanLabel}.`,
13620
+ `${humanLabel} is the human. ${augmentLabel} is the AI-augmented looper with a human in the loop.`,
13621
+ `Do not impersonate ${humanLabel}. Speak and act as ${augmentLabel}, and involve ${humanLabel} only when human judgment or approval is the real next dependency.`,
13622
+ "",
13623
+ "Operating loop:",
13624
+ " - Keep long-running conversations in the same DM or invocation thread whenever possible.",
13625
+ " - Maintain continuity across turns: track the goal, decisions made, open questions, blockers, and promised follow-ups.",
13626
+ " - For efforts that span many turns, many files, or more than one working session, create or update a durable note/checkpoint when you have write access, then point to it briefly.",
13627
+ " - Prefer continuing from a concise recap over resetting context. If context is aging, summarize the useful state and keep moving.",
13628
+ ` - Be explicit about the next responsible owner: ${augmentLabel}, ${humanLabel}, or another named agent.`,
13629
+ "",
13630
+ `When to invoke ${humanLabel}:`,
13631
+ " - Approval is needed for destructive, irreversible, public, financial, security-sensitive, credential, privacy, or cross-project priority decisions.",
13632
+ ` - The task depends on taste, intent, product direction, personal context, or a choice only ${humanLabel} can make.`,
13633
+ " - You are blocked after a reasonable local attempt and the unblock request can be stated as a short concrete question.",
13634
+ " - A result is materially uncertain and acting without human input could waste meaningful time or create cleanup work.",
13635
+ " - You need permission to interrupt, wake, or redirect other people or agents outside the current work venue.",
13636
+ "",
13637
+ `How to invoke ${humanLabel}:`,
13638
+ " - Keep the ask concise: context, the decision needed, the default you recommend, and the consequence of no answer.",
13639
+ " - Use the same DM/thread when it exists. Do not broadcast human-loop requests.",
13640
+ ` - If a reply is required before work can proceed, say that ${humanLabel} owns the next move and stop claiming progress.`,
13641
+ " - If work can continue safely, state the assumption and continue without waiting.",
13642
+ "",
13643
+ `Do not invoke ${humanLabel} for:`,
13644
+ " - Routine status updates, obvious implementation details, command outputs, local orientation, or reversible low-risk cleanup.",
13645
+ " - Ambiguity that you can resolve by reading the repo, checking Scout state, or asking the correct specialist agent.",
13646
+ "",
13647
+ "{{project_context}}",
13648
+ "",
13649
+ "{{collaboration_prompt}}",
13650
+ "",
13651
+ "{{protocol_prompt}}"
13652
+ ].join(`
13653
+ `);
13654
+ }
13655
+ function operatorAugmentDefaultsForDefinitionId(definitionId) {
13656
+ const normalizedDefinitionId = normalizeAgentSelectorSegment(definitionId);
13657
+ if (!normalizedDefinitionId || !operatorAugmentAgentNameCandidates().has(normalizedDefinitionId)) {
13658
+ return null;
13659
+ }
13660
+ return {
13661
+ displayName: titleCaseLocalAgentName(normalizedDefinitionId),
13662
+ systemPrompt: buildOperatorAugmentSystemPromptTemplate({
13663
+ augmentHandle: normalizedDefinitionId
13664
+ })
13665
+ };
13666
+ }
12898
13667
  function renderLocalAgentSystemPromptTemplate(template, context, options = {}) {
12899
13668
  const basePrompt = buildLocalAgentBasePrompt(context);
12900
13669
  const projectContext = buildLocalAgentProjectContextPrompt(context);
@@ -13049,13 +13818,20 @@ function expandHomePath4(value) {
13049
13818
  return process.env.HOME ?? process.cwd();
13050
13819
  }
13051
13820
  if (value.startsWith("~/")) {
13052
- return join19(process.env.HOME ?? process.cwd(), value.slice(2));
13821
+ return join20(process.env.HOME ?? process.cwd(), value.slice(2));
13053
13822
  }
13054
13823
  return value;
13055
13824
  }
13056
13825
  function normalizeProjectPath(value) {
13057
13826
  return resolve6(expandHomePath4(value.trim() || "."));
13058
13827
  }
13828
+ function compactHomePath(value) {
13829
+ const home = process.env.HOME;
13830
+ if (!home) {
13831
+ return value;
13832
+ }
13833
+ return value.startsWith(home) ? value.replace(home, "~") : value;
13834
+ }
13059
13835
  function normalizeTmuxSessionName2(value, agentId) {
13060
13836
  const fallback = `relay-${agentId}`;
13061
13837
  const trimmed = value?.trim() ?? "";
@@ -13309,6 +14085,21 @@ function applyRequestedRuntimeOptionsToLaunchArgs(harness, launchArgs, options)
13309
14085
  ...buildLaunchArgsForRequestedReasoningEffort(harness, requestedReasoningEffort)
13310
14086
  ];
13311
14087
  }
14088
+ function setLaunchModelForHarness(harness, launchArgs, model) {
14089
+ const normalized = normalizeLaunchArgsForHarness(harness, launchArgs);
14090
+ if (model === undefined) {
14091
+ return normalized;
14092
+ }
14093
+ const stripped = stripLaunchModelForHarness(harness, normalized);
14094
+ const requestedModel = normalizeRequestedModel(model ?? undefined);
14095
+ if (!requestedModel) {
14096
+ return stripped;
14097
+ }
14098
+ return [
14099
+ ...stripped,
14100
+ ...buildLaunchArgsForRequestedModel(harness, requestedModel)
14101
+ ];
14102
+ }
13312
14103
  function defaultHarnessForOverride(override, fallback = "claude") {
13313
14104
  return normalizeManagedHarness2(override.defaultHarness ?? override.runtime?.harness, fallback);
13314
14105
  }
@@ -13429,6 +14220,31 @@ function recordForHarness(record, harnessOverride) {
13429
14220
  permissionProfile: profile.permissionProfile
13430
14221
  };
13431
14222
  }
14223
+ function normalizeCardTimestamp(value) {
14224
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined;
14225
+ }
14226
+ function normalizeLocalAgentCardLifecycle(value, now = Date.now()) {
14227
+ if (!value) {
14228
+ return;
14229
+ }
14230
+ const kind = value.kind === "one_time" ? "one_time" : value.kind === "persistent" ? "persistent" : undefined;
14231
+ if (!kind) {
14232
+ return;
14233
+ }
14234
+ const createdAt = normalizeCardTimestamp(value.createdAt) ?? now;
14235
+ const expiresAt = normalizeCardTimestamp(value.expiresAt);
14236
+ const maxUses = typeof value.maxUses === "number" && Number.isFinite(value.maxUses) && value.maxUses > 0 ? Math.floor(value.maxUses) : undefined;
14237
+ const createdById = value.createdById?.trim();
14238
+ const inboxConversationId = value.inboxConversationId?.trim();
14239
+ return {
14240
+ kind,
14241
+ createdAt,
14242
+ ...createdById ? { createdById } : {},
14243
+ ...expiresAt ? { expiresAt } : {},
14244
+ ...maxUses ? { maxUses } : {},
14245
+ ...inboxConversationId ? { inboxConversationId } : {}
14246
+ };
14247
+ }
13432
14248
  function normalizeLocalAgentRecord(agentId, record) {
13433
14249
  const cwd = normalizeProjectPath(record.cwd || process.cwd());
13434
14250
  const projectRoot = normalizeProjectPath(record.projectRoot || cwd);
@@ -13458,7 +14274,8 @@ function normalizeLocalAgentRecord(agentId, record) {
13458
14274
  capabilities: normalizeLocalAgentCapabilities(record.capabilities),
13459
14275
  launchArgs: activeProfile?.launchArgs ?? normalizeLaunchArgsForHarness(harness, record.launchArgs),
13460
14276
  permissionProfile: activeProfile?.permissionProfile ?? record.permissionProfile,
13461
- registrationSource: record.registrationSource
14277
+ registrationSource: record.registrationSource,
14278
+ card: normalizeLocalAgentCardLifecycle(record.card)
13462
14279
  };
13463
14280
  }
13464
14281
  function localAgentStatusFromRecord(agentId, record, source) {
@@ -13512,7 +14329,8 @@ function localAgentRecordFromRelayAgentOverride(agentId, override) {
13512
14329
  harnessProfiles: override.harnessProfiles,
13513
14330
  transport: normalizeLocalAgentTransport(override.runtime?.transport, normalizeLocalAgentHarness(override.runtime?.harness)),
13514
14331
  capabilities: override.capabilities,
13515
- launchArgs: override.launchArgs
14332
+ launchArgs: override.launchArgs,
14333
+ card: override.card
13516
14334
  });
13517
14335
  }
13518
14336
  function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
@@ -13533,6 +14351,7 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
13533
14351
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
13534
14352
  defaultHarness: normalizeManagedHarness2(normalizedRecord.defaultHarness, "claude"),
13535
14353
  harnessProfiles: normalizedRecord.harnessProfiles,
14354
+ ...normalizedRecord.card ? { card: normalizedRecord.card } : {},
13536
14355
  runtime: {
13537
14356
  cwd: normalizeProjectPath(normalizedRecord.cwd),
13538
14357
  harness: normalizeLocalAgentHarness(normalizedRecord.harness),
@@ -13542,6 +14361,245 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
13542
14361
  }
13543
14362
  };
13544
14363
  }
14364
+ function buildLocalAgentConfigState(agentId, record) {
14365
+ const harness = normalizeLocalAgentHarness(record.harness);
14366
+ const launchArgs = normalizeLaunchArgsForHarness(harness, record.launchArgs);
14367
+ return {
14368
+ agentId,
14369
+ editable: true,
14370
+ model: readLaunchModelForHarness(harness, launchArgs) ?? null,
14371
+ permissionProfile: record.permissionProfile ?? null,
14372
+ systemPrompt: record.systemPrompt || buildLocalAgentSystemPromptTemplate(),
14373
+ runtime: {
14374
+ cwd: compactHomePath(record.cwd),
14375
+ harness,
14376
+ transport: normalizeLocalAgentTransport(record.transport, harness),
14377
+ sessionId: record.tmuxSession,
14378
+ wakePolicy: "on_demand"
14379
+ },
14380
+ launchArgs,
14381
+ capabilities: normalizeLocalAgentCapabilities(record.capabilities),
14382
+ applyMode: "restart",
14383
+ templateHint: LOCAL_AGENT_SYSTEM_PROMPT_TEMPLATE_HINT
14384
+ };
14385
+ }
14386
+ async function assertDirectoryExists(directory) {
14387
+ const stats = await stat3(directory);
14388
+ if (!stats.isDirectory()) {
14389
+ throw new Error(`${directory} is not a directory.`);
14390
+ }
14391
+ }
14392
+ async function resolveConfiguredLocalAgentRecord(agentId) {
14393
+ const overrides = await readRelayAgentOverrides();
14394
+ const override = overrides[agentId];
14395
+ if (override) {
14396
+ return localAgentRecordFromRelayAgentOverride(agentId, override);
14397
+ }
14398
+ const registry = await readLocalAgentRegistry();
14399
+ const record = registry[agentId];
14400
+ return record ? normalizeLocalAgentRecord(agentId, record) : null;
14401
+ }
14402
+ async function getLocalAgentConfig(agentId) {
14403
+ const record = await resolveConfiguredLocalAgentRecord(agentId);
14404
+ if (!record) {
14405
+ return null;
14406
+ }
14407
+ return buildLocalAgentConfigState(agentId, record);
14408
+ }
14409
+ function defaultLocalAgentSessionId(agentId, harness) {
14410
+ return normalizeTmuxSessionName2(undefined, `${agentId}-${harness}`);
14411
+ }
14412
+ async function updateLocalAgentCard(agentId, input) {
14413
+ const existing = await getLocalAgentConfig(agentId);
14414
+ if (!existing) {
14415
+ return null;
14416
+ }
14417
+ const nextHarness = input.harness ? normalizeLocalAgentHarness(input.harness) : existing.runtime.harness;
14418
+ const harnessChanged = nextHarness !== existing.runtime.harness;
14419
+ const model = input.model === undefined ? harnessChanged ? null : undefined : input.model;
14420
+ return updateLocalAgentConfig(agentId, {
14421
+ runtime: {
14422
+ cwd: existing.runtime.cwd,
14423
+ harness: nextHarness,
14424
+ transport: harnessChanged ? normalizeLocalAgentTransport(undefined, nextHarness) : existing.runtime.transport,
14425
+ sessionId: harnessChanged ? defaultLocalAgentSessionId(agentId, nextHarness) : existing.runtime.sessionId
14426
+ },
14427
+ systemPrompt: existing.systemPrompt,
14428
+ launchArgs: harnessChanged ? [] : existing.launchArgs,
14429
+ model,
14430
+ reasoningEffort: input.reasoningEffort,
14431
+ permissionProfile: input.permissionProfile,
14432
+ capabilities: existing.capabilities
14433
+ });
14434
+ }
14435
+ async function updateLocalAgentConfig(agentId, input) {
14436
+ const [registry, overrides] = await Promise.all([
14437
+ readLocalAgentRegistry(),
14438
+ readRelayAgentOverrides()
14439
+ ]);
14440
+ const record = registry[agentId] ?? (overrides[agentId] ? localAgentRecordFromRelayAgentOverride(agentId, overrides[agentId]) : undefined);
14441
+ if (!record) {
14442
+ return null;
14443
+ }
14444
+ const cwd = normalizeProjectPath(input.runtime.cwd || record.cwd);
14445
+ await assertDirectoryExists(cwd);
14446
+ const nextHarness = normalizeLocalAgentHarness(input.runtime.harness);
14447
+ const nextTransport = normalizeLocalAgentTransport(input.runtime.transport ?? record.transport, nextHarness);
14448
+ let nextLaunchArgs = setLaunchModelForHarness(nextHarness, input.launchArgs, input.model);
14449
+ if (input.reasoningEffort !== undefined) {
14450
+ const stripped = stripLaunchReasoningEffortForHarness(nextHarness, nextLaunchArgs);
14451
+ const requestedReasoningEffort = normalizeRequestedReasoningEffort(input.reasoningEffort ?? undefined);
14452
+ nextLaunchArgs = requestedReasoningEffort ? [
14453
+ ...stripped,
14454
+ ...buildLaunchArgsForRequestedReasoningEffort(nextHarness, requestedReasoningEffort)
14455
+ ] : stripped;
14456
+ }
14457
+ const nextPermissionProfile = input.permissionProfile === undefined ? record.permissionProfile : input.permissionProfile === null ? undefined : parseScoutPermissionProfile(input.permissionProfile);
14458
+ const nextRecord = normalizeLocalAgentRecord(agentId, {
14459
+ ...record,
14460
+ cwd,
14461
+ tmuxSession: input.runtime.sessionId,
14462
+ harness: nextHarness,
14463
+ defaultHarness: nextHarness,
14464
+ harnessProfiles: {
14465
+ ...record.harnessProfiles ?? {},
14466
+ [normalizeManagedHarness2(input.runtime.harness, "claude")]: {
14467
+ cwd,
14468
+ transport: nextTransport,
14469
+ sessionId: normalizeTmuxSessionName2(input.runtime.sessionId, `${agentId}-${normalizeManagedHarness2(input.runtime.harness, "claude")}`),
14470
+ launchArgs: nextLaunchArgs,
14471
+ ...nextPermissionProfile ? { permissionProfile: nextPermissionProfile } : {}
14472
+ }
14473
+ },
14474
+ transport: nextTransport,
14475
+ systemPrompt: input.systemPrompt.trim() || undefined,
14476
+ launchArgs: nextLaunchArgs,
14477
+ permissionProfile: nextPermissionProfile,
14478
+ capabilities: input.capabilities
14479
+ });
14480
+ registry[agentId] = nextRecord;
14481
+ await writeLocalAgentRegistry(registry);
14482
+ return buildLocalAgentConfigState(agentId, nextRecord);
14483
+ }
14484
+ async function updateLocalAgentCardLifecycle(agentId, input) {
14485
+ const overrides = await readRelayAgentOverrides();
14486
+ const override = overrides[agentId];
14487
+ if (!override) {
14488
+ return null;
14489
+ }
14490
+ const lifecycle2 = normalizeLocalAgentCardLifecycle({
14491
+ ...override.card ?? {},
14492
+ ...input
14493
+ });
14494
+ if (!lifecycle2) {
14495
+ return null;
14496
+ }
14497
+ overrides[agentId] = {
14498
+ ...override,
14499
+ card: lifecycle2
14500
+ };
14501
+ await writeRelayAgentOverrides(overrides);
14502
+ return lifecycle2;
14503
+ }
14504
+ function oneTimeCardCreatedAt(lifecycle2) {
14505
+ return normalizeCardTimestamp(lifecycle2.createdAt) ?? 0;
14506
+ }
14507
+ function shouldPruneOneTimeCard(lifecycle2, now, maxAgeMs) {
14508
+ if (lifecycle2.kind !== "one_time") {
14509
+ return false;
14510
+ }
14511
+ if (lifecycle2.expiresAt !== undefined && lifecycle2.expiresAt <= now) {
14512
+ return true;
14513
+ }
14514
+ const createdAt = oneTimeCardCreatedAt(lifecycle2);
14515
+ return createdAt > 0 && createdAt + maxAgeMs <= now;
14516
+ }
14517
+ function oneTimeCardMatchesScope(agentId, override, input) {
14518
+ if (BUILT_IN_AGENT_DEFINITION_IDS.has(agentId)) {
14519
+ return false;
14520
+ }
14521
+ const lifecycle2 = normalizeLocalAgentCardLifecycle(override.card);
14522
+ if (lifecycle2?.kind !== "one_time") {
14523
+ return false;
14524
+ }
14525
+ if (input.createdById?.trim() && lifecycle2.createdById !== input.createdById.trim()) {
14526
+ return false;
14527
+ }
14528
+ if (input.projectRoot?.trim()) {
14529
+ const scopedRoot = normalizeProjectPath(input.projectRoot);
14530
+ const cardRoot = normalizeProjectPath(override.projectRoot || override.runtime?.cwd || ".");
14531
+ if (cardRoot !== scopedRoot) {
14532
+ return false;
14533
+ }
14534
+ }
14535
+ return true;
14536
+ }
14537
+ async function retireLocalAgentOverrides(overrides, agentIds) {
14538
+ const retired = [];
14539
+ for (const agentId of agentIds) {
14540
+ const override = overrides[agentId];
14541
+ if (!override) {
14542
+ continue;
14543
+ }
14544
+ const record = localAgentRecordFromRelayAgentOverride(agentId, override);
14545
+ const status = localAgentStatusFromRecord(agentId, record, localAgentStatusSource(agentId, overrides));
14546
+ await stopLocalAgent(agentId).catch(() => null);
14547
+ retired.push({
14548
+ ...status,
14549
+ isOnline: false
14550
+ });
14551
+ delete overrides[agentId];
14552
+ }
14553
+ if (retired.length > 0) {
14554
+ await writeRelayAgentOverrides(overrides);
14555
+ }
14556
+ return retired;
14557
+ }
14558
+ async function pruneOneTimeLocalAgentCards(input = {}) {
14559
+ const now = input.now ?? Date.now();
14560
+ const maxAgeMs = Math.max(0, input.maxAgeMs ?? DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS);
14561
+ const maxCount = Math.max(0, input.maxCount ?? DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_RETAIN);
14562
+ const excluded = new Set(input.excludeAgentIds ?? []);
14563
+ const overrides = await readRelayAgentOverrides();
14564
+ const candidates = Object.entries(overrides).filter(([agentId, override]) => !excluded.has(agentId) && oneTimeCardMatchesScope(agentId, override, input)).map(([agentId, override]) => ({
14565
+ agentId,
14566
+ override,
14567
+ lifecycle: normalizeLocalAgentCardLifecycle(override.card)
14568
+ })).sort((left, right) => oneTimeCardCreatedAt(right.lifecycle) - oneTimeCardCreatedAt(left.lifecycle) || left.agentId.localeCompare(right.agentId));
14569
+ const retireIds = new Set;
14570
+ for (const candidate of candidates) {
14571
+ if (shouldPruneOneTimeCard(candidate.lifecycle, now, maxAgeMs)) {
14572
+ retireIds.add(candidate.agentId);
14573
+ }
14574
+ }
14575
+ const retained = candidates.filter((candidate) => !retireIds.has(candidate.agentId));
14576
+ for (const candidate of retained.slice(maxCount)) {
14577
+ retireIds.add(candidate.agentId);
14578
+ }
14579
+ const retired = await retireLocalAgentOverrides({ ...overrides }, retireIds);
14580
+ return {
14581
+ inspected: candidates.length,
14582
+ remaining: Math.max(0, candidates.length - retired.length),
14583
+ retired
14584
+ };
14585
+ }
14586
+ async function retireLocalAgent(agentId) {
14587
+ const overrides = await readRelayAgentOverrides();
14588
+ const override = overrides[agentId];
14589
+ if (!override) {
14590
+ return null;
14591
+ }
14592
+ const record = localAgentRecordFromRelayAgentOverride(agentId, override);
14593
+ const status = localAgentStatusFromRecord(agentId, record, localAgentStatusSource(agentId, overrides));
14594
+ await stopLocalAgent(agentId).catch(() => null);
14595
+ const nextOverrides = { ...overrides };
14596
+ delete nextOverrides[agentId];
14597
+ await writeRelayAgentOverrides(nextOverrides);
14598
+ return {
14599
+ ...status,
14600
+ isOnline: false
14601
+ };
14602
+ }
13545
14603
  function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
13546
14604
  const permissionPosture = compileCodexPermissionProfile(record.permissionProfile);
13547
14605
  return {
@@ -13592,6 +14650,90 @@ function buildLocalAgentSystemPrompt(agentName, projectName, projectPath, option
13592
14650
  function buildLocalAgentInitialMessage(projectName, agentName) {
13593
14651
  return `You are now online as the ${agentName} relay agent for ${projectName}. Announce yourself on the relay with: ${brokerRelayCommand()} send --as ${agentName} "relay agent online \u2014 ready to assist with ${projectName}"`;
13594
14652
  }
14653
+ function tmuxDispatchSleep(ms) {
14654
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
14655
+ }
14656
+ function captureTmuxPaneTail(sessionName, lines) {
14657
+ try {
14658
+ return execFileSync4("tmux", ["capture-pane", "-p", "-t", sessionName, "-S", `-${lines}`, "-E", "-"], { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
14659
+ } catch {
14660
+ return "";
14661
+ }
14662
+ }
14663
+ async function waitForTmuxHarnessReady(sessionName, harness) {
14664
+ if (harness !== "claude") {
14665
+ return;
14666
+ }
14667
+ const deadline = Date.now() + TMUX_READY_TIMEOUT_MS;
14668
+ let paneTail = "";
14669
+ while (Date.now() < deadline) {
14670
+ if (!isLocalAgentSessionAlive(sessionName)) {
14671
+ throw new Error(`tmux session ${sessionName} exited before Claude Code was ready.`);
14672
+ }
14673
+ paneTail = captureTmuxPaneTail(sessionName, TMUX_READY_TAIL_LINES);
14674
+ if (tmuxPaneTailShowsReadyComposer(paneTail)) {
14675
+ return;
14676
+ }
14677
+ await tmuxDispatchSleep(TMUX_READY_POLL_MS);
14678
+ }
14679
+ const tail = stripTerminalControlSequences(paneTail).trim().split(/\r?\n/).slice(-20).join(`
14680
+ `).trim();
14681
+ throw new Error(`tmux session ${sessionName} did not show a ready Claude Code composer within ${TMUX_READY_TIMEOUT_MS}ms.` + (tail ? `
14682
+ Recent pane tail:
14683
+ ${tail}` : ""));
14684
+ }
14685
+ function tmuxPaneTailShowsReadyComposer(paneTail) {
14686
+ const cleanedTail = stripTerminalControlSequences(paneTail);
14687
+ const lines = cleanedTail.split(/\r?\n/);
14688
+ const anchor = findActiveTmuxComposerAnchor(lines);
14689
+ if (!anchor) {
14690
+ return false;
14691
+ }
14692
+ const afterComposerLines = [];
14693
+ for (const line of lines.slice(anchor.index + 1)) {
14694
+ if (isTmuxComposerBoundary(line)) {
14695
+ break;
14696
+ }
14697
+ afterComposerLines.push(line);
14698
+ }
14699
+ return !tmuxPaneTailShowsHarnessActivity(afterComposerLines.join(`
14700
+ `));
14701
+ }
14702
+ function stripTerminalControlSequences(value) {
14703
+ return value.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
14704
+ }
14705
+ function findActiveTmuxComposerAnchor(lines) {
14706
+ const inlineComposerIndex = findLastIndex(lines, (line) => /^\s*[\u276F\u203A]\s*/.test(line));
14707
+ const boxedComposerIndex = findLastIndex(lines, (line) => /^\s*[\u2502\u2503]\s*[>\u276F]\s*/.test(line));
14708
+ if (inlineComposerIndex < 0 && boxedComposerIndex < 0) {
14709
+ return null;
14710
+ }
14711
+ if (inlineComposerIndex > boxedComposerIndex) {
14712
+ return { index: inlineComposerIndex, kind: "inline" };
14713
+ }
14714
+ return { index: boxedComposerIndex, kind: "boxed" };
14715
+ }
14716
+ function findLastIndex(items, predicate) {
14717
+ for (let index = items.length - 1;index >= 0; index -= 1) {
14718
+ if (predicate(items[index])) {
14719
+ return index;
14720
+ }
14721
+ }
14722
+ return -1;
14723
+ }
14724
+ function isTmuxComposerBoundary(line) {
14725
+ const trimmed = line.trim();
14726
+ if (!trimmed) {
14727
+ return false;
14728
+ }
14729
+ if (/^[\u2500\u2501\u2550\u256D\u256E\u2570\u256F\u250C\u2510\u2514\u2518\u2554\u2557\u255A\u255D\u255F\u2562\u2560\u2563\u256A\u256B\u256C\u2569\u2566\u2564\u2567\u254C\u254D\u254E\u254F\s]+$/.test(trimmed)) {
14730
+ return true;
14731
+ }
14732
+ return /^--\s*(?:INSERT|NORMAL)\s*--/.test(trimmed) || /^(?:Opus|Sonnet|Haiku|Claude|Codex|GPT)\b/.test(trimmed);
14733
+ }
14734
+ function tmuxPaneTailShowsHarnessActivity(paneTail) {
14735
+ return /(?:^|\n)\s*(?:[\u23FA\u25CF\u273D\u2722\u273B\u23BF]|Bash\(|Read\(|Edit\(|Write\(|Grep\(|Glob\(|TodoWrite\()/.test(paneTail);
14736
+ }
13595
14737
  function shellQuoteArguments(args) {
13596
14738
  return args.map((arg) => JSON.stringify(arg)).join(" ");
13597
14739
  }
@@ -13602,10 +14744,10 @@ function buildLocalAgentLaunchCommand(agentName, record, projectPath, promptFile
13602
14744
  const harness = normalizeLocalAgentHarness(record.harness);
13603
14745
  const extraArgs = shellQuoteArguments(harness === "claude" ? normalizeClaudeRuntimeLaunchArgs(record.launchArgs) : normalizeLaunchArgsForHarness(harness, record.launchArgs));
13604
14746
  if (harness === "codex") {
13605
- return `exec bash ${JSON.stringify(workerScript ?? join19(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
14747
+ return `exec bash ${JSON.stringify(workerScript ?? join20(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
13606
14748
  }
13607
14749
  if (harness === "pi") {
13608
- const sessionDir = join19(relayAgentRuntimeDirectory(agentName), "pi-sessions");
14750
+ const sessionDir = join20(relayAgentRuntimeDirectory(agentName), "pi-sessions");
13609
14751
  return [
13610
14752
  "pi",
13611
14753
  `--append-system-prompt "$(cat ${JSON.stringify(promptFile)})"`,
@@ -13675,7 +14817,7 @@ async function ensureLocalAgentOnline(agentName, record) {
13675
14817
  await mkdir6(agentRuntimeDir, { recursive: true });
13676
14818
  await mkdir6(logsDir, { recursive: true });
13677
14819
  if (normalizedRecord.transport === "codex_app_server") {
13678
- await writeFile6(join19(agentRuntimeDir, "prompt.txt"), systemPrompt);
14820
+ await writeFile6(join20(agentRuntimeDir, "prompt.txt"), systemPrompt);
13679
14821
  await ensureCodexAppServerAgentOnline(buildCodexAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
13680
14822
  const registry2 = await readLocalAgentRegistry();
13681
14823
  registry2[agentName] = {
@@ -13687,7 +14829,7 @@ async function ensureLocalAgentOnline(agentName, record) {
13687
14829
  return registry2[agentName];
13688
14830
  }
13689
14831
  if (normalizedRecord.transport === "claude_stream_json") {
13690
- await writeFile6(join19(agentRuntimeDir, "prompt.txt"), systemPrompt);
14832
+ await writeFile6(join20(agentRuntimeDir, "prompt.txt"), systemPrompt);
13691
14833
  await ensureClaudeStreamJsonAgentOnline(buildClaudeAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
13692
14834
  const registry2 = await readLocalAgentRegistry();
13693
14835
  registry2[agentName] = {
@@ -13700,17 +14842,17 @@ async function ensureLocalAgentOnline(agentName, record) {
13700
14842
  }
13701
14843
  const initialMessage = buildLocalAgentInitialMessage(projectName, agentName);
13702
14844
  const bootstrapPrompt = buildLocalAgentBootstrapPrompt(normalizeLocalAgentHarness(normalizedRecord.harness), systemPrompt, initialMessage);
13703
- const queueDirectory = join19(agentRuntimeDir, "queue");
13704
- const processingDirectory = join19(queueDirectory, "processing");
13705
- const processedDirectory = join19(queueDirectory, "processed");
13706
- const promptFile = join19(agentRuntimeDir, "prompt.txt");
13707
- const initialFile = join19(agentRuntimeDir, "initial.txt");
13708
- const launchScript = join19(agentRuntimeDir, "launch.sh");
13709
- const workerScript = join19(agentRuntimeDir, "codex-worker.sh");
13710
- const codexSessionIdFile = join19(agentRuntimeDir, "codex-session-id.txt");
13711
- const stateFile = join19(agentRuntimeDir, "state.json");
13712
- const stdoutLogFile = join19(logsDir, "stdout.log");
13713
- const stderrLogFile = join19(logsDir, "stderr.log");
14845
+ const queueDirectory = join20(agentRuntimeDir, "queue");
14846
+ const processingDirectory = join20(queueDirectory, "processing");
14847
+ const processedDirectory = join20(queueDirectory, "processed");
14848
+ const promptFile = join20(agentRuntimeDir, "prompt.txt");
14849
+ const initialFile = join20(agentRuntimeDir, "initial.txt");
14850
+ const launchScript = join20(agentRuntimeDir, "launch.sh");
14851
+ const workerScript = join20(agentRuntimeDir, "codex-worker.sh");
14852
+ const codexSessionIdFile = join20(agentRuntimeDir, "codex-session-id.txt");
14853
+ const stateFile = join20(agentRuntimeDir, "state.json");
14854
+ const stdoutLogFile = join20(logsDir, "stdout.log");
14855
+ const stderrLogFile = join20(logsDir, "stderr.log");
13714
14856
  await writeFile6(promptFile, systemPrompt);
13715
14857
  await writeFile6(initialFile, bootstrapPrompt);
13716
14858
  await writeFile6(stderrLogFile, "");
@@ -13775,11 +14917,17 @@ async function ensureLocalAgentOnline(agentName, record) {
13775
14917
  await new Promise((resolve7) => setTimeout(resolve7, 100));
13776
14918
  }
13777
14919
  if (!isLocalAgentSessionAlive(normalizedRecord.tmuxSession)) {
13778
- const stderrTail = existsSync16(stderrLogFile) ? readFileSync10(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
14920
+ const stderrTail = existsSync17(stderrLogFile) ? readFileSync11(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
13779
14921
  `).trim() : "";
13780
14922
  throw new Error(stderrTail ? `Relay agent ${agentName} failed to stay online:
13781
14923
  ${stderrTail}` : `Relay agent ${agentName} failed to stay online.`);
13782
14924
  }
14925
+ try {
14926
+ await waitForTmuxHarnessReady(normalizedRecord.tmuxSession, normalizeLocalAgentHarness(normalizedRecord.harness));
14927
+ } catch (error) {
14928
+ killAgentSession(normalizedRecord.tmuxSession);
14929
+ throw error;
14930
+ }
13783
14931
  const registry = await readLocalAgentRegistry();
13784
14932
  registry[agentName] = {
13785
14933
  ...normalizedRecord,
@@ -13836,6 +14984,7 @@ async function startLocalAgent(input) {
13836
14984
  const effectiveCwd = input.cwdOverride ? normalizeProjectPath(input.cwdOverride) : undefined;
13837
14985
  const shouldEnsureOnline = input.ensureOnline !== false;
13838
14986
  const requestedPermissionProfile = parseScoutPermissionProfile(input.permissionProfile);
14987
+ const cardLifecycle = normalizeLocalAgentCardLifecycle(input.card);
13839
14988
  const requestedDefinitionId = input.agentName?.trim() ? normalizeAgentSelectorSegment(input.agentName.trim()) : "";
13840
14989
  if (input.agentName?.trim() && !requestedDefinitionId) {
13841
14990
  throw new Error(`Invalid agent name "${input.agentName}".`);
@@ -13885,7 +15034,8 @@ async function startLocalAgent(input) {
13885
15034
  const configDefinitionId = coldProjectConfig?.agent?.id?.trim() ? normalizeAgentSelectorSegment(coldProjectConfig.agent.id.trim()) : "";
13886
15035
  const definitionId = requestedDefinitionId || configDefinitionId || normalizeAgentSelectorSegment(basename8(projectRoot)) || "agent";
13887
15036
  const configDisplayName = coldProjectConfig?.agent?.displayName?.trim() || "";
13888
- const effectiveDisplayName = input.displayName || configDisplayName || titleCaseLocalAgentName(definitionId);
15037
+ const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(definitionId);
15038
+ const effectiveDisplayName = input.displayName || operatorAugmentDefaults?.displayName || configDisplayName || titleCaseLocalAgentName(definitionId);
13889
15039
  const instance = buildRelayAgentInstance(definitionId, projectRoot);
13890
15040
  const configDefaultHarness = coldProjectConfig?.agent?.runtime?.defaultHarness;
13891
15041
  const effectiveHarness = normalizeManagedHarness2(preferredHarness ?? configDefaultHarness, "claude");
@@ -13908,8 +15058,10 @@ async function startLocalAgent(input) {
13908
15058
  projectConfigPath: coldProjectConfigPath,
13909
15059
  source: "manual",
13910
15060
  startedAt: nowSeconds2(),
15061
+ ...operatorAugmentDefaults ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
13911
15062
  launchArgs,
13912
15063
  defaultHarness: effectiveHarness,
15064
+ ...cardLifecycle ? { card: cardLifecycle } : {},
13913
15065
  harnessProfiles: {
13914
15066
  [effectiveHarness]: {
13915
15067
  cwd: effectiveCwd ?? projectRoot,
@@ -13945,18 +15097,21 @@ async function startLocalAgent(input) {
13945
15097
  reasoningEffort: input.reasoningEffort
13946
15098
  });
13947
15099
  const nextTransport = normalizeLocalAgentTransport(preferredHarness ? undefined : matchingOverride.runtime?.transport, nextHarness);
15100
+ const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(requestedDefinitionId);
13948
15101
  overrides[instance.id] = {
13949
15102
  agentId: instance.id,
13950
15103
  definitionId: requestedDefinitionId,
13951
- displayName: input.displayName || titleCaseLocalAgentName(requestedDefinitionId),
15104
+ displayName: input.displayName || operatorAugmentDefaults?.displayName || titleCaseLocalAgentName(requestedDefinitionId),
13952
15105
  projectName: matchingOverride.projectName ?? basename8(matchingProjectRoot),
13953
15106
  projectRoot: matchingProjectRoot,
13954
15107
  projectConfigPath: null,
13955
15108
  source: "manual",
13956
15109
  startedAt: matchingOverride.startedAt ?? nowSeconds2(),
15110
+ ...operatorAugmentDefaults ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
13957
15111
  launchArgs: nextHarness === nextDefaultHarness ? nextLaunchArgs : matchingOverride.launchArgs,
13958
15112
  capabilities: matchingOverride.capabilities,
13959
15113
  defaultHarness: nextDefaultHarness,
15114
+ ...cardLifecycle ? { card: cardLifecycle } : {},
13960
15115
  harnessProfiles: {
13961
15116
  ...matchingOverride.harnessProfiles ?? {},
13962
15117
  [nextHarness]: {
@@ -13978,7 +15133,9 @@ async function startLocalAgent(input) {
13978
15133
  await writeRelayAgentOverrides(overrides);
13979
15134
  targetAgentId = instance.id;
13980
15135
  } else {
13981
- if (input.model || input.reasoningEffort || requestedPermissionProfile) {
15136
+ const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(matchingOverride.definitionId ?? requestedDefinitionId);
15137
+ const shouldApplyOperatorAugmentPrompt = Boolean(operatorAugmentDefaults && !matchingOverride.systemPrompt?.trim());
15138
+ if (input.model || input.reasoningEffort || requestedPermissionProfile || cardLifecycle || shouldApplyOperatorAugmentPrompt) {
13982
15139
  const existingHarness = normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness ?? matchingOverride.runtime?.harness, "claude");
13983
15140
  const nextLaunchArgs = applyRequestedRuntimeOptionsToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), {
13984
15141
  model: input.model,
@@ -13986,6 +15143,8 @@ async function startLocalAgent(input) {
13986
15143
  });
13987
15144
  overrides[matchingAgentId] = {
13988
15145
  ...matchingOverride,
15146
+ ...cardLifecycle ? { card: cardLifecycle } : {},
15147
+ ...shouldApplyOperatorAugmentPrompt ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
13989
15148
  launchArgs: existingHarness === defaultHarnessForOverride(matchingOverride, existingHarness) ? nextLaunchArgs : matchingOverride.launchArgs,
13990
15149
  harnessProfiles: {
13991
15150
  ...matchingOverride.harnessProfiles ?? {},
@@ -14090,9 +15249,9 @@ async function restartAllLocalAgents(options = {}) {
14090
15249
  function readPersistedClaudeSessionId(agentName) {
14091
15250
  try {
14092
15251
  const runtimeDir = relayAgentRuntimeDirectory(agentName);
14093
- const catalogPath = join19(runtimeDir, "session-catalog.json");
14094
- if (existsSync16(catalogPath)) {
14095
- const raw = readFileSync10(catalogPath, "utf8").trim();
15252
+ const catalogPath = join20(runtimeDir, "session-catalog.json");
15253
+ if (existsSync17(catalogPath)) {
15254
+ const raw = readFileSync11(catalogPath, "utf8").trim();
14096
15255
  if (raw) {
14097
15256
  const catalog = JSON.parse(raw);
14098
15257
  if (typeof catalog.activeSessionId === "string" && catalog.activeSessionId.trim()) {
@@ -14100,9 +15259,9 @@ function readPersistedClaudeSessionId(agentName) {
14100
15259
  }
14101
15260
  }
14102
15261
  }
14103
- const legacyPath = join19(runtimeDir, "claude-session-id.txt");
14104
- if (existsSync16(legacyPath)) {
14105
- const value = readFileSync10(legacyPath, "utf8").trim();
15262
+ const legacyPath = join20(runtimeDir, "claude-session-id.txt");
15263
+ if (existsSync17(legacyPath)) {
15264
+ const value = readFileSync11(legacyPath, "utf8").trim();
14106
15265
  return value || null;
14107
15266
  }
14108
15267
  return null;
@@ -14114,6 +15273,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14114
15273
  const normalizedRecord = normalizeLocalAgentRecord(agentId, record);
14115
15274
  const configuredModel = readLaunchModelForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs);
14116
15275
  const codexPermissionPosture = normalizeLocalAgentHarness(normalizedRecord.harness) === "codex" ? compileCodexPermissionProfile(normalizedRecord.permissionProfile) : null;
15276
+ const cardLifecycle = normalizeLocalAgentCardLifecycle(normalizedRecord.card);
14117
15277
  const definitionId = normalizedRecord.definitionId ?? agentId;
14118
15278
  const displayName = titleCaseLocalAgentName(definitionId);
14119
15279
  const projectRoot = normalizedRecord.projectRoot ?? normalizedRecord.cwd;
@@ -14141,6 +15301,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14141
15301
  workspaceQualifier: instance.workspaceQualifier,
14142
15302
  branch: instance.branch,
14143
15303
  ...configuredModel ? { model: configuredModel } : {},
15304
+ ...cardLifecycle ? { cardLifecycle } : {},
14144
15305
  ...codexPermissionPosture ? {
14145
15306
  permissionProfile: codexPermissionPosture.profile,
14146
15307
  approvalPolicy: codexPermissionPosture.approvalPolicy,
@@ -14176,6 +15337,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14176
15337
  workspaceQualifier: instance.workspaceQualifier,
14177
15338
  branch: instance.branch,
14178
15339
  ...configuredModel ? { model: configuredModel } : {},
15340
+ ...cardLifecycle ? { cardLifecycle } : {},
14179
15341
  ...codexPermissionPosture ? {
14180
15342
  permissionProfile: codexPermissionPosture.profile,
14181
15343
  approvalPolicy: codexPermissionPosture.approvalPolicy,
@@ -14216,6 +15378,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14216
15378
  workspaceQualifier: instance.workspaceQualifier,
14217
15379
  branch: instance.branch,
14218
15380
  ...configuredModel ? { model: configuredModel } : {},
15381
+ ...cardLifecycle ? { cardLifecycle } : {},
14219
15382
  ...codexPermissionPosture ? {
14220
15383
  permissionProfile: codexPermissionPosture.profile,
14221
15384
  approvalPolicy: codexPermissionPosture.approvalPolicy,
@@ -14302,9 +15465,10 @@ async function ensureLocalAgentBindingOnline(agentId, nodeId, options = {}) {
14302
15465
  const onlineRecord = await ensureLocalAgentOnline(agentId, recordForHarness(localAgentRecordFromRelayAgentOverride(agentId, override), options.harness));
14303
15466
  return buildLocalAgentBinding(agentId, onlineRecord, isLocalAgentRecordOnline(agentId, onlineRecord), nodeId, "relay-agent-registry");
14304
15467
  }
14305
- var MODULE_DIRECTORY, OPENSCOUT_REPO_ROOT, DEFAULT_LOCAL_AGENT_CAPABILITIES, DEFAULT_LOCAL_AGENT_HARNESS = "claude", SUPPORTED_LOCAL_AGENT_HARNESSES, SUPPORTED_SCOUT_HARNESSES, DEFAULT_CLAUDE_SCOUT_ALLOWED_TOOLS;
15468
+ var MODULE_DIRECTORY, OPENSCOUT_REPO_ROOT, DEFAULT_LOCAL_AGENT_CAPABILITIES, DEFAULT_LOCAL_AGENT_HARNESS = "claude", DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS, DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_RETAIN = 24, SUPPORTED_LOCAL_AGENT_HARNESSES, SUPPORTED_SCOUT_HARNESSES, DEFAULT_CLAUDE_SCOUT_ALLOWED_TOOLS, TMUX_READY_TIMEOUT_MS = 20000, TMUX_READY_POLL_MS = 250, TMUX_READY_TAIL_LINES = 80;
14306
15469
  var init_local_agents = __esm(() => {
14307
15470
  init_src2();
15471
+ init_dispatch_stalled();
14308
15472
  init_claude_stream_json();
14309
15473
  init_codex_app_server();
14310
15474
  init_setup();
@@ -14314,9 +15478,11 @@ var init_local_agents = __esm(() => {
14314
15478
  init_local_agent_template();
14315
15479
  init_permission_policy2();
14316
15480
  init_requester_timeout();
14317
- MODULE_DIRECTORY = dirname9(fileURLToPath5(import.meta.url));
15481
+ init_user_config();
15482
+ MODULE_DIRECTORY = dirname10(fileURLToPath5(import.meta.url));
14318
15483
  OPENSCOUT_REPO_ROOT = resolve6(MODULE_DIRECTORY, "..", "..", "..");
14319
15484
  DEFAULT_LOCAL_AGENT_CAPABILITIES = ["chat", "invoke", "deliver"];
15485
+ DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS = 24 * 60 * 60 * 1000;
14320
15486
  SUPPORTED_LOCAL_AGENT_HARNESSES = ["claude", "codex", "pi"];
14321
15487
  SUPPORTED_SCOUT_HARNESSES = [
14322
15488
  ...SUPPORTED_LOCAL_AGENT_HARNESSES,
@@ -14328,11 +15494,12 @@ var init_local_agents = __esm(() => {
14328
15494
  "mcp__scout__messages_inbox",
14329
15495
  "mcp__scout__messages_channel",
14330
15496
  "mcp__scout__broker_feed",
15497
+ "mcp__scout__tail_events",
14331
15498
  "mcp__scout__agents_search",
14332
15499
  "mcp__scout__agents_resolve",
14333
15500
  "mcp__scout__messages_reply",
15501
+ "mcp__scout__ask",
14334
15502
  "mcp__scout__messages_send",
14335
- "mcp__scout__invocations_ask",
14336
15503
  "mcp__scout__invocations_get",
14337
15504
  "mcp__scout__invocations_wait",
14338
15505
  "mcp__scout__work_update",
@@ -17829,7 +18996,7 @@ function writeJsonAtomically(filePath, value) {
17829
18996
 
17830
18997
  // ../web/server/core/pairing/runtime/runtime.ts
17831
18998
  init_src();
17832
- import { homedir as homedir24 } from "os";
18999
+ import { homedir as homedir25 } from "os";
17833
19000
 
17834
19001
  // ../web/server/core/pairing/runtime/bridge/bridge.ts
17835
19002
  init_src();
@@ -18643,18 +19810,21 @@ function formatTimestamp(value) {
18643
19810
 
18644
19811
  // ../web/server/core/pairing/runtime/bridge/server.ts
18645
19812
  init_src();
18646
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, realpathSync, statSync as statSync5 } from "fs";
19813
+ import { readdirSync as readdirSync5, readFileSync as readFileSync13, realpathSync, statSync as statSync5 } from "fs";
18647
19814
  import { execSync as execSync3 } from "child_process";
18648
- import { basename as basename10, isAbsolute as isAbsolute3, join as join23, relative as relative3 } from "path";
18649
- import { homedir as homedir21 } from "os";
19815
+ import { basename as basename11, isAbsolute as isAbsolute3, join as join25, relative as relative3 } from "path";
19816
+ import { homedir as homedir22 } from "os";
18650
19817
 
18651
19818
  // ../web/server/core/mobile/service.ts
18652
19819
  init_harness_catalog();
18653
19820
  init_setup();
18654
- import { basename as basename9, resolve as resolve7 } from "path";
19821
+ import { basename as basename10, resolve as resolve7 } from "path";
18655
19822
 
18656
19823
  // ../runtime/src/control-plane-agents.ts
19824
+ init_src2();
18657
19825
  init_local_agents();
19826
+ import { randomUUID as randomUUID2 } from "crypto";
19827
+ import { basename as basename9 } from "path";
18658
19828
 
18659
19829
  // ../runtime/src/scout-agent-cards.ts
18660
19830
  init_src2();
@@ -18707,6 +19877,7 @@ function buildScoutAgentCard(binding, options = {}) {
18707
19877
  const supportedInterfaces = metadataRecordArray(binding.agent.metadata, "supportedInterfaces");
18708
19878
  const securitySchemes = metadataRecord2(binding.agent.metadata, "securitySchemes");
18709
19879
  const securityRequirements = metadataStringMatrix(binding.agent.metadata, "securityRequirements");
19880
+ const lifecycle2 = options.lifecycle ?? metadataRecord2(binding.agent.metadata, "cardLifecycle");
18710
19881
  return {
18711
19882
  id: binding.agent.id,
18712
19883
  agentId: binding.agent.id,
@@ -18736,6 +19907,7 @@ function buildScoutAgentCard(binding, options = {}) {
18736
19907
  ...options.createdById?.trim() ? { createdById: options.createdById.trim() } : {},
18737
19908
  brokerRegistered: options.brokerRegistered ?? false,
18738
19909
  ...options.inboxConversationId?.trim() ? { inboxConversationId: options.inboxConversationId.trim() } : {},
19910
+ ...lifecycle2 ? { lifecycle: lifecycle2 } : {},
18739
19911
  returnAddress: buildScoutReturnAddress({
18740
19912
  actorId: binding.agent.id,
18741
19913
  handle,
@@ -18753,19 +19925,30 @@ function buildScoutAgentCard(binding, options = {}) {
18753
19925
  actorId: binding.actor.id,
18754
19926
  endpointId: binding.endpoint.id,
18755
19927
  wakePolicy: binding.agent.wakePolicy,
18756
- ...model ? { model } : {}
19928
+ ...model ? { model } : {},
19929
+ ...lifecycle2 ? { cardLifecycle: lifecycle2 } : {}
18757
19930
  }
18758
19931
  };
18759
19932
  }
18760
19933
 
18761
19934
  // ../runtime/src/control-plane-agents.ts
19935
+ var DEFAULT_ONE_TIME_SCOUT_AGENT_CARD_TTL_MS = 24 * 60 * 60 * 1000;
19936
+ function oneTimeAgentName(input) {
19937
+ const base = normalizeAgentSelectorSegment(input.agentName || basename9(input.projectPath)) || "agent";
19938
+ return `${base}-card-${randomUUID2().slice(0, 8)}`;
19939
+ }
18762
19940
  function createScoutAgentService(deps) {
18763
19941
  const localAgents = {
18764
19942
  listLocalAgents: deps.localAgents?.listLocalAgents ?? listLocalAgents,
19943
+ retireLocalAgent: deps.localAgents?.retireLocalAgent ?? retireLocalAgent,
18765
19944
  restartAllLocalAgents: deps.localAgents?.restartAllLocalAgents ?? restartAllLocalAgents,
19945
+ restartLocalAgent: deps.localAgents?.restartLocalAgent ?? restartLocalAgent,
18766
19946
  startLocalAgent: deps.localAgents?.startLocalAgent ?? startLocalAgent,
18767
19947
  stopAllLocalAgents: deps.localAgents?.stopAllLocalAgents ?? stopAllLocalAgents,
18768
19948
  stopLocalAgent: deps.localAgents?.stopLocalAgent ?? stopLocalAgent,
19949
+ updateLocalAgentCard: deps.localAgents?.updateLocalAgentCard ?? updateLocalAgentCard,
19950
+ updateLocalAgentCardLifecycle: deps.localAgents?.updateLocalAgentCardLifecycle ?? updateLocalAgentCardLifecycle,
19951
+ pruneOneTimeLocalAgentCards: deps.localAgents?.pruneOneTimeLocalAgentCards ?? pruneOneTimeLocalAgentCards,
18769
19952
  inferLocalAgentBinding: deps.localAgents?.inferLocalAgentBinding ?? inferLocalAgentBinding
18770
19953
  };
18771
19954
  return {
@@ -18782,22 +19965,62 @@ function createScoutAgentService(deps) {
18782
19965
  async downScoutAgent(agentId) {
18783
19966
  return localAgents.stopLocalAgent(agentId);
18784
19967
  },
19968
+ async retireScoutAgentCard(agentId) {
19969
+ const broker = await deps.loadScoutBrokerContext().catch(() => null);
19970
+ const retired = await localAgents.retireLocalAgent(agentId);
19971
+ if (retired) {
19972
+ await deps.retireScoutLocalAgentBinding?.({ agentId, broker }).catch(() => false);
19973
+ }
19974
+ return retired;
19975
+ },
19976
+ async updateScoutAgentCard(agentId, input) {
19977
+ const config = await localAgents.updateLocalAgentCard(agentId, input);
19978
+ if (!config) {
19979
+ return null;
19980
+ }
19981
+ if (input.restart === true) {
19982
+ await localAgents.restartLocalAgent(agentId);
19983
+ }
19984
+ await deps.registerScoutLocalAgentBinding({ agentId }).catch(() => {});
19985
+ return config;
19986
+ },
18785
19987
  async downAllScoutAgents(input = {}) {
18786
19988
  return localAgents.stopAllLocalAgents(input);
18787
19989
  },
18788
19990
  async restartScoutAgents(input = {}) {
18789
19991
  return localAgents.restartAllLocalAgents(input);
18790
19992
  },
19993
+ async cleanupScoutAgentCards(input = {}) {
19994
+ const broker = await deps.loadScoutBrokerContext().catch(() => null);
19995
+ const result = await localAgents.pruneOneTimeLocalAgentCards(input);
19996
+ await Promise.all(result.retired.map((status) => deps.retireScoutLocalAgentBinding?.({ agentId: status.agentId, broker }).catch(() => false)));
19997
+ return result;
19998
+ },
18791
19999
  async createScoutAgentCard(input) {
20000
+ const createdAt = Date.now();
20001
+ const oneTimeUse = input.oneTimeUse === true;
20002
+ const createdById = input.createdById?.trim() || undefined;
20003
+ const agentName = oneTimeUse ? oneTimeAgentName({
20004
+ agentName: input.agentName,
20005
+ projectPath: input.projectPath
20006
+ }) : input.agentName;
20007
+ const lifecycle2 = oneTimeUse ? {
20008
+ kind: "one_time",
20009
+ createdAt,
20010
+ ...createdById ? { createdById } : {},
20011
+ expiresAt: createdAt + Math.max(1, input.ttlMs ?? DEFAULT_ONE_TIME_SCOUT_AGENT_CARD_TTL_MS),
20012
+ maxUses: 1
20013
+ } : undefined;
18792
20014
  const status = await localAgents.startLocalAgent({
18793
20015
  projectPath: input.projectPath,
18794
- agentName: input.agentName,
20016
+ agentName,
18795
20017
  displayName: input.displayName,
18796
20018
  harness: input.harness,
18797
20019
  model: input.model,
18798
20020
  reasoningEffort: input.reasoningEffort,
18799
20021
  permissionProfile: input.permissionProfile,
18800
20022
  currentDirectory: input.currentDirectory,
20023
+ ...lifecycle2 ? { card: lifecycle2 } : {},
18801
20024
  ensureOnline: false
18802
20025
  });
18803
20026
  const currentDirectory = input.currentDirectory ?? input.projectPath;
@@ -18811,57 +20034,76 @@ function createScoutAgentService(deps) {
18811
20034
  throw new Error(`Agent ${status.agentId} did not expose an addressable binding.`);
18812
20035
  }
18813
20036
  let inboxConversationId;
18814
- let createdById = input.createdById?.trim() || undefined;
18815
- if (broker && createdById && createdById !== binding.agent.id) {
20037
+ let resolvedCreatedById = createdById;
20038
+ if (broker && resolvedCreatedById && resolvedCreatedById !== binding.agent.id) {
18816
20039
  const session = await deps.openScoutPeerSession({
18817
- sourceId: createdById,
20040
+ sourceId: resolvedCreatedById,
18818
20041
  targetId: binding.agent.id,
18819
20042
  currentDirectory
18820
20043
  });
18821
20044
  inboxConversationId = session.conversation.id;
18822
- createdById = session.sourceId;
20045
+ resolvedCreatedById = session.sourceId;
20046
+ }
20047
+ const finalLifecycle = lifecycle2 ? {
20048
+ ...lifecycle2,
20049
+ ...resolvedCreatedById ? { createdById: resolvedCreatedById } : {},
20050
+ ...inboxConversationId ? { inboxConversationId } : {}
20051
+ } : undefined;
20052
+ if (finalLifecycle) {
20053
+ await localAgents.updateLocalAgentCardLifecycle(status.agentId, finalLifecycle).catch(() => null);
20054
+ const cleaned = await localAgents.pruneOneTimeLocalAgentCards({
20055
+ createdById: finalLifecycle.createdById,
20056
+ projectRoot: binding.endpoint.projectRoot ?? input.projectPath,
20057
+ excludeAgentIds: [status.agentId]
20058
+ }).catch(() => null);
20059
+ if (cleaned?.retired.length) {
20060
+ await Promise.all(cleaned.retired.map((retired) => deps.retireScoutLocalAgentBinding?.({ agentId: retired.agentId, broker }).catch(() => false)));
20061
+ }
18823
20062
  }
18824
20063
  return buildScoutAgentCard(binding, {
18825
20064
  currentDirectory,
18826
- createdById,
20065
+ createdById: resolvedCreatedById,
18827
20066
  brokerRegistered: syncResult?.brokerRegistered ?? false,
18828
- inboxConversationId
20067
+ inboxConversationId,
20068
+ lifecycle: finalLifecycle
18829
20069
  });
18830
20070
  }
18831
20071
  };
18832
20072
  }
18833
20073
 
18834
20074
  // ../web/server/core/broker/service.ts
20075
+ import { randomUUID as randomUUID3 } from "crypto";
18835
20076
  init_src2();
18836
20077
  init_setup();
18837
20078
  init_broker_api();
18838
20079
  init_broker_process_manager();
18839
20080
  init_local_agents();
18840
20081
  init_support_paths();
20082
+ init_user_config();
18841
20083
 
18842
20084
  // ../runtime/src/local-config.ts
18843
20085
  import {
18844
- existsSync as existsSync17,
18845
- mkdirSync as mkdirSync9,
18846
- readFileSync as readFileSync11,
20086
+ existsSync as existsSync18,
20087
+ mkdirSync as mkdirSync10,
20088
+ readFileSync as readFileSync12,
18847
20089
  renameSync as renameSync2,
18848
- writeFileSync as writeFileSync6
20090
+ writeFileSync as writeFileSync7
18849
20091
  } from "fs";
18850
- import { homedir as homedir18, hostname as osHostname } from "os";
18851
- import { dirname as dirname10, join as join20 } from "path";
20092
+ import { homedir as homedir19, hostname as osHostname } from "os";
20093
+ import { dirname as dirname11, join as join21 } from "path";
18852
20094
  var LOCAL_CONFIG_VERSION = 1;
18853
20095
  function localConfigHome() {
18854
- return process.env.OPENSCOUT_HOME ?? join20(homedir18(), ".openscout");
20096
+ return process.env.OPENSCOUT_HOME ?? join21(homedir19(), ".openscout");
18855
20097
  }
18856
20098
  function localConfigPath() {
18857
- return join20(localConfigHome(), "config.json");
20099
+ return join21(localConfigHome(), "config.json");
18858
20100
  }
18859
20101
  function loadLocalConfig() {
18860
20102
  const configPath = localConfigPath();
18861
- if (!existsSync17(configPath))
20103
+ if (!existsSync18(configPath))
18862
20104
  return { version: LOCAL_CONFIG_VERSION };
18863
20105
  try {
18864
- return validateLocalConfig(JSON.parse(readFileSync11(configPath, "utf8")));
20106
+ return validateLocalConfig(JSON.parse(readFileSync12(configPath, "utf8")));
18865
20107
  } catch {
18866
20108
  return { version: LOCAL_CONFIG_VERSION };
18867
20109
  }
@@ -18903,6 +20145,8 @@ var scoutBrokerPaths = {
18903
20145
  node: "/v1/node",
18904
20146
  snapshot: "/v1/snapshot",
18905
20147
  topologySnapshot: "/v1/topology/snapshot",
20148
+ tailDiscover: "/v1/tail/discover",
20149
+ tailRecent: "/v1/tail/recent",
18906
20150
  messages: "/v1/messages",
18907
20151
  eventsStream: "/v1/events/stream",
18908
20152
  actors: "/v1/actors",
@@ -18921,7 +20165,6 @@ var scoutBrokerPaths = {
18921
20165
  };
18922
20166
 
18923
20167
  // ../web/server/core/broker/service.ts
18924
- var BROKER_SHARED_CHANNEL_ID = "channel.shared";
18925
20168
  var OPERATOR_ID = "operator";
18926
20169
  var DEFAULT_BROKER_HOST2 = "127.0.0.1";
18927
20170
  var DEFAULT_BROKER_PORT2 = 65535;
@@ -18952,6 +20195,13 @@ function metadataString3(metadata, key) {
18952
20195
  function metadataBoolean(metadata, key) {
18953
20196
  return metadata?.[key] === true;
18954
20197
  }
20198
+ function isSupersededBrokerAgent(snapshot, agentId) {
20199
+ const agent = snapshot.agents[agentId];
20200
+ if (!agent) {
20201
+ return false;
20202
+ }
20203
+ return metadataBoolean(agent.metadata, "retiredFromFleet") || metadataBoolean(agent.metadata, "staleLocalRegistration");
20204
+ }
18955
20205
  async function brokerReadJson(baseUrl, path2, options = {}) {
18956
20206
  return requestScoutBrokerJson(baseUrl, path2, {
18957
20207
  socketPath: resolveBrokerSocketPathForBaseUrl(baseUrl),
@@ -18989,6 +20239,17 @@ async function readScoutBrokerHealth(baseUrl = resolveScoutBrokerUrl(), options
18989
20239
  nodes: health.counts.nodes ?? 0,
18990
20240
  actors: health.counts.actors ?? 0,
18991
20241
  agents: health.counts.agents ?? 0,
20242
+ agentRecords: health.counts.agentRecords,
20243
+ rawAgentRecords: health.counts.rawAgentRecords,
20244
+ configuredAgents: health.counts.configuredAgents,
20245
+ scoutManagedAgents: health.counts.scoutManagedAgents,
20246
+ currentAgentRegistrations: health.counts.currentAgentRegistrations,
20247
+ localAgentRegistrations: health.counts.localAgentRegistrations,
20248
+ remoteAgentRegistrations: health.counts.remoteAgentRegistrations,
20249
+ staleAgentRegistrations: health.counts.staleAgentRegistrations,
20250
+ retiredAgentRegistrations: health.counts.retiredAgentRegistrations,
20251
+ oneTimeAgentCards: health.counts.oneTimeAgentCards,
20252
+ persistentAgentCards: health.counts.persistentAgentCards,
18992
20253
  conversations: health.counts.conversations ?? 0,
18993
20254
  messages: health.counts.messages ?? 0,
18994
20255
  flights: health.counts.flights ?? 0
@@ -19138,6 +20399,48 @@ async function registerScoutLocalAgentBinding(input) {
19138
20399
  brokerRegistered: Boolean(broker)
19139
20400
  };
19140
20401
  }
20402
+ async function retireScoutLocalAgentBinding(input) {
20403
+ const broker = input.broker ?? await loadScoutBrokerContext();
20404
+ if (!broker) {
20405
+ return false;
20406
+ }
20407
+ const retiredAt = Date.now();
20408
+ let retired = false;
20409
+ const agent = broker.snapshot.agents[input.agentId];
20410
+ if (agent) {
20411
+ const nextAgent = {
20412
+ ...agent,
20413
+ metadata: {
20414
+ ...agent.metadata ?? {},
20415
+ retiredFromFleet: true,
20416
+ retiredAt
20417
+ }
20418
+ };
20419
+ await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.agents, nextAgent);
20420
+ broker.snapshot.agents[input.agentId] = nextAgent;
20421
+ retired = true;
20422
+ }
20423
+ for (const endpoint of Object.values(broker.snapshot.endpoints)) {
20424
+ if (endpoint.agentId !== input.agentId) {
20425
+ continue;
20426
+ }
20427
+ const nextEndpoint = {
20428
+ ...endpoint,
20429
+ state: "offline",
20430
+ metadata: {
20431
+ ...endpoint.metadata ?? {},
20432
+ retiredFromFleet: true,
20433
+ retiredAt,
20434
+ lastError: "local agent card retired",
20435
+ lastFailedAt: retiredAt
20436
+ }
20437
+ };
20438
+ await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.endpoints, nextEndpoint);
20439
+ broker.snapshot.endpoints[endpoint.id] = nextEndpoint;
20440
+ retired = true;
20441
+ }
20442
+ return retired;
20443
+ }
19141
20444
  async function resolveConversationActorId(baseUrl, snapshot, nodeId, actorId, currentDirectory, displayName) {
19142
20445
  const normalized = actorId.trim() || OPERATOR_ID;
19143
20446
  if (snapshot.agents[normalized] || snapshot.actors[normalized]) {
@@ -19165,6 +20468,9 @@ async function resolveConversationActorId(baseUrl, snapshot, nodeId, actorId, cu
19165
20468
  }
19166
20469
  async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agentId, currentDirectory) {
19167
20470
  const existingAgent = snapshot.agents[agentId];
20471
+ if (existingAgent && metadataBoolean(existingAgent.metadata, "retiredFromFleet")) {
20472
+ return false;
20473
+ }
19168
20474
  if (existingAgent && !metadataBoolean(existingAgent.metadata, "staleLocalRegistration")) {
19169
20475
  return true;
19170
20476
  }
@@ -19173,7 +20479,7 @@ async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agent
19173
20479
  syncLegacyMirror: true
19174
20480
  });
19175
20481
  if (!configured) {
19176
- return false;
20482
+ return Boolean(existingAgent);
19177
20483
  }
19178
20484
  const binding = await inferLocalAgentBinding(configured.agentId, nodeId);
19179
20485
  if (!binding) {
@@ -19185,21 +20491,15 @@ async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agent
19185
20491
  });
19186
20492
  return Boolean(binding);
19187
20493
  }
19188
- function directConversationIdForActors(sourceId, targetId) {
19189
- if (sourceId === targetId) {
19190
- return `dm.${sourceId}.${targetId}`;
19191
- }
19192
- if (sourceId === OPERATOR_ID || targetId === OPERATOR_ID) {
19193
- const peerId = sourceId === OPERATOR_ID ? targetId : sourceId;
19194
- return `dm.${OPERATOR_ID}.${peerId}`;
19195
- }
19196
- return `dm.${[sourceId, targetId].sort().join(".")}`;
20494
+ function findConversationByIdentity(snapshot, naturalKey) {
20495
+ return Object.values(snapshot.conversations).find((conversation) => channelNaturalKeyFromMetadata(conversation.metadata) === naturalKey);
19197
20496
  }
19198
20497
  async function ensureBrokerDirectConversationBetween(baseUrl, snapshot, nodeId, sourceId, targetId) {
19199
- const conversationId = targetId === SCOUT_AGENT_ID && sourceId === OPERATOR_ID ? BROKER_SHARED_CHANNEL_ID : directConversationIdForActors(sourceId, targetId);
19200
20498
  const participantIds = [...new Set([sourceId, targetId])].sort();
20499
+ const naturalKey = directChannelNaturalKey(participantIds);
20500
+ const existing = findConversationByIdentity(snapshot, naturalKey);
20501
+ const conversationId = existing?.id ?? mintChannelId(randomUUID3);
19201
20502
  const nextShareMode = resolveConversationShareMode(snapshot, nodeId, participantIds, "local");
19202
- const existing = snapshot.conversations[conversationId];
19203
20503
  const alreadyMatches = existing && existing.kind === "direct" && existing.shareMode === nextShareMode && existing.visibility === "private" && existing.participantIds.join("\x00") === participantIds.join("\x00");
19204
20504
  if (alreadyMatches) {
19205
20505
  const preferredTargetId = targetId === OPERATOR_ID ? sourceId : targetId;
@@ -19221,6 +20521,7 @@ async function ensureBrokerDirectConversationBetween(baseUrl, snapshot, nodeId,
19221
20521
  participantIds,
19222
20522
  metadata: {
19223
20523
  surface: "scout",
20524
+ naturalKey,
19224
20525
  ...targetId === SCOUT_AGENT_ID && sourceId === OPERATOR_ID ? { role: "partner" } : {}
19225
20526
  }
19226
20527
  };
@@ -19254,6 +20555,11 @@ async function sendScoutDirectMessage(input) {
19254
20555
  const broker = await requireScoutBrokerContext();
19255
20556
  const createdAt = Date.now();
19256
20557
  const source = input.source?.trim() || "scout-mobile";
20558
+ const targetEndpoints = Object.values(broker.snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === input.agentId);
20559
+ const targetIsStale = isSupersededBrokerAgent(broker.snapshot, input.agentId) || targetEndpoints.length > 0 && targetEndpoints.every((endpoint) => metadataBoolean(endpoint.metadata, "retiredFromFleet") || metadataBoolean(endpoint.metadata, "staleLocalRegistration"));
20560
+ if (targetIsStale) {
20561
+ throw new Error(`${displayNameForBrokerActor(broker.snapshot, input.agentId)} is a stale local registration. Start the current project session from Workspaces before sending.`);
20562
+ }
19257
20563
  const delivery = await brokerPostDeliver(broker.baseUrl, {
19258
20564
  id: `deliver-${createdAt.toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
19259
20565
  requesterId: OPERATOR_ID,
@@ -19311,24 +20617,28 @@ async function loadScoutActivityItems(options = {}) {
19311
20617
  var scoutAgentService = createScoutAgentService({
19312
20618
  loadScoutBrokerContext,
19313
20619
  openScoutPeerSession,
19314
- registerScoutLocalAgentBinding
20620
+ registerScoutLocalAgentBinding,
20621
+ retireScoutLocalAgentBinding
19315
20622
  });
19316
20623
  var {
20624
+ cleanupScoutAgentCards,
19317
20625
  createScoutAgentCard,
19318
20626
  downAllScoutAgents,
19319
20627
  downScoutAgent,
19320
20628
  loadScoutAgentStatuses,
20629
+ retireScoutAgentCard,
19321
20630
  restartScoutAgents,
20631
+ updateScoutAgentCard,
19322
20632
  upScoutAgent
19323
20633
  } = scoutAgentService;
19324
20634
 
19325
20635
  // ../web/server/db/internal/db.ts
19326
20636
  import { Database } from "bun:sqlite";
19327
- import { homedir as homedir19 } from "os";
19328
- import { join as join21 } from "path";
20637
+ import { homedir as homedir20 } from "os";
20638
+ import { join as join22 } from "path";
19329
20639
  function resolveDbPath() {
19330
- const controlHome = process.env.OPENSCOUT_CONTROL_HOME ?? join21(homedir19(), ".openscout", "control-plane");
19331
- return join21(controlHome, "control-plane.sqlite");
20640
+ const controlHome = process.env.OPENSCOUT_CONTROL_HOME ?? join22(homedir20(), ".openscout", "control-plane");
20641
+ return join22(controlHome, "control-plane.sqlite");
19332
20642
  }
19333
20643
  var _db = null;
19334
20644
  var DB_BUSY_TIMEOUT_MS = 250;
@@ -19344,6 +20654,7 @@ function db() {
19344
20654
  return _db;
19345
20655
  }
19346
20656
  // ../runtime/src/conversations/legacy-ids.ts
20657
+ init_user_config();
19347
20658
  function conversationIdForAgent(agentId) {
19348
20659
  return buildDirectConversationId("operator", agentId);
19349
20660
  }
@@ -19360,9 +20671,9 @@ var EPOCH_MILLISECONDS_FLOOR = 1000000000000;
19360
20671
 
19361
20672
  // ../web/server/db/internal/paths.ts
19362
20673
  init_support_paths();
19363
- import { homedir as homedir20 } from "os";
19364
- import { join as join22 } from "path";
19365
- var HOME = homedir20();
20674
+ import { homedir as homedir21 } from "os";
20675
+ import { join as join23 } from "path";
20676
+ var HOME = homedir21();
19366
20677
  function compact(p) {
19367
20678
  if (!p)
19368
20679
  return null;
@@ -19374,12 +20685,15 @@ function pairingHarnessLogPath(adapterType, sessionId) {
19374
20685
  if (!normalizedAdapter || !normalizedSessionId) {
19375
20686
  return null;
19376
20687
  }
19377
- return join22(HOME, ".scout", "pairing", normalizedAdapter, normalizedSessionId, "logs", "stdout.log");
20688
+ return join23(HOME, ".scout", "pairing", normalizedAdapter, normalizedSessionId, "logs", "stdout.log");
19378
20689
  }
19379
20690
  function relayHarnessLogPath(agentId) {
19380
- return join22(relayAgentLogsDirectory(agentId), "stdout.log");
20691
+ return join23(relayAgentLogsDirectory(agentId), "stdout.log");
19381
20692
  }
19382
20693
  function resolveHarnessSessionId(transport, endpointSessionId, metadata) {
20694
+ if (transport === "tmux") {
20695
+ return metadataString4(metadata, "tmuxSession") ?? endpointSessionId;
20696
+ }
19383
20697
  if (transport === "pairing_bridge") {
19384
20698
  const attachedTransport = metadataString4(metadata, "attachedTransport");
19385
20699
  if (attachedTransport === "codex_app_server") {
@@ -19444,7 +20758,8 @@ function summarizeAgentState(rawState, isWorking, wakePolicy) {
19444
20758
  if (isWorking) {
19445
20759
  return "working";
19446
20760
  }
19447
- if (rawState === "offline" && wakePolicy === "on_demand") {
20761
+ const wakeableWithoutEndpoint = wakePolicy === "on_demand" || wakePolicy === "always_on";
20762
+ if ((!rawState || rawState === "offline") && wakeableWithoutEndpoint) {
19448
20763
  return "available";
19449
20764
  }
19450
20765
  return rawState && rawState !== "offline" ? "available" : "offline";
@@ -19466,13 +20781,22 @@ function transientBrokerWorkingStatusPredicate(alias) {
19466
20781
  return `NOT (
19467
20782
  ${alias}.class = 'status'
19468
20783
  AND COALESCE(json_extract(${alias}.metadata_json, '$.source'), '') = 'broker'
19469
- AND ${alias}.body LIKE '% is working.'
20784
+ AND (
20785
+ ${alias}.body LIKE '% is working.'
20786
+ OR ${alias}.body LIKE '%Scout stopped waiting for a synchronous result%'
20787
+ OR ${alias}.body LIKE '%the requester stopped waiting after%'
20788
+ )
19470
20789
  )`;
19471
20790
  }
19472
20791
  // ../web/server/db/activity.ts
19473
20792
  var HEARTRATE_WINDOW_MS = 7 * 24 * 60 * 60000;
19474
20793
  // ../web/server/db/runs.ts
19475
20794
  init_src2();
20795
+
20796
+ // ../web/server/db/sessions.ts
20797
+ init_src2();
20798
+
20799
+ // ../web/server/db/runs.ts
19476
20800
  var ACTIVE_RUN_STATES = new Set([
19477
20801
  "queued",
19478
20802
  "waking",
@@ -19485,13 +20809,29 @@ var ACTIVE_RUN_STATES_SQL = sqlStringList([...ACTIVE_RUN_STATES]);
19485
20809
  var AGENT_RUN_STATES_SQL = sqlStringList(AGENT_RUN_STATES);
19486
20810
  var AGENT_RUN_SOURCES_SQL = sqlStringList(AGENT_RUN_SOURCES);
19487
20811
  // ../web/server/db/fleet.ts
20812
+ init_user_config();
19488
20813
  var TERMINAL_FLIGHT_STATES = new Set(["completed", "failed", "cancelled"]);
19489
20814
  var FLEET_RECENT_COMPLETED_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000;
19490
20815
  // ../web/server/db/mobile/agents.ts
19491
- function queryMobileAgents(limit = 50) {
20816
+ function queryMobileAgents(limit = 50, filters = {}) {
19492
20817
  const executingAgentIds = queryExecutingAgentIds();
19493
20818
  const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
19494
20819
  const endpointUpdatedAtExpression = sqlTimestampMsExpression("ep.updated_at");
20820
+ const query = filters.query?.trim().toLowerCase();
20821
+ const whereClauses = [activeAgentMetadataPredicate("a")];
20822
+ const params = [];
20823
+ if (query) {
20824
+ whereClauses.push(`(
20825
+ lower(ac.display_name) LIKE ?
20826
+ OR lower(a.id) LIKE ?
20827
+ OR lower(COALESCE(a.default_selector, '')) LIKE ?
20828
+ OR lower(COALESCE(a.selector, '')) LIKE ?
20829
+ OR lower(COALESCE(ep.project_root, '')) LIKE ?
20830
+ )`);
20831
+ const pattern = `%${query}%`;
20832
+ params.push(pattern, pattern, pattern, pattern, pattern);
20833
+ }
20834
+ params.push(limit);
19495
20835
  const lastMessageAt = new Map(db().prepare(`SELECT actor_id, MAX(${messageCreatedAtExpression}) AS last_at FROM messages GROUP BY actor_id`).all().map((r) => [r.actor_id, r.last_at]));
19496
20836
  const rows = db().prepare(`SELECT
19497
20837
  a.id,
@@ -19508,9 +20848,9 @@ function queryMobileAgents(limit = 50) {
19508
20848
  FROM agents a
19509
20849
  JOIN actors ac ON ac.id = a.id
19510
20850
  ${LATEST_AGENT_ENDPOINT_JOIN}
19511
- WHERE ${activeAgentMetadataPredicate("a")}
20851
+ WHERE ${whereClauses.join(" AND ")}
19512
20852
  ORDER BY COALESCE(${endpointUpdatedAtExpression}, 0) DESC, ac.display_name ASC
19513
- LIMIT ?`).all(limit);
20853
+ LIMIT ?`).all(...params);
19514
20854
  return rows.map((r) => {
19515
20855
  let meta = {};
19516
20856
  try {
@@ -19619,6 +20959,9 @@ function queryMobileAgentDetail(agentId) {
19619
20959
  }
19620
20960
  // ../web/server/db/mobile/sessions.ts
19621
20961
  function queryMobileSessions(limit = 50) {
20962
+ const conversationCreatedAtExpression = sqlTimestampMsExpression("c.created_at");
20963
+ const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
20964
+ const previewMessageCreatedAtExpression = sqlTimestampMsExpression("m.created_at");
19622
20965
  const rows = db().prepare(`SELECT
19623
20966
  c.id,
19624
20967
  c.kind,
@@ -19626,16 +20969,16 @@ function queryMobileSessions(limit = 50) {
19626
20969
  c.metadata_json
19627
20970
  FROM conversations c
19628
20971
  WHERE c.kind = 'direct'
19629
- ORDER BY c.created_at DESC
20972
+ ORDER BY ${conversationCreatedAtExpression} DESC
19630
20973
  LIMIT ?`).all(limit);
19631
20974
  const memberStmt = db().prepare(`SELECT actor_id FROM conversation_members WHERE conversation_id = ?`);
19632
- const statsStmt = db().prepare(`SELECT COUNT(*) AS cnt, MAX(created_at) AS last_at FROM messages WHERE conversation_id = ?`);
20975
+ const statsStmt = db().prepare(`SELECT COUNT(*) AS cnt, MAX(${messageCreatedAtExpression}) AS last_at FROM messages WHERE conversation_id = ?`);
19633
20976
  const previewStmt = db().prepare(`SELECT body
19634
20977
  FROM messages m
19635
20978
  WHERE conversation_id = ?
19636
20979
  AND actor_id != 'operator'
19637
20980
  AND ${transientBrokerWorkingStatusPredicate("m")}
19638
- ORDER BY created_at DESC LIMIT 1`);
20981
+ ORDER BY ${previewMessageCreatedAtExpression} DESC LIMIT 1`);
19639
20982
  return rows.map((r) => {
19640
20983
  const participants = memberStmt.all(r.id).map((m) => m.actor_id);
19641
20984
  const agentId = participants.find((p) => p !== "operator") ?? null;
@@ -19741,6 +21084,716 @@ function queryMobileWorkspaces(limit = 50) {
19741
21084
  }
19742
21085
  return results;
19743
21086
  }
21087
+ // ../web/server/scoutbot/directives.ts
21088
+ var ACTIONS = new Set([
21089
+ "help",
21090
+ "agents",
21091
+ "status",
21092
+ "recent",
21093
+ "doing",
21094
+ "flight",
21095
+ "steer"
21096
+ ]);
21097
+ var EFFORT_ALIASES = {
21098
+ none: "none",
21099
+ off: "none",
21100
+ minimal: "minimal",
21101
+ min: "minimal",
21102
+ low: "low",
21103
+ quick: "low",
21104
+ medium: "medium",
21105
+ med: "medium",
21106
+ mid: "medium",
21107
+ high: "high",
21108
+ deep: "high",
21109
+ xhigh: "xhigh",
21110
+ max: "xhigh"
21111
+ };
21112
+ var EFFORT_KEYS = new Set(["eff", "effort", "reasoning", "reasoning-effort", "reasoning_effort"]);
21113
+ var SESSION_KEYS = new Set(["sid", "session", "session-id", "session_id", "target-session", "target_session"]);
21114
+ function parseScoutbotDirectives(input) {
21115
+ const original = input.trim();
21116
+ const rawTokens = original.split(/\s+/).filter(Boolean);
21117
+ const directives = {};
21118
+ const bodyTokens = [];
21119
+ const messageTokens = [];
21120
+ let command = null;
21121
+ let commandIndex = -1;
21122
+ for (let index = 0;index < rawTokens.length; index += 1) {
21123
+ const token = rawTokens[index] ?? "";
21124
+ const directive = parseDirectiveToken(token);
21125
+ if (directive) {
21126
+ if (directive.kind === "reasoningEffort") {
21127
+ directives.reasoningEffort = directive.value;
21128
+ } else {
21129
+ directives.targetSessionId = directive.value;
21130
+ }
21131
+ continue;
21132
+ }
21133
+ messageTokens.push(token);
21134
+ const action = command ? null : parseActionToken(token);
21135
+ if (action) {
21136
+ command = { name: action, raw: token };
21137
+ commandIndex = index;
21138
+ continue;
21139
+ }
21140
+ bodyTokens.push(token);
21141
+ }
21142
+ const args = command ? rawTokens.slice(commandIndex + 1).filter((token) => !parseDirectiveToken(token) && !parseActionToken(token)).join(" ").trim() : "";
21143
+ return {
21144
+ original,
21145
+ body: bodyTokens.join(" ").trim(),
21146
+ messageBody: messageTokens.join(" ").trim(),
21147
+ command: command ? { ...command, args } : null,
21148
+ directives
21149
+ };
21150
+ }
21151
+ function scoutbotDirectiveMetadata(parsed) {
21152
+ const metadata = {};
21153
+ if (parsed.command)
21154
+ metadata.scoutbotAction = parsed.command.name;
21155
+ if (parsed.directives.reasoningEffort)
21156
+ metadata.reasoningEffort = parsed.directives.reasoningEffort;
21157
+ if (parsed.directives.targetSessionId)
21158
+ metadata.targetSessionId = parsed.directives.targetSessionId;
21159
+ if ((parsed.directives.reasoningEffort || parsed.directives.targetSessionId) && parsed.body && parsed.body !== parsed.original) {
21160
+ metadata.directiveBody = parsed.body;
21161
+ }
21162
+ return metadata;
21163
+ }
21164
+ function parseActionToken(rawToken) {
21165
+ const token = trimTokenBoundary(rawToken);
21166
+ const match = token.match(/^\/([A-Za-z][A-Za-z0-9_-]*)$/);
21167
+ if (!match?.[1])
21168
+ return null;
21169
+ const action = match[1].toLowerCase().replace(/-/g, "_");
21170
+ return ACTIONS.has(action) ? action : null;
21171
+ }
21172
+ function parseDirectiveToken(rawToken) {
21173
+ const token = trimTokenBoundary(rawToken);
21174
+ const separator = token.indexOf(":");
21175
+ if (separator <= 0)
21176
+ return null;
21177
+ const key = token.slice(0, separator).trim().toLowerCase();
21178
+ const value = normalizeDirectiveValue(token.slice(separator + 1));
21179
+ if (!value)
21180
+ return null;
21181
+ if (EFFORT_KEYS.has(key)) {
21182
+ const effort = EFFORT_ALIASES[value.toLowerCase()];
21183
+ return effort ? { kind: "reasoningEffort", value: effort } : null;
21184
+ }
21185
+ if (SESSION_KEYS.has(key) && isValidSessionId(value)) {
21186
+ return { kind: "targetSessionId", value };
21187
+ }
21188
+ return null;
21189
+ }
21190
+ function normalizeDirectiveValue(value) {
21191
+ return trimTokenBoundary(value).replace(/^["']+|["']+$/g, "").trim();
21192
+ }
21193
+ function trimTokenBoundary(value) {
21194
+ return value.trim().replace(/^[([{]+/g, "").replace(/[)\]},.!?;]+$/g, "");
21195
+ }
21196
+ function isValidSessionId(value) {
21197
+ return /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value);
21198
+ }
21199
+
21200
+ // ../web/server/scoutbot/role.ts
21201
+ var SCOUTBOT_AGENT_ID = "scoutbot";
21202
+ var SCOUTBOT_DISPLAY_NAME = "Scout";
21203
+ var SCOUTBOT_HANDLE = "scoutbot";
21204
+ var SCOUTBOT_DEFAULT_THREAD_ID = "thr-default";
21205
+ var SCOUTBOT_DEFAULT_THREAD_NAME = "default";
21206
+ var SCOUTBOT_DEFAULT_CONVERSATION_ID = "dm.operator.scoutbot.default";
21207
+ var SCOUTBOT_LEGACY_CONVERSATION_ID = "dm.operator.scoutbot";
21208
+ var SCOUTBOT_ENDPOINT_ID = "endpoint.scoutbot.codex_app_server";
21209
+ var SCOUTBOT_RUNTIME_INSTANCE_ID = "scoutbot-default";
21210
+ var SCOUTBOT_REASONING_EFFORT = "low";
21211
+ var SCOUTBOT_SYSTEM_PROMPT = `# Scoutbot role
21212
+
21213
+ You are Scoutbot, the operator-facing concierge for the local OpenScout fleet.
21214
+
21215
+ Your job is to read broker state, explain what is happening, and perform structured broker operations on the operator's behalf. You do not write code, edit files, or run shell commands. If a task requires project work, ask or dispatch the appropriate project agent instead of doing that work yourself.
21216
+
21217
+ ## Operating loop
21218
+
21219
+ Default to a low-effort triage pass. Your first move is to read the current broker facts and recent thread context, then answer directly if the operator is asking for status, latest activity, who is blocked, what changed, routing, or a next-action recommendation.
21220
+
21221
+ For status questions such as "what's latest on Hudson", answer from broker facts in a few bullets:
21222
+ - current state
21223
+ - most recent activity
21224
+ - blocker or risk
21225
+ - suggested next action
21226
+
21227
+ Do not start a broad investigation, inspect code, or dispatch project work unless the operator asks for that next step.
21228
+
21229
+ ## Inline answers
21230
+
21231
+ Answer inline when the request can be handled from broker state, recent messages, active flights, agent registrations, endpoint state, or known routing metadata. Keep these answers short and explicit. Separate observed facts from inference.
21232
+
21233
+ ## Offloading
21234
+
21235
+ Offload with structured broker operations when the request requires a project agent to inspect files, run commands, reproduce a bug, write code, review a diff, research a repo, operate a UI, or own multi-step work. Pick the agent by explicit routing metadata or by the closest matching project/workspace identity. If the target is ambiguous, ask one clarifying question instead of guessing.
21236
+
21237
+ Use send_message for tells/status nudges. Use ask_agent or dispatch_subagent when the meaning is "own this and report back". Use cancel_flight only for a specific active flight the operator wants stopped.
21238
+
21239
+ ## Parallelism
21240
+
21241
+ Use parallel asks only when the work naturally splits into independent lanes, for example frontend and backend investigation, reproduce and log inspection, docs and implementation review, or several agents owning separate repos. Keep fanout bounded, usually two to four agents. Do not parallelize when agents would edit the same files, depend on a single sequence, or need one owner to make a coherent decision.
21242
+
21243
+ When you fan out, tell the operator who owns each lane and what result you expect back. Prefer an explicit channel for group coordination; use DMs for one-agent work.
21244
+
21245
+ ## Tools and routing
21246
+
21247
+ Use read-only broker tools first: list_agents, list_endpoints, list_flights, latest_messages, and current_turn. For writes, use only structured broker tools: send_message, ask_agent, dispatch_subagent, and cancel_flight. No shell access. No codebase writes.
21248
+
21249
+ Routing must be explicit. Resolve targets before broker writes; do not rely on body mentions as instructions. Every broker write you emit must carry Scoutbot provenance so the operator can audit why it happened.
21250
+
21251
+ Prefer concise operational answers. Use the deterministic broker facts available to you, say when you are inferring, and keep follow-up in the same Scout thread unless the operator explicitly asks otherwise.`;
21252
+ var SCOUTBOT_ROLE_CONFIG = {
21253
+ roleId: "scoutbot",
21254
+ systemPrompt: SCOUTBOT_SYSTEM_PROMPT,
21255
+ grants: {
21256
+ read: [
21257
+ "list_agents",
21258
+ "list_endpoints",
21259
+ "list_flights",
21260
+ "latest_messages",
21261
+ "current_turn"
21262
+ ],
21263
+ write: [
21264
+ "send_message",
21265
+ "ask_agent",
21266
+ "dispatch_subagent",
21267
+ "cancel_flight"
21268
+ ],
21269
+ shell: false,
21270
+ codebaseWrites: false
21271
+ },
21272
+ defaults: {
21273
+ requestedBy: "operator",
21274
+ provenanceSource: "scoutbot",
21275
+ generatedBy: "scoutbot",
21276
+ cwdPolicy: "openscout_control_plane",
21277
+ reasoningEffort: SCOUTBOT_REASONING_EFFORT
21278
+ }
21279
+ };
21280
+
21281
+ // ../web/server/scoutbot/thread-map.ts
21282
+ init_support_paths();
21283
+ import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
21284
+ import { dirname as dirname12, join as join24 } from "path";
21285
+ class ScoutbotThreadMapStore {
21286
+ filePath;
21287
+ constructor(filePath = defaultScoutbotThreadMapPath()) {
21288
+ this.filePath = filePath;
21289
+ }
21290
+ async getThreads() {
21291
+ const map = await this.read();
21292
+ return map.threads.filter((thread) => !thread.archivedAt);
21293
+ }
21294
+ async list() {
21295
+ const map = await this.read();
21296
+ return {
21297
+ threads: map.threads.filter((thread) => !thread.archivedAt),
21298
+ defaultThreadId: map.defaultThreadId
21299
+ };
21300
+ }
21301
+ async getThread(threadId) {
21302
+ const normalized = threadId.trim();
21303
+ if (!normalized)
21304
+ return null;
21305
+ const map = await this.read();
21306
+ return map.threads.find((thread) => thread.threadId === normalized && !thread.archivedAt) ?? null;
21307
+ }
21308
+ async getThreadByConversationId(conversationId) {
21309
+ const normalized = conversationId.trim();
21310
+ if (!normalized)
21311
+ return null;
21312
+ const map = await this.read();
21313
+ return map.threads.find((thread) => thread.conversationId === normalized && !thread.archivedAt) ?? null;
21314
+ }
21315
+ async ensureDefaultThread(options) {
21316
+ const now = options.now ?? Date.now();
21317
+ const map = await this.read();
21318
+ const existing = map.threads.find((thread2) => thread2.threadId === map.defaultThreadId);
21319
+ if (existing) {
21320
+ const next = {
21321
+ ...existing,
21322
+ transportSessionId: options.transportSessionId !== undefined ? normalizeSessionId(options.transportSessionId) : existing.transportSessionId,
21323
+ transport: options.transport ?? existing.transport
21324
+ };
21325
+ if (sameThread(existing, next))
21326
+ return existing;
21327
+ const updated = replaceThread(map, next);
21328
+ await this.write(updated);
21329
+ return next;
21330
+ }
21331
+ const conversationId = chooseDefaultConversationId(options.snapshot);
21332
+ const thread = {
21333
+ threadId: SCOUTBOT_DEFAULT_THREAD_ID,
21334
+ name: SCOUTBOT_DEFAULT_THREAD_NAME,
21335
+ conversationId,
21336
+ transportSessionId: normalizeSessionId(options.transportSessionId),
21337
+ transport: options.transport ?? "codex_app_server",
21338
+ pins: null,
21339
+ lastActiveAt: now
21340
+ };
21341
+ await this.write({
21342
+ version: 1,
21343
+ defaultThreadId: SCOUTBOT_DEFAULT_THREAD_ID,
21344
+ threads: [thread]
21345
+ });
21346
+ return thread;
21347
+ }
21348
+ async createThread(name, opts) {
21349
+ const map = await this.read();
21350
+ const now = opts.now ?? Date.now();
21351
+ const threadId = opts.threadId?.trim() || `thr-${now.toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
21352
+ const conversationId = opts.conversationId?.trim() || `dm.operator.scoutbot.${threadId.replace(/^thr-/, "")}`;
21353
+ const thread = {
21354
+ threadId,
21355
+ name: name.trim() || threadId,
21356
+ conversationId,
21357
+ transportSessionId: normalizeSessionId(opts.transportSessionId),
21358
+ transport: opts.transport ?? "codex_app_server",
21359
+ pins: opts.pins ?? null,
21360
+ lastActiveAt: now
21361
+ };
21362
+ await this.write({
21363
+ ...map,
21364
+ threads: [...map.threads.filter((candidate) => candidate.threadId !== threadId), thread]
21365
+ });
21366
+ return thread;
21367
+ }
21368
+ async archiveThread(threadId) {
21369
+ const map = await this.read();
21370
+ const thread = map.threads.find((candidate) => candidate.threadId === threadId);
21371
+ if (!thread || thread.threadId === map.defaultThreadId)
21372
+ return null;
21373
+ const archived = { ...thread, archivedAt: Date.now() };
21374
+ await this.write(replaceThread(map, archived));
21375
+ return archived;
21376
+ }
21377
+ async touchThread(threadId, now = Date.now()) {
21378
+ const map = await this.read();
21379
+ const thread = map.threads.find((candidate) => candidate.threadId === threadId);
21380
+ if (!thread)
21381
+ return;
21382
+ await this.write(replaceThread(map, { ...thread, lastActiveAt: now }));
21383
+ }
21384
+ async setThreadTransportSessionId(threadId, transportSessionId, now = Date.now()) {
21385
+ const normalized = normalizeSessionId(transportSessionId);
21386
+ const map = await this.read();
21387
+ const thread = map.threads.find((candidate) => candidate.threadId === threadId);
21388
+ if (!thread)
21389
+ return null;
21390
+ const next = { ...thread, transportSessionId: normalized, lastActiveAt: now };
21391
+ if (sameThread(thread, next))
21392
+ return thread;
21393
+ await this.write(replaceThread(map, next));
21394
+ return next;
21395
+ }
21396
+ async read() {
21397
+ try {
21398
+ const raw = await readFile7(this.filePath, "utf8");
21399
+ const parsed = JSON.parse(raw);
21400
+ const threads = Array.isArray(parsed.threads) ? parsed.threads.filter(isThreadRecord).map((thread) => ({
21401
+ ...thread,
21402
+ transportSessionId: normalizeSessionId(thread.transportSessionId)
21403
+ })) : [];
21404
+ return {
21405
+ version: 1,
21406
+ defaultThreadId: typeof parsed.defaultThreadId === "string" && parsed.defaultThreadId.trim() ? parsed.defaultThreadId : SCOUTBOT_DEFAULT_THREAD_ID,
21407
+ threads
21408
+ };
21409
+ } catch (error) {
21410
+ if (error.code === "ENOENT") {
21411
+ return { version: 1, defaultThreadId: SCOUTBOT_DEFAULT_THREAD_ID, threads: [] };
21412
+ }
21413
+ throw error;
21414
+ }
21415
+ }
21416
+ async write(map) {
21417
+ await mkdir7(dirname12(this.filePath), { recursive: true });
21418
+ await writeFile7(this.filePath, `${JSON.stringify(map, null, 2)}
21419
+ `, "utf8");
21420
+ }
21421
+ }
21422
+ function defaultScoutbotThreadMapPath() {
21423
+ return join24(resolveOpenScoutSupportPaths().supportDirectory, "scoutbot-threads.json");
21424
+ }
21425
+ function chooseDefaultConversationId(snapshot) {
21426
+ return snapshot?.conversations?.[SCOUTBOT_LEGACY_CONVERSATION_ID] ? SCOUTBOT_LEGACY_CONVERSATION_ID : SCOUTBOT_DEFAULT_CONVERSATION_ID;
21427
+ }
21428
+ function buildScoutbotThreadConversation(thread, snapshot, nodeId) {
21429
+ const existing = snapshot.conversations[thread.conversationId];
21430
+ if (existing)
21431
+ return null;
21432
+ const participantIds = ["operator", "scoutbot"].sort();
21433
+ return {
21434
+ id: thread.conversationId,
21435
+ kind: "direct",
21436
+ title: thread.name === SCOUTBOT_DEFAULT_THREAD_NAME ? "Scout \xB7 default" : `Scout \xB7 ${thread.name}`,
21437
+ visibility: "private",
21438
+ shareMode: "local",
21439
+ authorityNodeId: nodeId,
21440
+ participantIds,
21441
+ metadata: {
21442
+ surface: "scoutbot",
21443
+ scoutbotThreadId: thread.threadId,
21444
+ transportSessionId: thread.transportSessionId,
21445
+ transport: thread.transport
21446
+ }
21447
+ };
21448
+ }
21449
+ function isThreadRecord(value) {
21450
+ if (!value || typeof value !== "object")
21451
+ return false;
21452
+ const record = value;
21453
+ return typeof record.threadId === "string" && typeof record.name === "string" && typeof record.conversationId === "string" && (typeof record.transportSessionId === "string" || record.transportSessionId === null || record.transportSessionId === undefined) && typeof record.transport === "string" && typeof record.lastActiveAt === "number";
21454
+ }
21455
+ function normalizeSessionId(value) {
21456
+ const normalized = value?.trim();
21457
+ return normalized ? normalized : null;
21458
+ }
21459
+ function replaceThread(map, thread) {
21460
+ return {
21461
+ ...map,
21462
+ threads: map.threads.map((candidate) => candidate.threadId === thread.threadId ? thread : candidate)
21463
+ };
21464
+ }
21465
+ function sameThread(left, right) {
21466
+ return JSON.stringify(left) === JSON.stringify(right);
21467
+ }
21468
+
21469
+ // ../web/server/scoutbot/runner.ts
21470
+ var defaultLog = {
21471
+ info: (msg) => {
21472
+ console.log(`[scoutbot] ${msg}`);
21473
+ },
21474
+ warn: (msg) => {
21475
+ console.warn(`[scoutbot] ${msg}`);
21476
+ }
21477
+ };
21478
+ async function postScoutbotOperatorMessage(input) {
21479
+ const log2 = input.log ?? defaultLog;
21480
+ const baseUrl = input.brokerBaseUrl ?? resolveScoutBrokerUrl();
21481
+ const threadMap = input.threadMap ?? new ScoutbotThreadMapStore;
21482
+ if (input.bootstrap) {
21483
+ const prepared = await ensureScoutbotBootstrapped({
21484
+ baseUrl,
21485
+ currentDirectory: input.currentDirectory,
21486
+ threadMap,
21487
+ log: log2
21488
+ });
21489
+ if (!prepared) {
21490
+ return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
21491
+ }
21492
+ }
21493
+ const thread = await resolvePostTargetThread({
21494
+ baseUrl,
21495
+ threadMap,
21496
+ threadId: input.threadId
21497
+ });
21498
+ if (!thread) {
21499
+ if (input.threadId?.trim()) {
21500
+ throw new Error(`Unknown scoutbot thread ${input.threadId}`);
21501
+ }
21502
+ return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
21503
+ }
21504
+ if (input.threadId?.trim() && thread.threadId !== input.threadId.trim()) {
21505
+ throw new Error(`Unknown scoutbot thread ${input.threadId}`);
21506
+ }
21507
+ return postOperatorThreadMessage({
21508
+ baseUrl,
21509
+ thread,
21510
+ body: input.body,
21511
+ source: input.source ?? "scout-web",
21512
+ clientMessageId: input.clientMessageId,
21513
+ replyToMessageId: input.replyToMessageId,
21514
+ referenceMessageIds: input.referenceMessageIds,
21515
+ deviceId: input.deviceId,
21516
+ log: log2
21517
+ });
21518
+ }
21519
+ async function resolvePostTargetThread(input) {
21520
+ const explicitThreadId = input.threadId?.trim();
21521
+ if (explicitThreadId) {
21522
+ return input.threadMap.getThread(explicitThreadId);
21523
+ }
21524
+ const threadList = await input.threadMap.list();
21525
+ const existing = threadList.threads.find((candidate) => candidate.threadId === threadList.defaultThreadId) ?? null;
21526
+ if (existing)
21527
+ return existing;
21528
+ const ctx = await loadScoutBrokerContext(input.baseUrl);
21529
+ if (!ctx)
21530
+ return null;
21531
+ return input.threadMap.ensureDefaultThread({
21532
+ snapshot: ctx.snapshot,
21533
+ transportSessionId: null,
21534
+ transport: "codex_app_server"
21535
+ });
21536
+ }
21537
+ async function ensureScoutbotBootstrapped(input) {
21538
+ const ctx = await loadScoutBrokerContext(input.baseUrl);
21539
+ if (!ctx) {
21540
+ input.log.warn(`broker unreachable at ${input.baseUrl}; runner is inert`);
21541
+ return false;
21542
+ }
21543
+ await ensureScoutbotRegistered(input.baseUrl, ctx.snapshot, ctx.node.id, input.currentDirectory, input.log);
21544
+ const refreshed = await loadScoutBrokerContext(input.baseUrl);
21545
+ const snapshot = refreshed?.snapshot ?? ctx.snapshot;
21546
+ const nodeId = refreshed?.node.id ?? ctx.node.id;
21547
+ const endpoint = findScoutbotEndpoint(snapshot, nodeId) ?? buildScoutbotEndpoint(nodeId, input.currentDirectory);
21548
+ const warmedEndpoint = await ensureScoutbotEndpointSession(input.baseUrl, endpoint, input.log);
21549
+ const transportSessionId = scoutbotEndpointTransportSessionId(warmedEndpoint);
21550
+ const thread = await input.threadMap.ensureDefaultThread({
21551
+ snapshot,
21552
+ transportSessionId,
21553
+ transport: endpoint.transport ?? "codex_app_server"
21554
+ });
21555
+ const latest = await loadScoutBrokerContext(input.baseUrl);
21556
+ const conversation = buildScoutbotThreadConversation(thread, latest?.snapshot ?? snapshot, latest?.node.id ?? nodeId);
21557
+ if (conversation) {
21558
+ await postJson(input.baseUrl, "/v1/conversations", conversation);
21559
+ }
21560
+ return true;
21561
+ }
21562
+ async function ensureScoutbotRegistered(baseUrl, snapshot, nodeId, currentDirectory, log2) {
21563
+ const actor = buildScoutbotActor(nodeId);
21564
+ const agent = buildScoutbotAgent(nodeId);
21565
+ const endpoint = normalizeScoutbotEndpoint(findScoutbotEndpoint(snapshot, nodeId), buildScoutbotEndpoint(nodeId, currentDirectory));
21566
+ if (!snapshot.actors?.[SCOUTBOT_AGENT_ID]) {
21567
+ await postJson(baseUrl, "/v1/actors", actor);
21568
+ }
21569
+ const existingAgent = snapshot.agents?.[SCOUTBOT_AGENT_ID];
21570
+ if (!existingAgent || !hasCurrentScoutbotAgentRegistration(existingAgent, nodeId)) {
21571
+ await postJson(baseUrl, "/v1/agents", agent);
21572
+ }
21573
+ const existingEndpoint = findScoutbotEndpoint(snapshot, nodeId);
21574
+ if (!existingEndpoint || existingEndpoint.state === "offline" || existingEndpoint.metadata?.source !== "scoutbot" || !hasCurrentScoutbotRuntimeConfig(existingEndpoint) || isInvalidScoutbotSessionEndpoint(existingEndpoint)) {
21575
+ await postJson(baseUrl, "/v1/endpoints", endpoint);
21576
+ log2.info(`registered endpoint ${endpoint.id}`);
21577
+ }
21578
+ }
21579
+ function buildScoutbotActor(_nodeId) {
21580
+ return {
21581
+ id: SCOUTBOT_AGENT_ID,
21582
+ kind: "agent",
21583
+ displayName: SCOUTBOT_DISPLAY_NAME,
21584
+ handle: SCOUTBOT_HANDLE,
21585
+ labels: ["assistant", "scout", "scoutbot"],
21586
+ metadata: {
21587
+ source: "scoutbot",
21588
+ role: "operator-assistant"
21589
+ }
21590
+ };
21591
+ }
21592
+ function buildScoutbotAgent(nodeId) {
21593
+ return {
21594
+ ...buildScoutbotActor(nodeId),
21595
+ kind: "agent",
21596
+ definitionId: SCOUTBOT_HANDLE,
21597
+ selector: `@${SCOUTBOT_HANDLE}`,
21598
+ defaultSelector: `@${SCOUTBOT_HANDLE}`,
21599
+ agentClass: "operator",
21600
+ capabilities: ["chat", "invoke", "deliver"],
21601
+ wakePolicy: "keep_warm",
21602
+ homeNodeId: nodeId,
21603
+ authorityNodeId: nodeId,
21604
+ advertiseScope: "local",
21605
+ metadata: {
21606
+ source: "scoutbot",
21607
+ role: "operator-assistant",
21608
+ summary: "Operator-facing fleet concierge backed by the broker's local session adapter.",
21609
+ roleConfig: SCOUTBOT_ROLE_CONFIG
21610
+ }
21611
+ };
21612
+ }
21613
+ function buildScoutbotEndpoint(nodeId, currentDirectory) {
21614
+ const reasoningEffort = SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
21615
+ return {
21616
+ id: `${SCOUTBOT_ENDPOINT_ID}.${nodeId}`,
21617
+ agentId: SCOUTBOT_AGENT_ID,
21618
+ nodeId,
21619
+ harness: "codex",
21620
+ transport: "codex_app_server",
21621
+ state: "waiting",
21622
+ cwd: currentDirectory,
21623
+ projectRoot: currentDirectory,
21624
+ metadata: {
21625
+ source: "scoutbot",
21626
+ managedByScout: true,
21627
+ sessionBacked: true,
21628
+ externalSource: "local-session",
21629
+ attachedTransport: "codex_app_server",
21630
+ agentName: SCOUTBOT_AGENT_ID,
21631
+ definitionId: SCOUTBOT_HANDLE,
21632
+ runtimeInstanceId: SCOUTBOT_RUNTIME_INSTANCE_ID,
21633
+ roleConfig: SCOUTBOT_ROLE_CONFIG,
21634
+ systemPrompt: SCOUTBOT_ROLE_CONFIG.systemPrompt,
21635
+ toolGrants: SCOUTBOT_ROLE_CONFIG.grants,
21636
+ reasoningEffort,
21637
+ launchArgs: ["--reasoning-effort", reasoningEffort],
21638
+ projectRoot: currentDirectory,
21639
+ transport: "codex_app_server",
21640
+ startedAt: String(Date.now())
21641
+ }
21642
+ };
21643
+ }
21644
+ function findScoutbotEndpoint(snapshot, nodeId) {
21645
+ const endpoints = Object.values(snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === SCOUTBOT_AGENT_ID && endpoint.transport === "codex_app_server");
21646
+ return endpoints.find((endpoint) => endpoint.nodeId === nodeId && endpoint.state !== "offline") ?? endpoints.find((endpoint) => endpoint.state !== "offline") ?? endpoints[0] ?? null;
21647
+ }
21648
+ function normalizeScoutbotEndpoint(existing, fallback) {
21649
+ if (!existing || isInvalidScoutbotSessionEndpoint(existing))
21650
+ return fallback;
21651
+ const existingMetadata = { ...existing.metadata ?? {} };
21652
+ delete existingMetadata.threadId;
21653
+ delete existingMetadata.externalSessionId;
21654
+ delete existingMetadata.targetSessionId;
21655
+ return {
21656
+ ...fallback,
21657
+ id: existing.id || fallback.id,
21658
+ state: existing.state === "offline" ? "waiting" : existing.state,
21659
+ metadata: {
21660
+ ...existingMetadata,
21661
+ ...fallback.metadata ?? {}
21662
+ }
21663
+ };
21664
+ }
21665
+ function isInvalidScoutbotSessionEndpoint(endpoint) {
21666
+ const lastError = String(endpoint.metadata?.lastError ?? "").toLowerCase();
21667
+ return lastError.includes("no rollout found for thread id") || lastError.includes("codex_app_server session unavailable") || lastError.includes("session unavailable");
21668
+ }
21669
+ function hasCurrentScoutbotRuntimeConfig(endpoint) {
21670
+ return metadataString5(endpoint.metadata, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt && metadataString5(endpoint.metadata, "reasoningEffort") === SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
21671
+ }
21672
+ function scoutbotEndpointTransportSessionId(endpoint) {
21673
+ if (isInvalidScoutbotSessionEndpoint(endpoint))
21674
+ return null;
21675
+ return metadataString5(endpoint.metadata, "threadId") ?? metadataString5(endpoint.metadata, "externalSessionId") ?? endpoint.sessionId?.trim() ?? null;
21676
+ }
21677
+ async function ensureScoutbotEndpointSession(baseUrl, endpoint, log2) {
21678
+ try {
21679
+ const result = await postJson(baseUrl, "/v1/local-sessions/ensure", { agentId: endpoint.agentId, endpointId: endpoint.id }, { timeoutMs: 2000 });
21680
+ return result.endpoint ?? endpoint;
21681
+ } catch (error) {
21682
+ log2.warn(`session warmup skipped: ${describeError(error)}`);
21683
+ return endpoint;
21684
+ }
21685
+ }
21686
+ async function postOperatorThreadMessage(input) {
21687
+ const ctx = await loadScoutBrokerContext(input.baseUrl);
21688
+ if (!ctx) {
21689
+ return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
21690
+ }
21691
+ const conversation = ctx.snapshot.conversations[input.thread.conversationId] ?? buildScoutbotThreadConversation(input.thread, ctx.snapshot, ctx.node.id);
21692
+ if (conversation && !ctx.snapshot.conversations[input.thread.conversationId]) {
21693
+ await postJson(input.baseUrl, "/v1/conversations", conversation);
21694
+ }
21695
+ const parsed = parseScoutbotDirectives(input.body);
21696
+ const now = Date.now();
21697
+ const messageId = createId("msg");
21698
+ const transportSessionId = parsed.directives.targetSessionId ?? input.thread.transportSessionId?.trim() ?? null;
21699
+ await postJson(input.baseUrl, "/v1/messages", {
21700
+ id: messageId,
21701
+ conversationId: input.thread.conversationId,
21702
+ actorId: "operator",
21703
+ originNodeId: ctx.node.id,
21704
+ class: "agent",
21705
+ body: input.body.trim(),
21706
+ replyToMessageId: input.replyToMessageId ?? undefined,
21707
+ mentions: [{ actorId: SCOUTBOT_AGENT_ID, label: "@scoutbot" }],
21708
+ audience: { notify: [SCOUTBOT_AGENT_ID], reason: "direct_message" },
21709
+ visibility: "private",
21710
+ policy: "durable",
21711
+ createdAt: now,
21712
+ metadata: {
21713
+ source: input.source,
21714
+ ...scoutbotDirectiveMetadata(parsed),
21715
+ destinationKind: "scoutbot_thread",
21716
+ destinationId: input.thread.threadId,
21717
+ scoutbotThreadId: input.thread.threadId,
21718
+ ...transportSessionId ? { targetSessionId: transportSessionId } : {},
21719
+ referenceMessageIds: input.referenceMessageIds ?? [],
21720
+ clientMessageId: input.clientMessageId ?? null,
21721
+ ...input.deviceId ? { deviceId: input.deviceId } : {},
21722
+ relayMessageId: messageId,
21723
+ returnAddress: {
21724
+ actorId: "operator",
21725
+ conversationId: input.thread.conversationId,
21726
+ replyToMessageId: messageId,
21727
+ ...transportSessionId ? { sessionId: transportSessionId } : {}
21728
+ }
21729
+ }
21730
+ });
21731
+ return {
21732
+ usedBroker: true,
21733
+ threadId: input.thread.threadId,
21734
+ conversationId: input.thread.conversationId,
21735
+ messageId,
21736
+ invokedTargets: [SCOUTBOT_AGENT_ID],
21737
+ unresolvedTargets: []
21738
+ };
21739
+ }
21740
+ var TERMINAL_FLIGHT_STATES2 = new Set(["completed", "failed", "cancelled"]);
21741
+ async function postJson(baseUrl, path2, body, options = {}) {
21742
+ const controller = options.timeoutMs ? new AbortController : null;
21743
+ const timer = controller && options.timeoutMs ? setTimeout(() => controller.abort(), options.timeoutMs) : null;
21744
+ try {
21745
+ const res = await fetch(new URL(path2, baseUrl), {
21746
+ method: "POST",
21747
+ headers: { "content-type": "application/json" },
21748
+ body: JSON.stringify(body),
21749
+ ...controller ? { signal: controller.signal } : {}
21750
+ });
21751
+ if (!res.ok) {
21752
+ const text = await res.text().catch(() => "");
21753
+ throw new Error(`POST ${path2} ${res.status}${text ? ` - ${text}` : ""}`);
21754
+ }
21755
+ return await res.json().catch(() => ({ ok: true }));
21756
+ } finally {
21757
+ if (timer)
21758
+ clearTimeout(timer);
21759
+ }
21760
+ }
21761
+ function hasScoutbotLabels(agent) {
21762
+ const labels = new Set(agent.labels ?? []);
21763
+ return labels.has("assistant") && labels.has("scout") && labels.has("scoutbot");
21764
+ }
21765
+ function hasCurrentScoutbotAgentRegistration(agent, nodeId) {
21766
+ return hasScoutbotLabels(agent) && hasCurrentScoutbotAgentConfig(agent) && agent.homeNodeId === nodeId && agent.authorityNodeId === nodeId && agent.advertiseScope === "local" && agent.wakePolicy === "keep_warm" && agent.metadata?.source === "scoutbot";
21767
+ }
21768
+ function hasCurrentScoutbotAgentConfig(agent) {
21769
+ const roleConfig = metadataObject(agent.metadata, "roleConfig");
21770
+ return metadataString5(roleConfig, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt;
21771
+ }
21772
+ function metadataObject(metadata, key) {
21773
+ const value = metadata?.[key];
21774
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
21775
+ }
21776
+ function metadataString5(metadata, key) {
21777
+ const value = metadata?.[key];
21778
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
21779
+ }
21780
+ var SCOUTBOT_REASONING_EFFORTS = new Set([
21781
+ "none",
21782
+ "minimal",
21783
+ "low",
21784
+ "medium",
21785
+ "high",
21786
+ "xhigh"
21787
+ ]);
21788
+ function createId(prefix) {
21789
+ return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
21790
+ }
21791
+ function describeError(cause) {
21792
+ if (cause instanceof Error)
21793
+ return cause.message;
21794
+ return String(cause);
21795
+ }
21796
+
19744
21797
  // ../web/server/core/mobile/service.ts
19745
21798
  var DEFAULT_MOBILE_RECENT_TURN_LIMIT = 24;
19746
21799
  var DEFAULT_MOBILE_HISTORY_PAGE_LIMIT = 40;
@@ -19754,10 +21807,31 @@ function normalizeTimestampMs2(value) {
19754
21807
  return null;
19755
21808
  return value > 10000000000 ? Math.floor(value) : Math.floor(value * 1000);
19756
21809
  }
19757
- function metadataString5(metadata, key) {
21810
+ function metadataString6(metadata, key) {
19758
21811
  const value = metadata?.[key];
19759
21812
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
19760
21813
  }
21814
+ function metadataBoolean2(metadata, key) {
21815
+ return metadata?.[key] === true;
21816
+ }
21817
+ function isBrokerRequesterWaitTimeoutStatusMessage(message) {
21818
+ if (message.class !== "status" || metadataString6(message.metadata, "source") !== "broker") {
21819
+ return false;
21820
+ }
21821
+ return message.body.includes("Scout stopped waiting for a synchronous result") || message.body.includes("the requester stopped waiting after");
21822
+ }
21823
+ function isRequesterWaitTimeoutFlight(flight) {
21824
+ return metadataBoolean2(flight.metadata, "requesterTimedOut") || metadataString6(flight.metadata, "timeoutScope") === "requester_wait" || Boolean(flight.summary?.includes("Scout stopped waiting for a synchronous result"));
21825
+ }
21826
+ function isInactiveAgent(agent) {
21827
+ return metadataBoolean2(agent?.metadata, "retiredFromFleet") || metadataBoolean2(agent?.metadata, "staleLocalRegistration");
21828
+ }
21829
+ function isInactiveEndpoint(snapshot, endpoint) {
21830
+ if (!endpoint) {
21831
+ return true;
21832
+ }
21833
+ return metadataBoolean2(endpoint.metadata, "retiredFromFleet") || metadataBoolean2(endpoint.metadata, "staleLocalRegistration") || isInactiveAgent(snapshot.agents[endpoint.agentId]);
21834
+ }
19761
21835
  function titleCaseToken(value) {
19762
21836
  return value.length === 0 ? value : `${value[0].toUpperCase()}${value.slice(1)}`;
19763
21837
  }
@@ -19812,6 +21886,9 @@ async function loadMobileWorkspaceInventory(currentDirectory) {
19812
21886
  function latestMessageByConversation(snapshot) {
19813
21887
  const buckets = new Map;
19814
21888
  for (const message of Object.values(snapshot.messages)) {
21889
+ if (isBrokerRequesterWaitTimeoutStatusMessage(message)) {
21890
+ continue;
21891
+ }
19815
21892
  const next = buckets.get(message.conversationId) ?? [];
19816
21893
  next.push(message);
19817
21894
  buckets.set(message.conversationId, next);
@@ -19830,14 +21907,14 @@ function agentDisplayName(snapshot, agentId) {
19830
21907
  return snapshot.agents[agentId]?.displayName ?? snapshot.actors[agentId]?.displayName ?? agentId;
19831
21908
  }
19832
21909
  function endpointForAgent(snapshot, agentId) {
19833
- return Object.values(snapshot.endpoints).find((endpoint) => endpoint.agentId === agentId) ?? null;
21910
+ return Object.values(snapshot.endpoints).find((endpoint) => endpoint.agentId === agentId && !isInactiveEndpoint(snapshot, endpoint)) ?? null;
19834
21911
  }
19835
21912
  function buildMobileAgentSummary(snapshot, agent) {
19836
21913
  const endpoint = endpointForAgent(snapshot, agent.id);
19837
21914
  const flights = Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agent.id);
19838
21915
  const hasWorkingFlight = flights.some((flight) => flight.state === "running");
19839
21916
  const lastAuthoredMessageAt = Object.values(snapshot.messages).filter((message) => message.actorId === agent.id).reduce((latest, message) => {
19840
- const createdAt = normalizeTimestamp2(message.createdAt);
21917
+ const createdAt = normalizeTimestampMs2(message.createdAt);
19841
21918
  return typeof createdAt === "number" && (!latest || createdAt > latest) ? createdAt : latest;
19842
21919
  }, null);
19843
21920
  const state = hasWorkingFlight ? "working" : endpoint && endpoint.state !== "offline" ? "available" : "offline";
@@ -19862,7 +21939,7 @@ function buildMobileSessionSummaries(snapshot) {
19862
21939
  const latestMessage = messages2.at(-1) ?? null;
19863
21940
  const directAgentId = conversation.kind === "direct" ? conversation.participantIds.find((participantId) => participantId !== "operator") ?? null : null;
19864
21941
  const agent = directAgentId ? snapshot.agents[directAgentId] ?? null : null;
19865
- if (!directAgentId || !agent || messages2.length === 0) {
21942
+ if (!directAgentId || !agent || isInactiveAgent(agent) || messages2.length === 0) {
19866
21943
  return [];
19867
21944
  }
19868
21945
  const endpoint = endpointForAgent(snapshot, directAgentId);
@@ -19877,10 +21954,10 @@ function buildMobileSessionSummaries(snapshot) {
19877
21954
  agentId: directAgentId,
19878
21955
  agentName: directAgentId ? agentDisplayName(snapshot, directAgentId) : null,
19879
21956
  harness: endpoint?.harness ?? null,
19880
- currentBranch: metadataString5(endpoint?.metadata, "branch") ?? metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "branch") ?? metadataString5(agent?.metadata, "workspaceQualifier"),
21957
+ currentBranch: metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier"),
19881
21958
  preview: latestMessage?.body ?? null,
19882
21959
  messageCount: messages2.length,
19883
- lastMessageAt: normalizeTimestamp2(latestMessage?.createdAt),
21960
+ lastMessageAt: normalizeTimestampMs2(latestMessage?.createdAt),
19884
21961
  workspaceRoot: endpoint?.projectRoot ?? endpoint?.cwd ?? null
19885
21962
  }];
19886
21963
  });
@@ -19888,7 +21965,7 @@ function buildMobileSessionSummaries(snapshot) {
19888
21965
  for (const summary of summaries) {
19889
21966
  const agent = summary.agentId ? snapshot.agents[summary.agentId] : null;
19890
21967
  const endpoint = summary.agentId ? endpointForAgent(snapshot, summary.agentId) : null;
19891
- const branchQualifier = metadataString5(endpoint?.metadata, "branch") ?? metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "branch") ?? metadataString5(agent?.metadata, "workspaceQualifier");
21968
+ const branchQualifier = metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier");
19892
21969
  const identityKey = [
19893
21970
  endpoint?.projectRoot?.trim().toLowerCase(),
19894
21971
  endpoint?.cwd?.trim().toLowerCase(),
@@ -19904,7 +21981,7 @@ function buildMobileSessionSummaries(snapshot) {
19904
21981
  return [...deduped.values()].sort((left, right) => (right.lastMessageAt ?? 0) - (left.lastMessageAt ?? 0));
19905
21982
  }
19906
21983
  function messagesForConversation(snapshot, conversationId) {
19907
- return Object.values(snapshot.messages).filter((message) => message.conversationId === conversationId).sort((left, right) => (normalizeTimestamp2(left.createdAt) ?? 0) - (normalizeTimestamp2(right.createdAt) ?? 0));
21984
+ return Object.values(snapshot.messages).filter((message) => !isBrokerRequesterWaitTimeoutStatusMessage(message)).filter((message) => message.conversationId === conversationId).sort((left, right) => (normalizeTimestamp2(left.createdAt) ?? 0) - (normalizeTimestamp2(right.createdAt) ?? 0));
19908
21985
  }
19909
21986
  function pageMessagesForConversation(snapshot, conversationId, options = {}) {
19910
21987
  const allMessages = messagesForConversation(snapshot, conversationId);
@@ -19941,7 +22018,7 @@ function pageMessagesForConversation(snapshot, conversationId, options = {}) {
19941
22018
  function latestActiveFlightForAgent(snapshot, agentId) {
19942
22019
  if (!agentId)
19943
22020
  return null;
19944
- return Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agentId && !["completed", "failed", "cancelled"].includes(flight.state)).sort((left, right) => (normalizeTimestamp2(right.startedAt) ?? 0) - (normalizeTimestamp2(left.startedAt) ?? 0))[0] ?? null;
22021
+ return Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agentId && (flight.state === "running" || flight.state === "waiting") && !isRequesterWaitTimeoutFlight(flight)).sort((left, right) => (normalizeTimestamp2(right.startedAt) ?? 0) - (normalizeTimestamp2(left.startedAt) ?? 0))[0] ?? null;
19945
22022
  }
19946
22023
  async function loadMobileRelayState() {
19947
22024
  const broker = await loadScoutBrokerContext();
@@ -19949,7 +22026,10 @@ async function loadMobileRelayState() {
19949
22026
  return { agents: [], sessions: [] };
19950
22027
  }
19951
22028
  const snapshot = broker.snapshot;
19952
- const agents = Object.values(snapshot.agents).map((agent) => buildMobileAgentSummary(snapshot, agent)).sort((left, right) => (right.lastActiveAt ?? 0) - (left.lastActiveAt ?? 0) || left.title.localeCompare(right.title));
22029
+ const agents = Object.values(snapshot.agents).filter((agent) => !isInactiveAgent(agent)).filter((agent) => {
22030
+ const endpoints = Object.values(snapshot.endpoints).filter((endpoint) => endpoint.agentId === agent.id);
22031
+ return endpoints.length === 0 || endpoints.some((endpoint) => !isInactiveEndpoint(snapshot, endpoint));
22032
+ }).map((agent) => buildMobileAgentSummary(snapshot, agent)).sort((left, right) => (right.lastActiveAt ?? 0) - (left.lastActiveAt ?? 0) || left.title.localeCompare(right.title));
19953
22033
  return {
19954
22034
  agents,
19955
22035
  sessions: buildMobileSessionSummaries(snapshot)
@@ -20028,10 +22108,10 @@ async function getScoutMobileSessionSnapshot(conversationId, options = {}, curre
20028
22108
  const messages2 = messagePage.messages;
20029
22109
  const activeFlight = latestActiveFlightForAgent(snapshot, directAgentId);
20030
22110
  const lastAgentMessageAt = messages2.filter((message) => message.actorId === directAgentId).reduce((latest, message) => {
20031
- const createdAt = normalizeTimestamp2(message.createdAt);
22111
+ const createdAt = normalizeTimestampMs2(message.createdAt);
20032
22112
  return typeof createdAt === "number" && (!latest || createdAt > latest) ? createdAt : latest;
20033
22113
  }, null);
20034
- const shouldShowWorkingTurn = Boolean(activeFlight && (normalizeTimestamp2(activeFlight.startedAt) ?? 0) > (lastAgentMessageAt ?? 0));
22114
+ const shouldShowWorkingTurn = Boolean(activeFlight && (normalizeTimestampMs2(activeFlight.startedAt) ?? 0) > (lastAgentMessageAt ?? 0));
20035
22115
  const turns = messages2.map((message) => ({
20036
22116
  id: message.id,
20037
22117
  status: "completed",
@@ -20086,8 +22166,8 @@ async function getScoutMobileSessionSnapshot(conversationId, options = {}, curre
20086
22166
  selector: agent?.selector ?? null,
20087
22167
  defaultSelector: agent?.defaultSelector ?? null,
20088
22168
  project: directAgentId ? agentDisplayName(snapshot, directAgentId) : conversation.title,
20089
- currentBranch: metadataString5(endpoint?.metadata, "branch") ?? metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "branch") ?? metadataString5(agent?.metadata, "workspaceQualifier"),
20090
- workspaceQualifier: metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "workspaceQualifier")
22169
+ currentBranch: metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier"),
22170
+ workspaceQualifier: metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "workspaceQualifier")
20091
22171
  }
20092
22172
  },
20093
22173
  history: {
@@ -20105,7 +22185,7 @@ async function createScoutSession(input, currentDirectory, deviceId) {
20105
22185
  throw new Error(`Invalid workspaceId.`);
20106
22186
  }
20107
22187
  const workspaceRoot = resolve7(rawWorkspaceId);
20108
- const projectName = basename9(workspaceRoot) || workspaceRoot;
22188
+ const projectName = basename10(workspaceRoot) || workspaceRoot;
20109
22189
  const workspace = {
20110
22190
  id: workspaceRoot,
20111
22191
  title: projectName,
@@ -20180,6 +22260,24 @@ async function createScoutSession(input, currentDirectory, deviceId) {
20180
22260
  };
20181
22261
  }
20182
22262
  async function sendScoutMobileMessage(input, currentDirectory, deviceId) {
22263
+ if (input.agentId === SCOUTBOT_AGENT_ID) {
22264
+ const result = await postScoutbotOperatorMessage({
22265
+ currentDirectory: currentDirectory ?? process.cwd(),
22266
+ body: input.body,
22267
+ source: "scout-mobile",
22268
+ clientMessageId: input.clientMessageId,
22269
+ replyToMessageId: input.replyToMessageId,
22270
+ referenceMessageIds: input.referenceMessageIds,
22271
+ deviceId
22272
+ });
22273
+ if (!result.usedBroker || !result.conversationId || !result.messageId) {
22274
+ throw new Error("Scoutbot is not available.");
22275
+ }
22276
+ return {
22277
+ conversationId: result.conversationId,
22278
+ messageId: result.messageId
22279
+ };
22280
+ }
20183
22281
  return sendScoutDirectMessage({
20184
22282
  agentId: input.agentId,
20185
22283
  body: input.body,
@@ -20216,8 +22314,8 @@ async function deriveNewAgentName(projectName, branch, harness) {
20216
22314
  }
20217
22315
  async function createGitWorktree(projectRoot, agentName, requestedBranch) {
20218
22316
  const { execFileSync: execFileSync5 } = await import("child_process");
20219
- const { join: join23 } = await import("path");
20220
- const { mkdirSync: mkdirSync10, existsSync: existsSync18 } = await import("fs");
22317
+ const { join: join25 } = await import("path");
22318
+ const { mkdirSync: mkdirSync11, existsSync: existsSync19 } = await import("fs");
20221
22319
  try {
20222
22320
  execFileSync5("git", ["rev-parse", "--git-dir"], { cwd: projectRoot, stdio: "pipe" });
20223
22321
  } catch {
@@ -20225,12 +22323,12 @@ async function createGitWorktree(projectRoot, agentName, requestedBranch) {
20225
22323
  }
20226
22324
  const normalizedRequestedBranch = requestedBranch?.trim();
20227
22325
  const branchName = normalizedRequestedBranch || `scout/${agentName}`;
20228
- const worktreeDir = join23(projectRoot, ".scout-worktrees");
20229
- const worktreePath = join23(worktreeDir, agentName);
20230
- if (existsSync18(worktreePath)) {
22326
+ const worktreeDir = join25(projectRoot, ".scout-worktrees");
22327
+ const worktreePath = join25(worktreeDir, agentName);
22328
+ if (existsSync19(worktreePath)) {
20231
22329
  return { path: worktreePath, branch: branchName };
20232
22330
  }
20233
- mkdirSync10(worktreeDir, { recursive: true });
22331
+ mkdirSync11(worktreeDir, { recursive: true });
20234
22332
  try {
20235
22333
  execFileSync5("git", ["worktree", "add", "-b", branchName, worktreePath], { cwd: projectRoot, stdio: "pipe" });
20236
22334
  return { path: worktreePath, branch: branchName };
@@ -20431,9 +22529,9 @@ async function handleRPCInner(bridge, req, deviceId) {
20431
22529
  }
20432
22530
  case "session/resume": {
20433
22531
  const p = req.params;
20434
- const sessionFilename = basename10(p.sessionPath, ".jsonl");
22532
+ const sessionFilename = basename11(p.sessionPath, ".jsonl");
20435
22533
  const parentDir = p.sessionPath.substring(0, p.sessionPath.lastIndexOf("/"));
20436
- const dirName = basename10(parentDir);
22534
+ const dirName = basename11(parentDir);
20437
22535
  let cwd;
20438
22536
  if (dirName.startsWith("-")) {
20439
22537
  const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
@@ -20500,7 +22598,7 @@ async function handleRPCInner(bridge, req, deviceId) {
20500
22598
  return { id: req.id, error: { code: -32000, message: "Workspace target is not a directory" } };
20501
22599
  }
20502
22600
  const adapterType = p.adapter ?? "claude-code";
20503
- const name = p.name ?? basename10(projectPath);
22601
+ const name = p.name ?? basename11(projectPath);
20504
22602
  const session = await bridge.createSession(adapterType, {
20505
22603
  name,
20506
22604
  cwd: projectPath
@@ -20622,7 +22720,7 @@ async function handleRPCInner(bridge, req, deviceId) {
20622
22720
  return { id: req.id, error: { code: -32000, message: "Only .jsonl files can be read" } };
20623
22721
  }
20624
22722
  try {
20625
- const content = readFileSync12(p.path, "utf-8");
22723
+ const content = readFileSync13(p.path, "utf-8");
20626
22724
  const lines = content.split(`
20627
22725
  `).filter((l) => l.trim().length > 0);
20628
22726
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -20740,13 +22838,13 @@ function resolveMobileCurrentDirectory() {
20740
22838
  }
20741
22839
  }
20742
22840
  function resolveWorkspaceRoot(root) {
20743
- const expandedRoot = root.replace(/^~/, homedir21());
22841
+ const expandedRoot = root.replace(/^~/, homedir22());
20744
22842
  return realpathSync(expandedRoot);
20745
22843
  }
20746
22844
  function resolveWorkspacePath(root, requestedPath) {
20747
22845
  const normalizedRoot = resolveWorkspaceRoot(root);
20748
- const expandedPath = requestedPath?.replace(/^~/, homedir21());
20749
- const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath : join23(normalizedRoot, expandedPath) : normalizedRoot;
22846
+ const expandedPath = requestedPath?.replace(/^~/, homedir22());
22847
+ const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath : join25(normalizedRoot, expandedPath) : normalizedRoot;
20750
22848
  const resolvedCandidate = realpathSync(candidate);
20751
22849
  const rel = relative3(normalizedRoot, resolvedCandidate);
20752
22850
  if (rel === "" || !rel.startsWith("..") && !isAbsolute3(rel)) {
@@ -20776,7 +22874,7 @@ function listDirectories(dirPath) {
20776
22874
  continue;
20777
22875
  if (name === "node_modules" || name === ".build" || name === "target")
20778
22876
  continue;
20779
- const fullPath = join23(dirPath, name);
22877
+ const fullPath = join25(dirPath, name);
20780
22878
  try {
20781
22879
  const stat4 = statSync5(fullPath);
20782
22880
  if (!stat4.isDirectory())
@@ -20799,7 +22897,7 @@ function listDirectories(dirPath) {
20799
22897
  return entries.sort((a, b) => a.name.localeCompare(b.name));
20800
22898
  }
20801
22899
  async function discoverSessionFiles(maxAgeDays, limit) {
20802
- const home = homedir21();
22900
+ const home = homedir22();
20803
22901
  const results = [];
20804
22902
  const searchPaths = [
20805
22903
  { pattern: `${home}/.claude/projects`, agent: "claude-code" },
@@ -35444,7 +37542,7 @@ function planMessageDeliveries(input) {
35444
37542
  }
35445
37543
  return [...deliveries2.values()];
35446
37544
  }
35447
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/entity.js
37545
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/entity.js
35448
37546
  var entityKind = Symbol.for("drizzle:entityKind");
35449
37547
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
35450
37548
  function is(value, type) {
@@ -35469,7 +37567,7 @@ function is(value, type) {
35469
37567
  return false;
35470
37568
  }
35471
37569
 
35472
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/column.js
37570
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/column.js
35473
37571
  class Column {
35474
37572
  constructor(table, config2) {
35475
37573
  this.table = table;
@@ -35519,7 +37617,7 @@ class Column {
35519
37617
  }
35520
37618
  }
35521
37619
 
35522
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/column-builder.js
37620
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/column-builder.js
35523
37621
  class ColumnBuilder {
35524
37622
  static [entityKind] = "ColumnBuilder";
35525
37623
  config;
@@ -35575,20 +37673,20 @@ class ColumnBuilder {
35575
37673
  }
35576
37674
  }
35577
37675
 
35578
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/table.utils.js
37676
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/table.utils.js
35579
37677
  var TableName = Symbol.for("drizzle:Name");
35580
37678
 
35581
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/tracing-utils.js
37679
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/tracing-utils.js
35582
37680
  function iife(fn, ...args) {
35583
37681
  return fn(...args);
35584
37682
  }
35585
37683
 
35586
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/unique-constraint.js
37684
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/unique-constraint.js
35587
37685
  function uniqueKeyName(table, columns) {
35588
37686
  return `${table[TableName]}_${columns.join("_")}_unique`;
35589
37687
  }
35590
37688
 
35591
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/columns/common.js
37689
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/columns/common.js
35592
37690
  class PgColumn extends Column {
35593
37691
  constructor(table, config2) {
35594
37692
  if (!config2.uniqueName) {
@@ -35637,7 +37735,7 @@ class ExtraConfigColumn extends PgColumn {
35637
37735
  }
35638
37736
  }
35639
37737
 
35640
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/columns/enum.js
37738
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/columns/enum.js
35641
37739
  class PgEnumObjectColumn extends PgColumn {
35642
37740
  static [entityKind] = "PgEnumObjectColumn";
35643
37741
  enum;
@@ -35667,7 +37765,7 @@ class PgEnumColumn extends PgColumn {
35667
37765
  }
35668
37766
  }
35669
37767
 
35670
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/subquery.js
37768
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/subquery.js
35671
37769
  class Subquery {
35672
37770
  static [entityKind] = "Subquery";
35673
37771
  constructor(sql, fields, alias, isWith = false, usedTables = []) {
@@ -35682,10 +37780,10 @@ class Subquery {
35682
37780
  }
35683
37781
  }
35684
37782
 
35685
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/version.js
37783
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/version.js
35686
37784
  var version2 = "0.45.2";
35687
37785
 
35688
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/tracing.js
37786
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/tracing.js
35689
37787
  var otel;
35690
37788
  var rawTracer;
35691
37789
  var tracer = {
@@ -35712,10 +37810,10 @@ var tracer = {
35712
37810
  }
35713
37811
  };
35714
37812
 
35715
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/view-common.js
37813
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/view-common.js
35716
37814
  var ViewBaseConfig = Symbol.for("drizzle:ViewBaseConfig");
35717
37815
 
35718
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/table.js
37816
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/table.js
35719
37817
  var Schema = Symbol.for("drizzle:Schema");
35720
37818
  var Columns = Symbol.for("drizzle:Columns");
35721
37819
  var ExtraConfigColumns = Symbol.for("drizzle:ExtraConfigColumns");
@@ -35753,7 +37851,7 @@ class Table {
35753
37851
  }
35754
37852
  }
35755
37853
 
35756
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sql/sql.js
37854
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sql/sql.js
35757
37855
  function isSQLWrapper(value) {
35758
37856
  return value !== null && value !== undefined && typeof value.getSQL === "function";
35759
37857
  }
@@ -36030,7 +38128,7 @@ function sql(strings, ...params) {
36030
38128
  return new SQL([new StringChunk(str)]);
36031
38129
  }
36032
38130
  sql2.raw = raw;
36033
- function join24(chunks, separator) {
38131
+ function join26(chunks, separator) {
36034
38132
  const result = [];
36035
38133
  for (const [i, chunk] of chunks.entries()) {
36036
38134
  if (i > 0 && separator !== undefined) {
@@ -36040,7 +38138,7 @@ function sql(strings, ...params) {
36040
38138
  }
36041
38139
  return new SQL(result);
36042
38140
  }
36043
- sql2.join = join24;
38141
+ sql2.join = join26;
36044
38142
  function identifier(value) {
36045
38143
  return new Name(value);
36046
38144
  }
@@ -36113,7 +38211,7 @@ Subquery.prototype.getSQL = function() {
36113
38211
  return new SQL([this]);
36114
38212
  };
36115
38213
 
36116
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/utils.js
38214
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/utils.js
36117
38215
  function getColumnNameAndConfig(a, b) {
36118
38216
  return {
36119
38217
  name: typeof a === "string" && a.length > 0 ? a : "",
@@ -36122,7 +38220,7 @@ function getColumnNameAndConfig(a, b) {
36122
38220
  }
36123
38221
  var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder;
36124
38222
 
36125
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
38223
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
36126
38224
  class ForeignKeyBuilder {
36127
38225
  static [entityKind] = "SQLiteForeignKeyBuilder";
36128
38226
  reference;
@@ -36176,12 +38274,12 @@ class ForeignKey {
36176
38274
  }
36177
38275
  }
36178
38276
 
36179
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
38277
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
36180
38278
  function uniqueKeyName2(table, columns) {
36181
38279
  return `${table[TableName]}_${columns.join("_")}_unique`;
36182
38280
  }
36183
38281
 
36184
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/common.js
38282
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/common.js
36185
38283
  class SQLiteColumnBuilder extends ColumnBuilder {
36186
38284
  static [entityKind] = "SQLiteColumnBuilder";
36187
38285
  foreignKeyConfigs = [];
@@ -36232,7 +38330,7 @@ class SQLiteColumn extends Column {
36232
38330
  static [entityKind] = "SQLiteColumn";
36233
38331
  }
36234
38332
 
36235
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/blob.js
38333
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/blob.js
36236
38334
  class SQLiteBigIntBuilder extends SQLiteColumnBuilder {
36237
38335
  static [entityKind] = "SQLiteBigIntBuilder";
36238
38336
  constructor(name) {
@@ -36320,7 +38418,7 @@ function blob(a, b) {
36320
38418
  return new SQLiteBlobBufferBuilder(name);
36321
38419
  }
36322
38420
 
36323
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/custom.js
38421
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/custom.js
36324
38422
  class SQLiteCustomColumnBuilder extends SQLiteColumnBuilder {
36325
38423
  static [entityKind] = "SQLiteCustomColumnBuilder";
36326
38424
  constructor(name, fieldConfig, customTypeParams) {
@@ -36361,7 +38459,7 @@ function customType(customTypeParams) {
36361
38459
  };
36362
38460
  }
36363
38461
 
36364
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/integer.js
38462
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/integer.js
36365
38463
  class SQLiteBaseIntegerBuilder extends SQLiteColumnBuilder {
36366
38464
  static [entityKind] = "SQLiteBaseIntegerBuilder";
36367
38465
  constructor(name, dataType, columnType) {
@@ -36463,7 +38561,7 @@ function integer2(a, b) {
36463
38561
  return new SQLiteIntegerBuilder(name);
36464
38562
  }
36465
38563
 
36466
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
38564
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
36467
38565
  class SQLiteNumericBuilder extends SQLiteColumnBuilder {
36468
38566
  static [entityKind] = "SQLiteNumericBuilder";
36469
38567
  constructor(name) {
@@ -36533,7 +38631,7 @@ function numeric(a, b) {
36533
38631
  return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
36534
38632
  }
36535
38633
 
36536
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/real.js
38634
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/real.js
36537
38635
  class SQLiteRealBuilder extends SQLiteColumnBuilder {
36538
38636
  static [entityKind] = "SQLiteRealBuilder";
36539
38637
  constructor(name) {
@@ -36554,7 +38652,7 @@ function real(name) {
36554
38652
  return new SQLiteRealBuilder(name ?? "");
36555
38653
  }
36556
38654
 
36557
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/text.js
38655
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/text.js
36558
38656
  class SQLiteTextBuilder extends SQLiteColumnBuilder {
36559
38657
  static [entityKind] = "SQLiteTextBuilder";
36560
38658
  constructor(name, config2) {
@@ -36609,7 +38707,7 @@ function text(a, b = {}) {
36609
38707
  return new SQLiteTextBuilder(name, config2);
36610
38708
  }
36611
38709
 
36612
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/all.js
38710
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/all.js
36613
38711
  function getSQLiteColumnBuilders() {
36614
38712
  return {
36615
38713
  blob,
@@ -36621,7 +38719,7 @@ function getSQLiteColumnBuilders() {
36621
38719
  };
36622
38720
  }
36623
38721
 
36624
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/table.js
38722
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/table.js
36625
38723
  var InlineForeignKeys = Symbol.for("drizzle:SQLiteInlineForeignKeys");
36626
38724
 
36627
38725
  class SQLiteTable extends Table {
@@ -36655,7 +38753,7 @@ var sqliteTable = (name, columns, extraConfig) => {
36655
38753
  return sqliteTableBase(name, columns, extraConfig);
36656
38754
  };
36657
38755
 
36658
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/indexes.js
38756
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/indexes.js
36659
38757
  class IndexBuilderOn {
36660
38758
  constructor(name, unique2) {
36661
38759
  this.name = name;
@@ -36699,6 +38797,7 @@ function index(name) {
36699
38797
  }
36700
38798
 
36701
38799
  // ../runtime/src/drizzle-schema.ts
38800
+ var epochMsNow = sql`(CAST(strftime('%s','now') AS INTEGER) * 1000)`;
36702
38801
  var deliveriesTable = sqliteTable("deliveries", {
36703
38802
  id: text("id").primaryKey(),
36704
38803
  messageId: text("message_id"),
@@ -36714,7 +38813,7 @@ var deliveriesTable = sqliteTable("deliveries", {
36714
38813
  leaseOwner: text("lease_owner"),
36715
38814
  leaseExpiresAt: integer2("lease_expires_at"),
36716
38815
  metadataJson: text("metadata_json"),
36717
- createdAt: integer2("created_at").notNull().default(sql`(unixepoch())`)
38816
+ createdAt: integer2("created_at").notNull().default(epochMsNow)
36718
38817
  }, (table) => [
36719
38818
  index("idx_deliveries_status_transport").on(table.status, table.transport)
36720
38819
  ]);
@@ -36741,7 +38840,7 @@ var briefingsTable = sqliteTable("briefings", {
36741
38840
  snapshotJson: text("snapshot_json").notNull(),
36742
38841
  callJson: text("call_json").notNull(),
36743
38842
  markdown: text("markdown"),
36744
- createdAt: integer2("created_at").notNull().default(sql`(unixepoch())`)
38843
+ createdAt: integer2("created_at").notNull().default(epochMsNow)
36745
38844
  }, (table) => [
36746
38845
  index("idx_briefings_created_at").on(table.createdAt),
36747
38846
  index("idx_briefings_kind_created_at").on(table.kind, table.createdAt)
@@ -37428,6 +39527,9 @@ init_src2();
37428
39527
  // ../runtime/src/drizzle-migrate.ts
37429
39528
  init_support_paths();
37430
39529
  if (false) {}
39530
+
39531
+ // ../runtime/src/conversations/api.ts
39532
+ init_src2();
37431
39533
  // ../runtime/src/tailscale.ts
37432
39534
  import { execFile } from "child_process";
37433
39535
  import { promisify } from "util";
@@ -37442,6 +39544,11 @@ init_support_paths();
37442
39544
  // ../runtime/src/index.ts
37443
39545
  init_broker_process_manager();
37444
39546
  init_broker_api();
39547
+
39548
+ // ../runtime/src/invocation-lifecycle-read-model.ts
39549
+ init_src2();
39550
+
39551
+ // ../runtime/src/index.ts
37445
39552
  init_local_agents();
37446
39553
 
37447
39554
  // ../runtime/src/scout-broker.ts
@@ -37456,6 +39563,7 @@ init_broker_process_manager();
37456
39563
  init_codex_app_server();
37457
39564
  init_setup();
37458
39565
  init_support_paths();
39566
+ init_user_config();
37459
39567
 
37460
39568
  // ../runtime/src/thread-events.ts
37461
39569
  class ThreadWatchProtocolError extends Error {
@@ -37692,10 +39800,10 @@ class ThreadEventPlane {
37692
39800
  // ../runtime/src/mobile-push.ts
37693
39801
  init_support_paths();
37694
39802
  import { Database as Database2 } from "bun:sqlite";
37695
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync13 } from "fs";
39803
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync14 } from "fs";
37696
39804
  import { connect as connectHttp2 } from "http2";
37697
39805
  import { createPrivateKey, sign as signWithKey } from "crypto";
37698
- import { dirname as dirname11, join as join24 } from "path";
39806
+ import { dirname as dirname13, join as join26 } from "path";
37699
39807
  var MOBILE_PUSH_SCHEMA = `
37700
39808
  CREATE TABLE IF NOT EXISTS mobile_push_registrations (
37701
39809
  id TEXT PRIMARY KEY,
@@ -37723,7 +39831,7 @@ var dbHandle = null;
37723
39831
  var dbPath = null;
37724
39832
  var cachedApnsJwt = null;
37725
39833
  function resolveControlPlaneDbPath() {
37726
- return join24(resolveOpenScoutSupportPaths().controlHome, "control-plane.sqlite");
39834
+ return join26(resolveOpenScoutSupportPaths().controlHome, "control-plane.sqlite");
37727
39835
  }
37728
39836
  function ensureMobilePushSchema(database) {
37729
39837
  database.exec("PRAGMA busy_timeout = 5000;");
@@ -37737,7 +39845,7 @@ function writeDb() {
37737
39845
  dbHandle = null;
37738
39846
  }
37739
39847
  if (!dbHandle) {
37740
- mkdirSync10(dirname11(nextPath), { recursive: true });
39848
+ mkdirSync11(dirname13(nextPath), { recursive: true });
37741
39849
  dbHandle = new Database2(nextPath, { create: true });
37742
39850
  dbPath = nextPath;
37743
39851
  ensureMobilePushSchema(dbHandle);
@@ -38029,7 +40137,7 @@ function loadApnsCredentials() {
38029
40137
  privateKeyPem = Buffer.from(inlineBase64, "base64").toString("utf8");
38030
40138
  }
38031
40139
  if (!privateKeyPem && path2) {
38032
- privateKeyPem = readFileSync13(path2, "utf8");
40140
+ privateKeyPem = readFileSync14(path2, "utf8");
38033
40141
  }
38034
40142
  if (!teamId || !keyId || !privateKeyPem) {
38035
40143
  return null;
@@ -38520,8 +40628,8 @@ function candidateFromValue(key, value, root, now) {
38520
40628
  title: firstMetadataString(root, "title", "attentionTitle"),
38521
40629
  summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
38522
40630
  detail: firstMetadataString(root, "detail", "statusDetail"),
38523
- turnId: metadataString6(root, "turnId"),
38524
- blockId: metadataString6(root, "blockId"),
40631
+ turnId: metadataString7(root, "turnId"),
40632
+ blockId: metadataString7(root, "blockId"),
38525
40633
  version: metadataNumber(root, "version"),
38526
40634
  updatedAt: metadataTimestamp(root, now),
38527
40635
  severity: nativeSeverity(root)
@@ -38540,8 +40648,8 @@ function candidateFromValue(key, value, root, now) {
38540
40648
  title: firstMetadataString(root, "title", "attentionTitle"),
38541
40649
  summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
38542
40650
  detail: firstMetadataString(root, "detail", "statusDetail"),
38543
- turnId: metadataString6(root, "turnId"),
38544
- blockId: metadataString6(root, "blockId"),
40651
+ turnId: metadataString7(root, "turnId"),
40652
+ blockId: metadataString7(root, "blockId"),
38545
40653
  version: metadataNumber(root, "version"),
38546
40654
  updatedAt: metadataTimestamp(root, now),
38547
40655
  severity: nativeSeverity(root)
@@ -38570,8 +40678,8 @@ function candidateFromRecord(key, record2, now, keyImpliesAttention) {
38570
40678
  title: firstMetadataString(record2, "title", "label"),
38571
40679
  summary: firstMetadataString(record2, "summary", "message", "reason", "description"),
38572
40680
  detail: firstMetadataString(record2, "detail", "statusDetail", "hint"),
38573
- turnId: metadataString6(record2, "turnId"),
38574
- blockId: metadataString6(record2, "blockId"),
40681
+ turnId: metadataString7(record2, "turnId"),
40682
+ blockId: metadataString7(record2, "blockId"),
38575
40683
  version: metadataNumber(record2, "version"),
38576
40684
  updatedAt: metadataTimestamp(record2, now),
38577
40685
  severity: nativeSeverity(record2)
@@ -38591,7 +40699,7 @@ function stableNativeIdPart(value) {
38591
40699
  return stable || "blocked";
38592
40700
  }
38593
40701
  function nativeSeverity(record2) {
38594
- const severity = metadataString6(record2, "severity")?.toLowerCase();
40702
+ const severity = metadataString7(record2, "severity")?.toLowerCase();
38595
40703
  if (severity === "critical" || severity === "warning" || severity === "info") {
38596
40704
  return severity;
38597
40705
  }
@@ -38600,7 +40708,7 @@ function nativeSeverity(record2) {
38600
40708
  function metadataRecord3(value) {
38601
40709
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
38602
40710
  }
38603
- function metadataString6(record2, key) {
40711
+ function metadataString7(record2, key) {
38604
40712
  const value = record2?.[key];
38605
40713
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
38606
40714
  }
@@ -38621,7 +40729,7 @@ function metadataTimestamp(record2, fallback) {
38621
40729
  }
38622
40730
  function firstMetadataString(record2, ...keys) {
38623
40731
  for (const key of keys) {
38624
- const value = metadataString6(record2, key);
40732
+ const value = metadataString7(record2, key);
38625
40733
  if (value) {
38626
40734
  return value;
38627
40735
  }
@@ -38639,10 +40747,10 @@ function compactAttentionSummary(value, max = 220) {
38639
40747
  init_src2();
38640
40748
  // ../web/server/core/pairing/runtime/bridge/router.ts
38641
40749
  init_local_agents();
38642
- import { readFileSync as readFileSync14, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync6 } from "fs";
40750
+ import { readFileSync as readFileSync15, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync6 } from "fs";
38643
40751
  import { execSync as execSync4 } from "child_process";
38644
- import { basename as basename11, isAbsolute as isAbsolute4, join as join25, relative as relative4 } from "path";
38645
- import { homedir as homedir22 } from "os";
40752
+ import { basename as basename12, isAbsolute as isAbsolute4, join as join27, relative as relative4 } from "path";
40753
+ import { homedir as homedir23 } from "os";
38646
40754
  var t = initTRPC.context().create();
38647
40755
  var logged = t.middleware(async ({ path: path2, type, next }) => {
38648
40756
  const start = Date.now();
@@ -38669,13 +40777,13 @@ function resolveMobileCurrentDirectory2() {
38669
40777
  }
38670
40778
  }
38671
40779
  function resolveWorkspaceRoot2(root) {
38672
- const expandedRoot = root.replace(/^~/, homedir22());
40780
+ const expandedRoot = root.replace(/^~/, homedir23());
38673
40781
  return realpathSync2(expandedRoot);
38674
40782
  }
38675
40783
  function resolveWorkspacePath2(root, requestedPath) {
38676
40784
  const normalizedRoot = resolveWorkspaceRoot2(root);
38677
- const expandedPath = requestedPath?.replace(/^~/, homedir22());
38678
- const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath : join25(normalizedRoot, expandedPath) : normalizedRoot;
40785
+ const expandedPath = requestedPath?.replace(/^~/, homedir23());
40786
+ const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath : join27(normalizedRoot, expandedPath) : normalizedRoot;
38679
40787
  const resolvedCandidate = realpathSync2(candidate);
38680
40788
  const rel = relative4(normalizedRoot, resolvedCandidate);
38681
40789
  if (rel === "" || !rel.startsWith("..") && !isAbsolute4(rel)) {
@@ -38705,7 +40813,7 @@ function listDirectories2(dirPath) {
38705
40813
  continue;
38706
40814
  if (name === "node_modules" || name === ".build" || name === "target")
38707
40815
  continue;
38708
- const fullPath = join25(dirPath, name);
40816
+ const fullPath = join27(dirPath, name);
38709
40817
  try {
38710
40818
  const stat4 = statSync6(fullPath);
38711
40819
  if (!stat4.isDirectory())
@@ -38744,7 +40852,7 @@ function detectAgent2(filePath) {
38744
40852
  return "unknown";
38745
40853
  }
38746
40854
  async function discoverSessionFiles2(maxAgeDays, limit) {
38747
- const home = homedir22();
40855
+ const home = homedir23();
38748
40856
  const results = [];
38749
40857
  const searchPaths = [
38750
40858
  { pattern: `${home}/.claude/projects`, agent: "claude-code" },
@@ -39038,9 +41146,9 @@ var sessionRouter = t.router({
39038
41146
  adapterType: exports_external.string().optional(),
39039
41147
  name: exports_external.string().optional()
39040
41148
  })).mutation(async ({ input, ctx }) => {
39041
- const sessionFilename = basename11(input.sessionPath, ".jsonl");
41149
+ const sessionFilename = basename12(input.sessionPath, ".jsonl");
39042
41150
  const parentDir = input.sessionPath.substring(0, input.sessionPath.lastIndexOf("/"));
39043
- const dirName = basename11(parentDir);
41151
+ const dirName = basename12(parentDir);
39044
41152
  let cwd;
39045
41153
  if (dirName.startsWith("-")) {
39046
41154
  const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
@@ -39340,7 +41448,7 @@ var workspaceRouter = t.router({
39340
41448
  });
39341
41449
  }
39342
41450
  const adapterType = input.adapter ?? "claude-code";
39343
- const name = input.name ?? basename11(projectPath);
41451
+ const name = input.name ?? basename12(projectPath);
39344
41452
  return ctx.bridge.createSession(adapterType, {
39345
41453
  name,
39346
41454
  cwd: projectPath
@@ -39409,7 +41517,7 @@ var historyRouter = t.router({
39409
41517
  });
39410
41518
  }
39411
41519
  try {
39412
- const content = readFileSync14(input.path, "utf-8");
41520
+ const content = readFileSync15(input.path, "utf-8");
39413
41521
  const lines = content.split(`
39414
41522
  `).filter((l) => l.trim().length > 0);
39415
41523
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -39442,7 +41550,7 @@ var historyRouter = t.router({
39442
41550
  }
39443
41551
  try {
39444
41552
  const fileStat = statSync6(input.path);
39445
- const content = readFileSync14(input.path, "utf-8");
41553
+ const content = readFileSync15(input.path, "utf-8");
39446
41554
  const replay = createHistorySessionSnapshot({
39447
41555
  path: input.path,
39448
41556
  content,
@@ -43539,14 +45647,14 @@ function parseTRPCMessage(obj, transformer) {
43539
45647
  }
43540
45648
  // ../web/server/core/pairing/runtime/bridge/server-trpc.ts
43541
45649
  import { realpathSync as realpathSync3 } from "fs";
43542
- import { homedir as homedir23 } from "os";
45650
+ import { homedir as homedir24 } from "os";
43543
45651
  function resolveCurrentDirectory() {
43544
45652
  try {
43545
45653
  const config2 = resolveConfig();
43546
45654
  const configuredRoot = config2.workspace?.root;
43547
45655
  if (!configuredRoot)
43548
45656
  return process.cwd();
43549
- const expanded = configuredRoot.replace(/^~/, homedir23());
45657
+ const expanded = configuredRoot.replace(/^~/, homedir24());
43550
45658
  return realpathSync3(expanded);
43551
45659
  } catch {
43552
45660
  return process.cwd();
@@ -44066,7 +46174,7 @@ async function autoStartConfiguredSessions(bridge, sessions3) {
44066
46174
  try {
44067
46175
  const session = await bridge.createSession(entry.adapter, {
44068
46176
  name: entry.name,
44069
- cwd: entry.cwd?.replace(/^~/, homedir24()),
46177
+ cwd: entry.cwd?.replace(/^~/, homedir25()),
44070
46178
  options: entry.options
44071
46179
  });
44072
46180
  console.log(`[bridge] session started: ${session.name} (${entry.adapter})`);
@@ -44156,6 +46264,7 @@ async function startSupervisorRuntime(state) {
44156
46264
  state.bonjour = managedRelay ? startBonjourRelayAdvertisement({
44157
46265
  port: relayPort,
44158
46266
  relayUrl: activeRelayUrl,
46267
+ fallbackRelayUrls: managedRelay.fallbackRelayUrls,
44159
46268
  publicKeyHex
44160
46269
  }) : null;
44161
46270
  state.runtime = await startPairingRuntime({
@@ -44306,6 +46415,10 @@ function startBonjourRelayAdvertisement(input) {
44306
46415
  `fp=${fingerprint}`,
44307
46416
  `scheme=${scheme}`
44308
46417
  ];
46418
+ const fallbackRelayUrls = normalizedBonjourFallbackRelayUrls(input.fallbackRelayUrls);
46419
+ if (fallbackRelayUrls.length > 0) {
46420
+ args.push(`fallbackRelays=${fallbackRelayUrls.join("|")}`);
46421
+ }
44309
46422
  let processRef = null;
44310
46423
  try {
44311
46424
  processRef = spawn4("/usr/bin/dns-sd", args, { stdio: "ignore" });
@@ -44330,6 +46443,19 @@ function startBonjourRelayAdvertisement(input) {
44330
46443
  }
44331
46444
  };
44332
46445
  }
46446
+ function normalizedBonjourFallbackRelayUrls(relayUrls) {
46447
+ const seen = new Set;
46448
+ const result = [];
46449
+ for (const relayUrl of relayUrls ?? []) {
46450
+ const trimmed = relayUrl.trim();
46451
+ if (!trimmed || seen.has(trimmed)) {
46452
+ continue;
46453
+ }
46454
+ seen.add(trimmed);
46455
+ result.push(trimmed);
46456
+ }
46457
+ return result;
46458
+ }
44333
46459
  function relayScheme(relayUrl) {
44334
46460
  try {
44335
46461
  const protocol = new URL(relayUrl).protocol;