@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.
- package/bin/openscout-runtime.mjs +11 -0
- package/dist/client/assets/addon-fit-BNV7JPhj.js +1 -0
- package/dist/client/assets/addon-webgl-B-_Y5L3F.js +82 -0
- package/dist/client/assets/index-BJri_z5a.css +1 -0
- package/dist/client/assets/index-Ccjo5BZz.js +199 -0
- package/dist/client/assets/xterm-Cx1oABPt.js +34 -0
- package/dist/client/index.html +3 -3
- package/dist/main.mjs +19550 -16823
- package/dist/openscout-terminal-relay.mjs +601 -0
- package/dist/pair-supervisor.mjs +2327 -201
- package/dist/runtime/base-daemon.mjs +19 -0
- package/dist/runtime/broker-daemon.mjs +3496 -454
- package/dist/runtime/broker-process-manager.mjs +11 -0
- package/dist/runtime/mesh-discover.mjs +11 -0
- package/dist/scout-control-plane-web.mjs +4183 -642
- package/dist/scout-web-server.mjs +4183 -642
- package/package.json +4 -2
- package/dist/client/assets/addon-fit_hudsonkit_false-DSvcGNZx.js +0 -1
- package/dist/client/assets/addon-webgl_hudsonkit_false-1rrLEmjL.js +0 -1
- package/dist/client/assets/index-R2y6GOWl.js +0 -184
- package/dist/client/assets/index-bOmc2x1_.css +0 -1
- package/dist/client/assets/xterm_hudsonkit_false-B2TbElXJ.js +0 -1
package/dist/pair-supervisor.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
11941
|
+
activeTurn.reject(stoppedError);
|
|
11331
11942
|
}
|
|
11332
11943
|
for (const pending of this.pendingRequests.values()) {
|
|
11333
|
-
pending.reject(
|
|
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 &&
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
|
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 (!
|
|
12755
|
-
return
|
|
13455
|
+
if (!existsSync17(supportPaths.settingsPath)) {
|
|
13456
|
+
return dirname10(projectPath);
|
|
12756
13457
|
}
|
|
12757
|
-
const raw = JSON.parse(
|
|
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) :
|
|
13460
|
+
return workspaceRoot ? resolve6(workspaceRoot) : dirname10(projectPath);
|
|
12760
13461
|
} catch {
|
|
12761
|
-
return
|
|
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
|
|
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
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
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 (
|
|
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
|
|
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 ??
|
|
14747
|
+
return `exec bash ${JSON.stringify(workerScript ?? join20(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
|
|
13606
14748
|
}
|
|
13607
14749
|
if (harness === "pi") {
|
|
13608
|
-
const sessionDir =
|
|
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(
|
|
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(
|
|
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 =
|
|
13704
|
-
const processingDirectory =
|
|
13705
|
-
const processedDirectory =
|
|
13706
|
-
const promptFile =
|
|
13707
|
-
const initialFile =
|
|
13708
|
-
const launchScript =
|
|
13709
|
-
const workerScript =
|
|
13710
|
-
const codexSessionIdFile =
|
|
13711
|
-
const stateFile =
|
|
13712
|
-
const stdoutLogFile =
|
|
13713
|
-
const stderrLogFile =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
14094
|
-
if (
|
|
14095
|
-
const raw =
|
|
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 =
|
|
14104
|
-
if (
|
|
14105
|
-
const value =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
18649
|
-
import { homedir as
|
|
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
|
|
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
|
|
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
|
|
18815
|
-
if (broker &&
|
|
20037
|
+
let resolvedCreatedById = createdById;
|
|
20038
|
+
if (broker && resolvedCreatedById && resolvedCreatedById !== binding.agent.id) {
|
|
18816
20039
|
const session = await deps.openScoutPeerSession({
|
|
18817
|
-
sourceId:
|
|
20040
|
+
sourceId: resolvedCreatedById,
|
|
18818
20041
|
targetId: binding.agent.id,
|
|
18819
20042
|
currentDirectory
|
|
18820
20043
|
});
|
|
18821
20044
|
inboxConversationId = session.conversation.id;
|
|
18822
|
-
|
|
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
|
|
18845
|
-
mkdirSync as
|
|
18846
|
-
readFileSync as
|
|
20086
|
+
existsSync as existsSync18,
|
|
20087
|
+
mkdirSync as mkdirSync10,
|
|
20088
|
+
readFileSync as readFileSync12,
|
|
18847
20089
|
renameSync as renameSync2,
|
|
18848
|
-
writeFileSync as
|
|
20090
|
+
writeFileSync as writeFileSync7
|
|
18849
20091
|
} from "fs";
|
|
18850
|
-
import { homedir as
|
|
18851
|
-
import { dirname as
|
|
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 ??
|
|
20096
|
+
return process.env.OPENSCOUT_HOME ?? join21(homedir19(), ".openscout");
|
|
18855
20097
|
}
|
|
18856
20098
|
function localConfigPath() {
|
|
18857
|
-
return
|
|
20099
|
+
return join21(localConfigHome(), "config.json");
|
|
18858
20100
|
}
|
|
18859
20101
|
function loadLocalConfig() {
|
|
18860
20102
|
const configPath = localConfigPath();
|
|
18861
|
-
if (!
|
|
20103
|
+
if (!existsSync18(configPath))
|
|
18862
20104
|
return { version: LOCAL_CONFIG_VERSION };
|
|
18863
20105
|
try {
|
|
18864
|
-
return validateLocalConfig(JSON.parse(
|
|
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
|
|
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
|
|
19189
|
-
|
|
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
|
|
19328
|
-
import { join as
|
|
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 ??
|
|
19331
|
-
return
|
|
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
|
|
19364
|
-
import { join as
|
|
19365
|
-
var HOME =
|
|
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
|
|
20688
|
+
return join23(HOME, ".scout", "pairing", normalizedAdapter, normalizedSessionId, "logs", "stdout.log");
|
|
19378
20689
|
}
|
|
19379
20690
|
function relayHarnessLogPath(agentId) {
|
|
19380
|
-
return
|
|
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
|
-
|
|
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
|
|
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 ${
|
|
20851
|
+
WHERE ${whereClauses.join(" AND ")}
|
|
19512
20852
|
ORDER BY COALESCE(${endpointUpdatedAtExpression}, 0) DESC, ac.display_name ASC
|
|
19513
|
-
LIMIT ?`).all(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
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 &&
|
|
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).
|
|
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 =
|
|
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 && (
|
|
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:
|
|
20090
|
-
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 =
|
|
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:
|
|
20220
|
-
const { mkdirSync:
|
|
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 =
|
|
20229
|
-
const worktreePath =
|
|
20230
|
-
if (
|
|
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
|
-
|
|
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 =
|
|
22532
|
+
const sessionFilename = basename11(p.sessionPath, ".jsonl");
|
|
20435
22533
|
const parentDir = p.sessionPath.substring(0, p.sessionPath.lastIndexOf("/"));
|
|
20436
|
-
const dirName =
|
|
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 ??
|
|
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 =
|
|
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(/^~/,
|
|
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(/^~/,
|
|
20749
|
-
const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath :
|
|
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 =
|
|
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 =
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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
|
|
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 =
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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+
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
38524
|
-
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:
|
|
38544
|
-
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:
|
|
38574
|
-
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
38645
|
-
import { homedir as
|
|
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(/^~/,
|
|
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(/^~/,
|
|
38678
|
-
const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath :
|
|
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 =
|
|
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 =
|
|
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 =
|
|
41149
|
+
const sessionFilename = basename12(input.sessionPath, ".jsonl");
|
|
39042
41150
|
const parentDir = input.sessionPath.substring(0, input.sessionPath.lastIndexOf("/"));
|
|
39043
|
-
const dirName =
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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
|
|
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(/^~/,
|
|
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(/^~/,
|
|
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;
|