@openscout/scout 0.2.69 → 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";
@@ -14922,6 +15494,7 @@ var init_local_agents = __esm(() => {
14922
15494
  "mcp__scout__messages_inbox",
14923
15495
  "mcp__scout__messages_channel",
14924
15496
  "mcp__scout__broker_feed",
15497
+ "mcp__scout__tail_events",
14925
15498
  "mcp__scout__agents_search",
14926
15499
  "mcp__scout__agents_resolve",
14927
15500
  "mcp__scout__messages_reply",
@@ -19499,6 +20072,7 @@ function createScoutAgentService(deps) {
19499
20072
  }
19500
20073
 
19501
20074
  // ../web/server/core/broker/service.ts
20075
+ import { randomUUID as randomUUID3 } from "crypto";
19502
20076
  init_src2();
19503
20077
  init_setup();
19504
20078
  init_broker_api();
@@ -19571,6 +20145,8 @@ var scoutBrokerPaths = {
19571
20145
  node: "/v1/node",
19572
20146
  snapshot: "/v1/snapshot",
19573
20147
  topologySnapshot: "/v1/topology/snapshot",
20148
+ tailDiscover: "/v1/tail/discover",
20149
+ tailRecent: "/v1/tail/recent",
19574
20150
  messages: "/v1/messages",
19575
20151
  eventsStream: "/v1/events/stream",
19576
20152
  actors: "/v1/actors",
@@ -19589,7 +20165,6 @@ var scoutBrokerPaths = {
19589
20165
  };
19590
20166
 
19591
20167
  // ../web/server/core/broker/service.ts
19592
- var BROKER_SHARED_CHANNEL_ID = "channel.shared";
19593
20168
  var OPERATOR_ID = "operator";
19594
20169
  var DEFAULT_BROKER_HOST2 = "127.0.0.1";
19595
20170
  var DEFAULT_BROKER_PORT2 = 65535;
@@ -19916,21 +20491,15 @@ async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agent
19916
20491
  });
19917
20492
  return Boolean(binding);
19918
20493
  }
19919
- function directConversationIdForActors(sourceId, targetId) {
19920
- if (sourceId === targetId) {
19921
- return `dm.${sourceId}.${targetId}`;
19922
- }
19923
- if (sourceId === OPERATOR_ID || targetId === OPERATOR_ID) {
19924
- const peerId = sourceId === OPERATOR_ID ? targetId : sourceId;
19925
- return `dm.${OPERATOR_ID}.${peerId}`;
19926
- }
19927
- return `dm.${[sourceId, targetId].sort().join(".")}`;
20494
+ function findConversationByIdentity(snapshot, naturalKey) {
20495
+ return Object.values(snapshot.conversations).find((conversation) => channelNaturalKeyFromMetadata(conversation.metadata) === naturalKey);
19928
20496
  }
19929
20497
  async function ensureBrokerDirectConversationBetween(baseUrl, snapshot, nodeId, sourceId, targetId) {
19930
- const conversationId = targetId === SCOUT_AGENT_ID && sourceId === OPERATOR_ID ? BROKER_SHARED_CHANNEL_ID : directConversationIdForActors(sourceId, targetId);
19931
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);
19932
20502
  const nextShareMode = resolveConversationShareMode(snapshot, nodeId, participantIds, "local");
19933
- const existing = snapshot.conversations[conversationId];
19934
20503
  const alreadyMatches = existing && existing.kind === "direct" && existing.shareMode === nextShareMode && existing.visibility === "private" && existing.participantIds.join("\x00") === participantIds.join("\x00");
19935
20504
  if (alreadyMatches) {
19936
20505
  const preferredTargetId = targetId === OPERATOR_ID ? sourceId : targetId;
@@ -19952,6 +20521,7 @@ async function ensureBrokerDirectConversationBetween(baseUrl, snapshot, nodeId,
19952
20521
  participantIds,
19953
20522
  metadata: {
19954
20523
  surface: "scout",
20524
+ naturalKey,
19955
20525
  ...targetId === SCOUT_AGENT_ID && sourceId === OPERATOR_ID ? { role: "partner" } : {}
19956
20526
  }
19957
20527
  };
@@ -20222,6 +20792,11 @@ function transientBrokerWorkingStatusPredicate(alias) {
20222
20792
  var HEARTRATE_WINDOW_MS = 7 * 24 * 60 * 60000;
20223
20793
  // ../web/server/db/runs.ts
20224
20794
  init_src2();
20795
+
20796
+ // ../web/server/db/sessions.ts
20797
+ init_src2();
20798
+
20799
+ // ../web/server/db/runs.ts
20225
20800
  var ACTIVE_RUN_STATES = new Set([
20226
20801
  "queued",
20227
20802
  "waking",
@@ -20992,7 +21567,7 @@ async function ensureScoutbotRegistered(baseUrl, snapshot, nodeId, currentDirect
20992
21567
  await postJson(baseUrl, "/v1/actors", actor);
20993
21568
  }
20994
21569
  const existingAgent = snapshot.agents?.[SCOUTBOT_AGENT_ID];
20995
- if (!existingAgent || !hasScoutbotLabels(existingAgent) || !hasCurrentScoutbotAgentConfig(existingAgent)) {
21570
+ if (!existingAgent || !hasCurrentScoutbotAgentRegistration(existingAgent, nodeId)) {
20996
21571
  await postJson(baseUrl, "/v1/agents", agent);
20997
21572
  }
20998
21573
  const existingEndpoint = findScoutbotEndpoint(snapshot, nodeId);
@@ -21187,6 +21762,9 @@ function hasScoutbotLabels(agent) {
21187
21762
  const labels = new Set(agent.labels ?? []);
21188
21763
  return labels.has("assistant") && labels.has("scout") && labels.has("scoutbot");
21189
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
+ }
21190
21768
  function hasCurrentScoutbotAgentConfig(agent) {
21191
21769
  const roleConfig = metadataObject(agent.metadata, "roleConfig");
21192
21770
  return metadataString5(roleConfig, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt;
@@ -38949,6 +39527,9 @@ init_src2();
38949
39527
  // ../runtime/src/drizzle-migrate.ts
38950
39528
  init_support_paths();
38951
39529
  if (false) {}
39530
+
39531
+ // ../runtime/src/conversations/api.ts
39532
+ init_src2();
38952
39533
  // ../runtime/src/tailscale.ts
38953
39534
  import { execFile } from "child_process";
38954
39535
  import { promisify } from "util";