@openscout/scout 0.2.69 → 0.2.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1503,6 +1503,31 @@ function buildScoutReturnAddress(input) {
1503
1503
  }
1504
1504
  return next;
1505
1505
  }
1506
+ // packages/protocol/src/channel-identity.ts
1507
+ function mintChannelId(randomUuid) {
1508
+ return `${CHANNEL_ID_PREFIX}${randomUuid().toLowerCase()}`;
1509
+ }
1510
+ function directChannelNaturalKey(participantIds) {
1511
+ return `direct:${stableIdentityParts(participantIds).join(",")}`;
1512
+ }
1513
+ function namedChannelNaturalKey(channel) {
1514
+ return `channel:${encodeIdentityPart(channel.trim().toLowerCase() || "shared")}`;
1515
+ }
1516
+ function systemChannelNaturalKey(name) {
1517
+ return `system:${encodeIdentityPart(name.trim().toLowerCase() || "system")}`;
1518
+ }
1519
+ function channelNaturalKeyFromMetadata(metadata) {
1520
+ const value = metadata?.[CHANNEL_NATURAL_KEY_METADATA];
1521
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1522
+ }
1523
+ function stableIdentityParts(values) {
1524
+ return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean))).sort().map(encodeIdentityPart);
1525
+ }
1526
+ function encodeIdentityPart(value) {
1527
+ return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
1528
+ }
1529
+ var CHANNEL_ID_PREFIX = "c.", CHANNEL_NATURAL_KEY_METADATA = "naturalKey";
1530
+
1506
1531
  // packages/protocol/src/collaboration.ts
1507
1532
  function isQuestionTerminalState(state) {
1508
1533
  return state === "closed" || state === "declined";
@@ -5742,6 +5767,59 @@ function renderToolResultContent(content) {
5742
5767
  }
5743
5768
  return stringifyUnknown(content);
5744
5769
  }
5770
+ function renderContentPartsText(content) {
5771
+ if (typeof content === "string") {
5772
+ return content;
5773
+ }
5774
+ if (!Array.isArray(content)) {
5775
+ return "";
5776
+ }
5777
+ return content.map((entry) => {
5778
+ if (typeof entry === "string") {
5779
+ return entry;
5780
+ }
5781
+ if (!isRecord(entry)) {
5782
+ return "";
5783
+ }
5784
+ if (typeof entry.text === "string") {
5785
+ return entry.text;
5786
+ }
5787
+ if (typeof entry.content === "string") {
5788
+ return entry.content;
5789
+ }
5790
+ return "";
5791
+ }).filter(Boolean).join(`
5792
+ `);
5793
+ }
5794
+ function extractReasoningText(item) {
5795
+ const summary = Array.isArray(item.summary) ? item.summary : [];
5796
+ const content = Array.isArray(item.content) ? item.content : [];
5797
+ const summaryText = summary.map((entry) => {
5798
+ if (typeof entry === "string") {
5799
+ return entry;
5800
+ }
5801
+ const record = entry;
5802
+ if (typeof record.text === "string") {
5803
+ return record.text;
5804
+ }
5805
+ if (typeof record.summary === "string") {
5806
+ return record.summary;
5807
+ }
5808
+ return "";
5809
+ }).filter(Boolean).join(`
5810
+ `);
5811
+ const contentText = content.map((entry) => {
5812
+ if (typeof entry === "string") {
5813
+ return entry;
5814
+ }
5815
+ const record = entry;
5816
+ return typeof record.text === "string" ? record.text : "";
5817
+ }).filter(Boolean).join(`
5818
+ `);
5819
+ return [summaryText, contentText].filter(Boolean).join(`
5820
+
5821
+ `).trim();
5822
+ }
5745
5823
  function extractQuestionOptions(firstQuestion) {
5746
5824
  const options = Array.isArray(firstQuestion.options) ? firstQuestion.options : [];
5747
5825
  return options.map((option) => {
@@ -6451,17 +6529,517 @@ class ClaudeCodeHistoryParser {
6451
6529
  });
6452
6530
  }
6453
6531
  }
6532
+
6533
+ class CodexHistoryParser {
6534
+ session;
6535
+ baseTimestampMs;
6536
+ events = [];
6537
+ currentTurn = null;
6538
+ turnCounter = 0;
6539
+ blockIndex = 0;
6540
+ blockById = new Map;
6541
+ toolBlockMap = new Map;
6542
+ assistantMessageTextThisTurn = new Set;
6543
+ inputTokens = 0;
6544
+ outputTokens = 0;
6545
+ reasoningOutputTokens = 0;
6546
+ cachedInputTokens = 0;
6547
+ tokenEventCount = 0;
6548
+ constructor(session, baseTimestampMs) {
6549
+ this.session = session;
6550
+ this.baseTimestampMs = baseTimestampMs;
6551
+ }
6552
+ parse(content) {
6553
+ const lines = content.split(/\r?\n/u);
6554
+ let parsedLineCount = 0;
6555
+ let skippedLineCount = 0;
6556
+ let lineCount = 0;
6557
+ let lastCapturedAt = this.baseTimestampMs;
6558
+ for (let index = 0;index < lines.length; index += 1) {
6559
+ const rawLine = lines[index];
6560
+ const trimmed = rawLine.trim();
6561
+ if (!trimmed) {
6562
+ continue;
6563
+ }
6564
+ lineCount += 1;
6565
+ let record;
6566
+ try {
6567
+ const parsed = JSON.parse(trimmed);
6568
+ if (!isRecord(parsed)) {
6569
+ skippedLineCount += 1;
6570
+ continue;
6571
+ }
6572
+ record = parsed;
6573
+ } catch {
6574
+ skippedLineCount += 1;
6575
+ continue;
6576
+ }
6577
+ const capturedAt = extractRecordTimestamp(record) ?? this.baseTimestampMs + index;
6578
+ lastCapturedAt = capturedAt;
6579
+ if (this.handleRecord(record, capturedAt)) {
6580
+ parsedLineCount += 1;
6581
+ } else {
6582
+ skippedLineCount += 1;
6583
+ }
6584
+ }
6585
+ if (this.persistUsageMetadata()) {
6586
+ this.emitSessionUpdate(lastCapturedAt);
6587
+ }
6588
+ return {
6589
+ events: this.events,
6590
+ lineCount,
6591
+ parsedLineCount,
6592
+ skippedLineCount
6593
+ };
6594
+ }
6595
+ handleRecord(record, capturedAt) {
6596
+ const type = maybeString(record.type);
6597
+ if (!type) {
6598
+ return false;
6599
+ }
6600
+ switch (type) {
6601
+ case "session_meta":
6602
+ this.handleSessionMeta(record, capturedAt);
6603
+ return true;
6604
+ case "turn_context":
6605
+ this.handleTurnContext(record, capturedAt);
6606
+ return true;
6607
+ case "event_msg":
6608
+ this.handleEventMessage(record, capturedAt);
6609
+ return true;
6610
+ case "response_item":
6611
+ this.handleResponseItem(record, capturedAt);
6612
+ return true;
6613
+ case "compacted":
6614
+ return true;
6615
+ default:
6616
+ return false;
6617
+ }
6618
+ }
6619
+ handleSessionMeta(record, capturedAt) {
6620
+ const payload = isRecord(record.payload) ? record.payload : {};
6621
+ let changed = false;
6622
+ const providerMeta = this.ensureProviderMeta();
6623
+ const externalSessionId = maybeString(payload.id);
6624
+ if (externalSessionId && providerMeta.externalSessionId !== externalSessionId) {
6625
+ providerMeta.externalSessionId = externalSessionId;
6626
+ changed = true;
6627
+ }
6628
+ const cwd = maybeString(payload.cwd);
6629
+ if (cwd && this.session.cwd !== cwd) {
6630
+ this.session.cwd = cwd;
6631
+ if (!this.session.name || /^\d+$/u.test(this.session.name)) {
6632
+ this.session.name = basename2(cwd) || "Codex Session";
6633
+ }
6634
+ changed = true;
6635
+ }
6636
+ const git = isRecord(payload.git) ? payload.git : null;
6637
+ const runtime = this.ensureObserveMetaRecord("observeRuntime");
6638
+ changed = this.assignObserveString(runtime, "originator", payload.originator) || changed;
6639
+ changed = this.assignObserveString(runtime, "cliVersion", payload.cli_version) || changed;
6640
+ changed = this.assignObserveString(runtime, "source", payload.source) || changed;
6641
+ changed = this.assignObserveString(runtime, "threadSource", payload.thread_source) || changed;
6642
+ changed = this.assignObserveString(runtime, "modelProvider", payload.model_provider) || changed;
6643
+ changed = this.assignObserveString(runtime, "gitBranch", git?.branch) || changed;
6644
+ changed = this.assignObserveString(runtime, "gitCommitHash", git?.commit_hash) || changed;
6645
+ changed = this.assignObserveString(runtime, "repositoryUrl", git?.repository_url) || changed;
6646
+ if (changed) {
6647
+ this.emitSessionUpdate(capturedAt);
6648
+ }
6649
+ }
6650
+ handleTurnContext(record, capturedAt) {
6651
+ const payload = isRecord(record.payload) ? record.payload : {};
6652
+ let changed = false;
6653
+ const cwd = maybeString(payload.cwd);
6654
+ if (cwd && this.session.cwd !== cwd) {
6655
+ this.session.cwd = cwd;
6656
+ changed = true;
6657
+ }
6658
+ const model = maybeString(payload.model);
6659
+ if (model && this.session.model !== model) {
6660
+ this.session.model = model;
6661
+ changed = true;
6662
+ }
6663
+ const runtime = this.ensureObserveMetaRecord("observeRuntime");
6664
+ changed = this.assignObserveString(runtime, "approvalPolicy", payload.approval_policy) || changed;
6665
+ changed = this.assignObserveString(runtime, "currentDate", payload.current_date) || changed;
6666
+ changed = this.assignObserveString(runtime, "timezone", payload.timezone) || changed;
6667
+ changed = this.assignObserveString(runtime, "effort", payload.effort) || changed;
6668
+ changed = this.assignObserveString(runtime, "personality", payload.personality) || changed;
6669
+ if (isRecord(payload.sandbox_policy) && runtime.sandboxPolicy !== payload.sandbox_policy) {
6670
+ runtime.sandboxPolicy = payload.sandbox_policy;
6671
+ changed = true;
6672
+ }
6673
+ if (changed) {
6674
+ this.emitSessionUpdate(capturedAt);
6675
+ }
6676
+ }
6677
+ handleEventMessage(record, capturedAt) {
6678
+ const payload = isRecord(record.payload) ? record.payload : {};
6679
+ const eventType = maybeString(payload.type);
6680
+ if (!eventType) {
6681
+ return;
6682
+ }
6683
+ switch (eventType) {
6684
+ case "task_started": {
6685
+ const startedAt = normalizeTimestamp(payload.started_at) ?? capturedAt;
6686
+ this.startTurn(startedAt, maybeString(payload.turn_id));
6687
+ break;
6688
+ }
6689
+ case "user_message":
6690
+ this.ensureTurn(capturedAt);
6691
+ break;
6692
+ case "agent_message": {
6693
+ const message = maybeString(payload.message);
6694
+ if (message) {
6695
+ this.appendCompletedText(capturedAt, message);
6696
+ this.assistantMessageTextThisTurn.add(message);
6697
+ }
6698
+ break;
6699
+ }
6700
+ case "patch_apply_end":
6701
+ this.handleToolOutput(capturedAt, maybeString(payload.call_id), [maybeString(payload.stdout), maybeString(payload.stderr)].filter(Boolean).join(`
6702
+ `), payload.success === false);
6703
+ break;
6704
+ case "token_count":
6705
+ this.captureTokenUsage(payload.info);
6706
+ break;
6707
+ case "task_complete":
6708
+ this.endTurn("completed", normalizeTimestamp(payload.completed_at) ?? capturedAt);
6709
+ break;
6710
+ case "context_compacted":
6711
+ case "web_search_end":
6712
+ break;
6713
+ default:
6714
+ break;
6715
+ }
6716
+ }
6717
+ handleResponseItem(record, capturedAt) {
6718
+ const payload = isRecord(record.payload) ? record.payload : {};
6719
+ const itemType = maybeString(payload.type);
6720
+ if (!itemType) {
6721
+ return;
6722
+ }
6723
+ switch (itemType) {
6724
+ case "message":
6725
+ this.handleResponseMessage(payload, capturedAt);
6726
+ break;
6727
+ case "reasoning":
6728
+ this.handleReasoning(payload, capturedAt);
6729
+ break;
6730
+ case "function_call":
6731
+ case "custom_tool_call":
6732
+ this.handleToolCall(payload, capturedAt);
6733
+ break;
6734
+ case "function_call_output":
6735
+ case "custom_tool_call_output":
6736
+ this.handleToolOutput(capturedAt, maybeString(payload.call_id), renderToolResultContent(payload.output), false);
6737
+ break;
6738
+ case "web_search_call":
6739
+ this.handleWebSearchCall(payload, capturedAt);
6740
+ break;
6741
+ default:
6742
+ break;
6743
+ }
6744
+ }
6745
+ handleResponseMessage(payload, capturedAt) {
6746
+ const role = maybeString(payload.role);
6747
+ if (role !== "assistant") {
6748
+ return;
6749
+ }
6750
+ const text = renderContentPartsText(payload.content).trim();
6751
+ if (!text || this.assistantMessageTextThisTurn.has(text)) {
6752
+ return;
6753
+ }
6754
+ this.appendCompletedText(capturedAt, text);
6755
+ this.assistantMessageTextThisTurn.add(text);
6756
+ }
6757
+ handleReasoning(payload, capturedAt) {
6758
+ const text = extractReasoningText(payload);
6759
+ if (!text) {
6760
+ return;
6761
+ }
6762
+ const turn = this.ensureTurn(capturedAt);
6763
+ const block = this.startBlock(turn, capturedAt, {
6764
+ type: "reasoning",
6765
+ text,
6766
+ status: "completed"
6767
+ });
6768
+ this.emitBlockEnd(capturedAt, turn, block, "completed");
6769
+ }
6770
+ handleToolCall(payload, capturedAt) {
6771
+ const turn = this.ensureTurn(capturedAt);
6772
+ const toolName = maybeString(payload.name) ?? "unknown";
6773
+ const toolCallId = maybeString(payload.call_id) ?? `${turn.id}:tool:${this.blockIndex}`;
6774
+ const input = this.parseToolInput(payload.arguments ?? payload.input);
6775
+ const action = this.buildAction(toolName, toolCallId, input);
6776
+ const block = this.startBlock(turn, capturedAt, {
6777
+ id: `${turn.id}:action:${toolCallId}`,
6778
+ type: "action",
6779
+ action,
6780
+ status: "streaming"
6781
+ });
6782
+ this.toolBlockMap.set(toolCallId, block.id);
6783
+ }
6784
+ handleWebSearchCall(payload, capturedAt) {
6785
+ const turn = this.ensureTurn(capturedAt);
6786
+ const toolCallId = `${turn.id}:web-search:${this.blockIndex}`;
6787
+ const action = {
6788
+ kind: "tool_call",
6789
+ toolName: "web_search",
6790
+ toolCallId,
6791
+ input: payload.action,
6792
+ result: payload.status,
6793
+ status: maybeString(payload.status) === "failed" ? "failed" : "completed",
6794
+ output: ""
6795
+ };
6796
+ const block = this.startBlock(turn, capturedAt, {
6797
+ id: `${turn.id}:action:${toolCallId}`,
6798
+ type: "action",
6799
+ action,
6800
+ status: "completed"
6801
+ });
6802
+ this.emitBlockEnd(capturedAt, turn, block, "completed");
6803
+ }
6804
+ parseToolInput(value) {
6805
+ if (typeof value !== "string") {
6806
+ return value;
6807
+ }
6808
+ const trimmed = value.trim();
6809
+ if (!trimmed) {
6810
+ return "";
6811
+ }
6812
+ try {
6813
+ return JSON.parse(trimmed);
6814
+ } catch {
6815
+ return value;
6816
+ }
6817
+ }
6818
+ buildAction(toolName, toolCallId, input) {
6819
+ const inputRecord = isRecord(input) ? input : {};
6820
+ if (toolName === "exec_command") {
6821
+ return {
6822
+ kind: "command",
6823
+ command: maybeString(inputRecord.cmd) ?? "",
6824
+ status: "running",
6825
+ output: ""
6826
+ };
6827
+ }
6828
+ return {
6829
+ kind: "tool_call",
6830
+ toolName,
6831
+ toolCallId,
6832
+ input,
6833
+ status: "running",
6834
+ output: ""
6835
+ };
6836
+ }
6837
+ handleToolOutput(capturedAt, toolCallId, output, isError) {
6838
+ if (!toolCallId) {
6839
+ return;
6840
+ }
6841
+ const blockId = this.toolBlockMap.get(toolCallId);
6842
+ const turn = this.currentTurn;
6843
+ if (!blockId || !turn) {
6844
+ return;
6845
+ }
6846
+ if (output) {
6847
+ this.emitEvent(capturedAt, {
6848
+ event: "block:action:output",
6849
+ sessionId: this.session.id,
6850
+ turnId: turn.id,
6851
+ blockId,
6852
+ output
6853
+ });
6854
+ }
6855
+ const exitCode = this.extractExitCode(output);
6856
+ const status = isError || exitCode != null && exitCode !== 0 ? "failed" : "completed";
6857
+ this.emitEvent(capturedAt, {
6858
+ event: "block:action:status",
6859
+ sessionId: this.session.id,
6860
+ turnId: turn.id,
6861
+ blockId,
6862
+ status,
6863
+ ...exitCode != null ? { meta: { exitCode } } : {}
6864
+ });
6865
+ const block = this.blockById.get(blockId);
6866
+ if (block) {
6867
+ this.emitBlockEnd(capturedAt, turn, block, status === "failed" ? "failed" : "completed");
6868
+ }
6869
+ this.toolBlockMap.delete(toolCallId);
6870
+ }
6871
+ extractExitCode(output) {
6872
+ const match2 = output.match(/(?:exit code|exited with code):\s*(-?\d+)/iu);
6873
+ if (!match2) {
6874
+ return null;
6875
+ }
6876
+ const exitCode = Number(match2[1]);
6877
+ return Number.isFinite(exitCode) ? exitCode : null;
6878
+ }
6879
+ captureTokenUsage(info) {
6880
+ if (!isRecord(info)) {
6881
+ return;
6882
+ }
6883
+ const total = isRecord(info.total_token_usage) ? info.total_token_usage : {};
6884
+ this.inputTokens = maybeNumber(total.input_tokens) ?? this.inputTokens;
6885
+ this.outputTokens = maybeNumber(total.output_tokens) ?? this.outputTokens;
6886
+ this.reasoningOutputTokens = maybeNumber(total.reasoning_output_tokens) ?? this.reasoningOutputTokens;
6887
+ this.cachedInputTokens = maybeNumber(total.cached_input_tokens) ?? this.cachedInputTokens;
6888
+ this.tokenEventCount += 1;
6889
+ }
6890
+ persistUsageMetadata() {
6891
+ if (this.tokenEventCount === 0) {
6892
+ return false;
6893
+ }
6894
+ const usage = this.ensureObserveMetaRecord("observeUsage");
6895
+ let changed = false;
6896
+ const assignNumber = (key, value) => {
6897
+ if (value > 0 && usage[key] !== value) {
6898
+ usage[key] = value;
6899
+ changed = true;
6900
+ }
6901
+ };
6902
+ assignNumber("inputTokens", this.inputTokens);
6903
+ assignNumber("outputTokens", this.outputTokens);
6904
+ assignNumber("reasoningOutputTokens", this.reasoningOutputTokens);
6905
+ assignNumber("cacheReadInputTokens", this.cachedInputTokens);
6906
+ assignNumber("tokenEvents", this.tokenEventCount);
6907
+ return changed;
6908
+ }
6909
+ startTurn(capturedAt, turnId) {
6910
+ if (this.currentTurn) {
6911
+ this.endTurn("stopped", capturedAt);
6912
+ }
6913
+ this.turnCounter += 1;
6914
+ this.blockIndex = 0;
6915
+ this.blockById.clear();
6916
+ this.toolBlockMap.clear();
6917
+ this.assistantMessageTextThisTurn.clear();
6918
+ this.session.status = "active";
6919
+ this.emitSessionUpdate(capturedAt);
6920
+ const turn = {
6921
+ id: turnId || `history-turn-${this.turnCounter}`,
6922
+ sessionId: this.session.id,
6923
+ status: "started",
6924
+ startedAt: new Date(capturedAt).toISOString(),
6925
+ blocks: []
6926
+ };
6927
+ this.currentTurn = turn;
6928
+ this.emitEvent(capturedAt, {
6929
+ event: "turn:start",
6930
+ sessionId: this.session.id,
6931
+ turn
6932
+ });
6933
+ return turn;
6934
+ }
6935
+ ensureTurn(capturedAt) {
6936
+ return this.currentTurn ?? this.startTurn(capturedAt);
6937
+ }
6938
+ endTurn(status, capturedAt) {
6939
+ const turn = this.currentTurn;
6940
+ if (!turn) {
6941
+ return;
6942
+ }
6943
+ turn.status = status;
6944
+ turn.endedAt = new Date(capturedAt).toISOString();
6945
+ this.emitEvent(capturedAt, {
6946
+ event: "turn:end",
6947
+ sessionId: this.session.id,
6948
+ turnId: turn.id,
6949
+ status
6950
+ });
6951
+ this.currentTurn = null;
6952
+ this.blockById.clear();
6953
+ this.toolBlockMap.clear();
6954
+ this.assistantMessageTextThisTurn.clear();
6955
+ this.session.status = "idle";
6956
+ this.emitSessionUpdate(capturedAt);
6957
+ }
6958
+ appendCompletedText(capturedAt, text) {
6959
+ const turn = this.ensureTurn(capturedAt);
6960
+ const block = this.startBlock(turn, capturedAt, {
6961
+ type: "text",
6962
+ text,
6963
+ status: "completed"
6964
+ });
6965
+ this.emitBlockEnd(capturedAt, turn, block, "completed");
6966
+ }
6967
+ startBlock(turn, capturedAt, partial) {
6968
+ const block = {
6969
+ ...partial,
6970
+ id: partial.id || `${turn.id}:block:${this.blockIndex}`,
6971
+ turnId: turn.id,
6972
+ index: this.blockIndex
6973
+ };
6974
+ this.blockIndex += 1;
6975
+ turn.blocks.push(block);
6976
+ this.blockById.set(block.id, block);
6977
+ this.emitEvent(capturedAt, {
6978
+ event: "block:start",
6979
+ sessionId: this.session.id,
6980
+ turnId: turn.id,
6981
+ block
6982
+ });
6983
+ return block;
6984
+ }
6985
+ emitBlockEnd(capturedAt, turn, block, status) {
6986
+ block.status = status;
6987
+ this.emitEvent(capturedAt, {
6988
+ event: "block:end",
6989
+ sessionId: this.session.id,
6990
+ turnId: turn.id,
6991
+ blockId: block.id,
6992
+ status
6993
+ });
6994
+ }
6995
+ ensureProviderMeta() {
6996
+ const providerMeta = isRecord(this.session.providerMeta) ? this.session.providerMeta : {};
6997
+ this.session.providerMeta = providerMeta;
6998
+ return providerMeta;
6999
+ }
7000
+ ensureObserveMetaRecord(key) {
7001
+ const providerMeta = this.ensureProviderMeta();
7002
+ const existing = providerMeta[key];
7003
+ if (isRecord(existing)) {
7004
+ return existing;
7005
+ }
7006
+ const next = {};
7007
+ providerMeta[key] = next;
7008
+ return next;
7009
+ }
7010
+ assignObserveString(target, key, value) {
7011
+ const next = maybeString(value);
7012
+ if (!next || target[key] === next) {
7013
+ return false;
7014
+ }
7015
+ target[key] = next;
7016
+ return true;
7017
+ }
7018
+ emitSessionUpdate(capturedAt) {
7019
+ this.emitEvent(capturedAt, {
7020
+ event: "session:update",
7021
+ session: { ...this.session }
7022
+ });
7023
+ }
7024
+ emitEvent(capturedAt, event) {
7025
+ this.events.push({
7026
+ capturedAt,
7027
+ event: structuredClone(event)
7028
+ });
7029
+ }
7030
+ }
6454
7031
  function supportsHistorySessionSnapshotForPath(path, adapterType) {
6455
- return inferHistoryAdapterType(path, adapterType) === "claude-code";
7032
+ const inferred = inferHistoryAdapterType(path, adapterType);
7033
+ return inferred === "claude-code" || inferred === "codex";
6456
7034
  }
6457
7035
  function createHistorySessionSnapshot(input) {
6458
7036
  const adapterType = inferHistoryAdapterType(input.path, input.adapterType);
6459
- if (adapterType !== "claude-code") {
7037
+ if (adapterType !== "claude-code" && adapterType !== "codex") {
6460
7038
  throw new Error(`History snapshot is not supported for adapter type "${adapterType}".`);
6461
7039
  }
6462
7040
  const session = buildBaseHistorySession(input, adapterType);
6463
7041
  const baseTimestampMs = normalizeTimestamp(input.baseTimestampMs) ?? Date.now();
6464
- const parser = new ClaudeCodeHistoryParser(session, baseTimestampMs);
7042
+ const parser = adapterType === "codex" ? new CodexHistoryParser(session, baseTimestampMs) : new ClaudeCodeHistoryParser(session, baseTimestampMs);
6465
7043
  const replay = parser.parse(input.content);
6466
7044
  const tracker = new StateTracker;
6467
7045
  tracker.createSession(session.id, session);
@@ -8181,7 +8759,7 @@ function stringifyValue(value) {
8181
8759
  return String(value);
8182
8760
  }
8183
8761
  }
8184
- function extractReasoningText(item) {
8762
+ function extractReasoningText2(item) {
8185
8763
  const summary = Array.isArray(item.summary) ? item.summary : [];
8186
8764
  const content = Array.isArray(item.content) ? item.content : [];
8187
8765
  const summaryText = summary.map((entry) => {
@@ -8657,7 +9235,7 @@ var init_codex = __esm(() => {
8657
9235
  this.ensureTextBlock(turnState, itemId, typeof item.text === "string" ? item.text : "");
8658
9236
  return;
8659
9237
  case "reasoning": {
8660
- const text = extractReasoningText(item);
9238
+ const text = extractReasoningText2(item);
8661
9239
  if (text) {
8662
9240
  this.ensureReasoningBlock(turnState, itemId, text);
8663
9241
  }
@@ -8736,7 +9314,7 @@ var init_codex = __esm(() => {
8736
9314
  return;
8737
9315
  }
8738
9316
  case "reasoning": {
8739
- const finalText = extractReasoningText(item);
9317
+ const finalText = extractReasoningText2(item);
8740
9318
  if (!finalText && !turnState.blocksByItemId.has(itemId)) {
8741
9319
  return;
8742
9320
  }
@@ -12481,7 +13059,7 @@ function isCodexThreadGlobalMessage(message) {
12481
13059
  function sessionKey2(options) {
12482
13060
  return `${options.agentName}:${options.sessionId}`;
12483
13061
  }
12484
- function metadataString2(metadata, key) {
13062
+ function metadataString3(metadata, key) {
12485
13063
  const value = metadata?.[key];
12486
13064
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
12487
13065
  }
@@ -13909,9 +14487,9 @@ class CodexAppServerSession {
13909
14487
  return;
13910
14488
  }
13911
14489
  const errorPayload = metadataRecord(params, "error");
13912
- const message2 = metadataString2(errorPayload, "message") ?? metadataString2(params, "message") ?? "Codex app-server reported an error.";
13913
- const codexErrorInfo = metadataString2(errorPayload, "codexErrorInfo") ?? metadataString2(params, "codexErrorInfo");
13914
- const additionalDetails = metadataString2(errorPayload, "additionalDetails") ?? metadataString2(params, "additionalDetails");
14490
+ const message2 = metadataString3(errorPayload, "message") ?? metadataString3(params, "message") ?? "Codex app-server reported an error.";
14491
+ const codexErrorInfo = metadataString3(errorPayload, "codexErrorInfo") ?? metadataString3(params, "codexErrorInfo");
14492
+ const additionalDetails = metadataString3(errorPayload, "additionalDetails") ?? metadataString3(params, "additionalDetails");
13915
14493
  const detail = [codexErrorInfo, additionalDetails].filter((value) => Boolean(value && value.trim().length > 0)).join("; ");
13916
14494
  const error = detail.length > 0 ? `${message2} (${detail})` : message2;
13917
14495
  if (this.activeTurn) {
@@ -14109,8 +14687,8 @@ async function getCodexAppServerAgentSnapshot(options) {
14109
14687
  readOptionalFile3(threadIdPath),
14110
14688
  readOptionalJsonRecord(statePath)
14111
14689
  ]);
14112
- const resolvedThreadId = options.threadId ?? persistedThreadId ?? metadataString2(persistedState ?? undefined, "threadId") ?? undefined;
14113
- const persistedThreadPath = metadataString2(persistedState ?? undefined, "threadPath");
14690
+ const resolvedThreadId = options.threadId ?? persistedThreadId ?? metadataString3(persistedState ?? undefined, "threadId") ?? undefined;
14691
+ const persistedThreadPath = metadataString3(persistedState ?? undefined, "threadPath");
14114
14692
  if (persistedThreadPath) {
14115
14693
  const rawRollout = await readOptionalFile3(persistedThreadPath);
14116
14694
  if (rawRollout) {
@@ -14379,6 +14957,8 @@ __export(exports_local_agents, {
14379
14957
  getLocalAgentConfig: () => getLocalAgentConfig,
14380
14958
  ensureLocalSessionEndpointOnline: () => ensureLocalSessionEndpointOnline,
14381
14959
  ensureLocalAgentBindingOnline: () => ensureLocalAgentBindingOnline,
14960
+ endpointStateAfterSuccessfulSessionWarmup: () => endpointStateAfterSuccessfulSessionWarmup,
14961
+ clearEndpointFailureMetadata: () => clearEndpointFailureMetadata,
14382
14962
  classifyLocalAgentContextState: () => classifyLocalAgentContextState,
14383
14963
  buildTmuxPasteBufferArgs: () => buildTmuxPasteBufferArgs,
14384
14964
  buildTmuxLaunchShellCommand: () => buildTmuxLaunchShellCommand,
@@ -15525,6 +16105,13 @@ async function ensureLocalSessionEndpointOnline(endpoint) {
15525
16105
  }
15526
16106
  return {};
15527
16107
  }
16108
+ function clearEndpointFailureMetadata(metadata) {
16109
+ const { lastError: _lastError, lastFailedAt: _lastFailedAt, ...baseMetadata } = metadata ?? {};
16110
+ return baseMetadata;
16111
+ }
16112
+ function endpointStateAfterSuccessfulSessionWarmup(state) {
16113
+ return state === "active" ? "active" : "idle";
16114
+ }
15528
16115
  async function shutdownLocalSessionEndpoint(endpoint) {
15529
16116
  if (endpoint.transport === "codex_app_server") {
15530
16117
  await shutdownCodexAppServerAgent(buildCodexEndpointSessionOptions(endpoint));
@@ -15953,7 +16540,7 @@ function invocationSessionFreshness(invocation) {
15953
16540
  case "existing":
15954
16541
  return "continuing session";
15955
16542
  case "any":
15956
- return "reuse-or-new session";
16543
+ return invocation.execution?.targetSessionId ? "continuing session" : "fresh session";
15957
16544
  default:
15958
16545
  return "session unspecified";
15959
16546
  }
@@ -17342,6 +17929,7 @@ var init_local_agents = __esm(() => {
17342
17929
  "mcp__scout__messages_inbox",
17343
17930
  "mcp__scout__messages_channel",
17344
17931
  "mcp__scout__broker_feed",
17932
+ "mcp__scout__tail_events",
17345
17933
  "mcp__scout__agents_search",
17346
17934
  "mcp__scout__agents_resolve",
17347
17935
  "mcp__scout__messages_reply",
@@ -20662,17 +21250,24 @@ function queryActivity(limit = 60) {
20662
21250
  ai.invocation_id,
20663
21251
  ai.session_id,
20664
21252
  ai.message_id,
20665
- ai.record_id
21253
+ ai.record_id,
21254
+ c.kind AS conversation_kind
20666
21255
  FROM activity_items ai
20667
21256
  LEFT JOIN actors ac ON ac.id = ai.actor_id
20668
21257
  LEFT JOIN actors agent_actor ON agent_actor.id = ai.agent_id
21258
+ LEFT JOIN conversations c ON c.id = ai.conversation_id
20669
21259
  WHERE ai.kind != 'ask_replied'
20670
21260
  AND ${staleFlightActivityPredicate("ai")}
20671
21261
  ORDER BY ${activityTsExpression} DESC
20672
21262
  LIMIT ?`).all(limit);
20673
21263
  const items = rows.map((r) => ({
20674
21264
  id: r.id,
20675
- kind: r.kind,
21265
+ kind: normalizeActivityKind(r.kind, {
21266
+ conversationKind: r.conversation_kind,
21267
+ messageId: r.message_id,
21268
+ invocationId: r.invocation_id,
21269
+ flightId: r.flight_id
21270
+ }),
20676
21271
  ts: r.ts,
20677
21272
  actorName: r.actor_name,
20678
21273
  title: r.title,
@@ -20689,6 +21284,12 @@ function queryActivity(limit = 60) {
20689
21284
  }));
20690
21285
  return items.filter((item, index) => !isDuplicateActivityFeedItem(items[index - 1] ?? null, item));
20691
21286
  }
21287
+ function normalizeActivityKind(kind, input) {
21288
+ if (kind === "ask_opened" && input.messageId && !input.invocationId && !input.flightId && input.conversationKind !== "direct") {
21289
+ return "message_posted";
21290
+ }
21291
+ return kind;
21292
+ }
20692
21293
  var HEARTRATE_WINDOW_MS = 7 * 24 * 60 * 60000;
20693
21294
  var DEFAULT_HEARTRATE_BUCKETS = 56;
20694
21295
  function smoothHeartrateCounts(counts) {
@@ -21540,6 +22141,7 @@ function queryWorkItems(opts) {
21540
22141
  }
21541
22142
 
21542
22143
  // packages/web/server/db/sessions.ts
22144
+ init_src();
21543
22145
  function pickDirectConversationAgentId(participants, candidateAgentIds) {
21544
22146
  const uniqueAgentIds = Array.from(new Set(candidateAgentIds.filter(Boolean)));
21545
22147
  if (uniqueAgentIds.length === 0) {
@@ -21609,6 +22211,64 @@ function queryConversationDefinitionById(conversationId) {
21609
22211
  participantIds: participants
21610
22212
  };
21611
22213
  }
22214
+ function parseMetadataJson(raw2) {
22215
+ if (!raw2)
22216
+ return {};
22217
+ try {
22218
+ const parsed = JSON.parse(raw2);
22219
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
22220
+ } catch {
22221
+ return {};
22222
+ }
22223
+ }
22224
+ function metadataString2(metadata, key) {
22225
+ const value = metadata[key];
22226
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
22227
+ }
22228
+ function formatChannelAlias(value) {
22229
+ const trimmed = value.trim();
22230
+ if (!trimmed)
22231
+ return "";
22232
+ return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
22233
+ }
22234
+ function conversationAliasForRow(row, metadata) {
22235
+ const explicitAlias = metadataString2(metadata, "alias");
22236
+ if (explicitAlias)
22237
+ return explicitAlias;
22238
+ const channel = metadataString2(metadata, "channel");
22239
+ if (channel && channel !== "system") {
22240
+ return formatChannelAlias(channel);
22241
+ }
22242
+ if (row.kind === "channel") {
22243
+ if (row.id.startsWith("channel.")) {
22244
+ return formatChannelAlias(row.id.slice("channel.".length));
22245
+ }
22246
+ return formatChannelAlias(row.title);
22247
+ }
22248
+ return null;
22249
+ }
22250
+ function conversationIdentityFields(row, metadata) {
22251
+ const alias = conversationAliasForRow(row, metadata);
22252
+ const naturalKey = channelNaturalKeyFromMetadata(metadata);
22253
+ return {
22254
+ ...alias ? { alias } : {},
22255
+ ...naturalKey ? { naturalKey } : {}
22256
+ };
22257
+ }
22258
+ function resolveConversationAlias(conversationId) {
22259
+ const byMetadata = db().prepare(`SELECT id, metadata_json
22260
+ FROM conversations
22261
+ ORDER BY created_at ASC`).all();
22262
+ const directConversation = parseDirectConversationId(conversationId);
22263
+ const naturalKey = directConversation ? directChannelNaturalKey([directConversation.operatorId, directConversation.agentId]) : conversationId.startsWith("channel.") ? namedChannelNaturalKey(conversationId.slice("channel.".length)) : null;
22264
+ for (const row of byMetadata) {
22265
+ const metadata = parseMetadataJson(row.metadata_json);
22266
+ if (naturalKey && channelNaturalKeyFromMetadata(metadata) === naturalKey) {
22267
+ return row.id;
22268
+ }
22269
+ }
22270
+ return null;
22271
+ }
21612
22272
  function projectSessionConversationRows(rows, opts) {
21613
22273
  const dedupeLocalSessionDirects = opts?.dedupeLocalSessionDirects ?? false;
21614
22274
  const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
@@ -21634,6 +22294,7 @@ function projectSessionConversationRows(rows, opts) {
21634
22294
  ORDER BY ${messageCreatedAtExpression} DESC
21635
22295
  LIMIT 1`);
21636
22296
  const summaries = rows.flatMap((r) => {
22297
+ const conversationMetadata = parseMetadataJson(r.metadata_json);
21637
22298
  const participants = memberStmt.all(r.id).map((m) => m.actor_id);
21638
22299
  const agentParticipants = agentMemberStmt.all(r.id);
21639
22300
  const primaryAgentId = r.kind === "direct" ? pickDirectConversationAgentId(participants, agentParticipants.map((entry) => entry.agent_id)) : agentParticipants.length === 1 ? agentParticipants[0]?.agent_id ?? null : null;
@@ -21666,10 +22327,12 @@ function projectSessionConversationRows(rows, opts) {
21666
22327
  }
21667
22328
  }
21668
22329
  const preview = previewStmt.get(r.id)?.body ?? null;
22330
+ const identityFields = conversationIdentityFields(r, conversationMetadata);
21669
22331
  return [{
21670
22332
  id: r.id,
21671
22333
  kind: r.kind,
21672
22334
  title: agentName ?? r.title,
22335
+ ...identityFields,
21673
22336
  participantIds: participants,
21674
22337
  agentId,
21675
22338
  agentName,
@@ -21739,7 +22402,7 @@ function querySessions(limit = 80) {
21739
22402
  }
21740
22403
  function querySessionById(conversationId) {
21741
22404
  const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
21742
- const row = db().prepare(`SELECT
22405
+ const readRow = (id) => db().prepare(`SELECT
21743
22406
  c.id,
21744
22407
  c.kind,
21745
22408
  c.title,
@@ -21754,11 +22417,20 @@ function querySessionById(conversationId) {
21754
22417
  GROUP BY conversation_id
21755
22418
  ) ms ON ms.conversation_id = c.id
21756
22419
  WHERE c.id = ?
21757
- LIMIT 1`).get(conversationId, conversationId);
22420
+ LIMIT 1`).get(id, id);
22421
+ const row = readRow(conversationId);
21758
22422
  const existing = row ? projectSessionConversationRows([row], { dedupeLocalSessionDirects: false, limit: 1 })[0] ?? null : null;
21759
22423
  if (existing) {
21760
22424
  return existing;
21761
22425
  }
22426
+ const resolvedConversationId = resolveConversationAlias(conversationId);
22427
+ if (resolvedConversationId) {
22428
+ const resolvedRow = readRow(resolvedConversationId);
22429
+ const resolved = resolvedRow ? projectSessionConversationRows([resolvedRow], { dedupeLocalSessionDirects: false, limit: 1 })[0] ?? null : null;
22430
+ if (resolved) {
22431
+ return resolved;
22432
+ }
22433
+ }
21762
22434
  const directConversation = parseDirectConversationId(conversationId);
21763
22435
  if (!directConversation) {
21764
22436
  return null;
@@ -22214,7 +22886,7 @@ init_user_config();
22214
22886
  function projectFleetActivity(row) {
22215
22887
  return {
22216
22888
  id: row.id,
22217
- kind: row.kind,
22889
+ kind: normalizeFleetActivityKind(row),
22218
22890
  ts: row.ts,
22219
22891
  actorName: row.actor_name,
22220
22892
  title: row.title,
@@ -22231,6 +22903,12 @@ function projectFleetActivity(row) {
22231
22903
  sessionId: row.session_id
22232
22904
  };
22233
22905
  }
22906
+ function normalizeFleetActivityKind(row) {
22907
+ if (row.kind === "ask_opened" && row.message_id && !row.invocation_id && !row.flight_id && row.conversation_kind !== "direct") {
22908
+ return "message_posted";
22909
+ }
22910
+ return row.kind;
22911
+ }
22234
22912
  function queryFleetActivity(opts) {
22235
22913
  const filters = [];
22236
22914
  const params = [];
@@ -22279,10 +22957,12 @@ function queryFleetActivity(opts) {
22279
22957
  ai.invocation_id,
22280
22958
  ai.flight_id,
22281
22959
  ai.record_id,
22282
- ai.session_id
22960
+ ai.session_id,
22961
+ c.kind AS conversation_kind
22283
22962
  FROM activity_items ai
22284
22963
  LEFT JOIN actors ac ON ac.id = ai.actor_id
22285
22964
  LEFT JOIN actors agent_actor ON agent_actor.id = ai.agent_id
22965
+ LEFT JOIN conversations c ON c.id = ai.conversation_id
22286
22966
  ${sqlWhereClause([
22287
22967
  staleFlightActivityPredicate("ai"),
22288
22968
  ...andClauses,
@@ -22554,6 +23234,7 @@ init_broker_process_manager();
22554
23234
  init_local_agents();
22555
23235
  init_support_paths();
22556
23236
  init_user_config();
23237
+ import { randomUUID as randomUUID3 } from "crypto";
22557
23238
  import { mkdir as mkdir7, readFile as readFile7, unlink, writeFile as writeFile7 } from "fs/promises";
22558
23239
  import { basename as basename9, join as join22, resolve as resolve8 } from "path";
22559
23240
 
@@ -22565,6 +23246,8 @@ var scoutBrokerPaths = {
22565
23246
  node: "/v1/node",
22566
23247
  snapshot: "/v1/snapshot",
22567
23248
  topologySnapshot: "/v1/topology/snapshot",
23249
+ tailDiscover: "/v1/tail/discover",
23250
+ tailRecent: "/v1/tail/recent",
22568
23251
  messages: "/v1/messages",
22569
23252
  eventsStream: "/v1/events/stream",
22570
23253
  actors: "/v1/actors",
@@ -22583,9 +23266,6 @@ var scoutBrokerPaths = {
22583
23266
  };
22584
23267
 
22585
23268
  // packages/web/server/core/broker/service.ts
22586
- var BROKER_SHARED_CHANNEL_ID = "channel.shared";
22587
- var BROKER_VOICE_CHANNEL_ID = "channel.voice";
22588
- var BROKER_SYSTEM_CHANNEL_ID = "channel.system";
22589
23269
  var OPERATOR_ID = "operator";
22590
23270
  var DEFAULT_BROKER_HOST2 = "127.0.0.1";
22591
23271
  var DEFAULT_BROKER_PORT2 = 65535;
@@ -22619,7 +23299,7 @@ function titleCaseName(value) {
22619
23299
  return value.split(/[-_.\s]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
22620
23300
  }
22621
23301
  function displayNameForBrokerActor(snapshot, actorId) {
22622
- return snapshot.agents[actorId]?.displayName ?? snapshot.actors[actorId]?.displayName ?? titleCaseName(metadataString3(snapshot.agents[actorId]?.metadata, "definitionId") || actorId);
23302
+ return snapshot.agents[actorId]?.displayName ?? snapshot.actors[actorId]?.displayName ?? titleCaseName(metadataString4(snapshot.agents[actorId]?.metadata, "definitionId") || actorId);
22623
23303
  }
22624
23304
  function firstEndpointForActor(snapshot, actorId) {
22625
23305
  return Object.values(snapshot.endpoints).filter((endpoint) => endpoint.agentId === actorId).sort((lhs, rhs) => lhs.id.localeCompare(rhs.id))[0];
@@ -22628,9 +23308,9 @@ function buildScoutReturnAddress2(snapshot, actorId, options = {}) {
22628
23308
  const agent = snapshot.agents[actorId];
22629
23309
  const actor = snapshot.actors[actorId];
22630
23310
  const endpoint = firstEndpointForActor(snapshot, actorId);
22631
- const selector = agent?.selector?.trim() || metadataString3(agent?.metadata, "selector") || metadataString3(actor?.metadata, "selector");
22632
- const defaultSelector = agent?.defaultSelector?.trim() || metadataString3(agent?.metadata, "defaultSelector") || metadataString3(actor?.metadata, "defaultSelector");
22633
- const projectRoot = endpoint?.projectRoot ?? endpoint?.cwd ?? metadataString3(agent?.metadata, "projectRoot") ?? metadataString3(actor?.metadata, "projectRoot");
23311
+ const selector = agent?.selector?.trim() || metadataString4(agent?.metadata, "selector") || metadataString4(actor?.metadata, "selector");
23312
+ const defaultSelector = agent?.defaultSelector?.trim() || metadataString4(agent?.metadata, "defaultSelector") || metadataString4(actor?.metadata, "defaultSelector");
23313
+ const projectRoot = endpoint?.projectRoot ?? endpoint?.cwd ?? metadataString4(agent?.metadata, "projectRoot") ?? metadataString4(actor?.metadata, "projectRoot");
22634
23314
  return buildScoutReturnAddress({
22635
23315
  actorId,
22636
23316
  handle: agent?.handle?.trim() || actor?.handle?.trim() || actorId,
@@ -22644,10 +23324,7 @@ function buildScoutReturnAddress2(snapshot, actorId, options = {}) {
22644
23324
  sessionId: endpoint?.sessionId
22645
23325
  });
22646
23326
  }
22647
- function sanitizeConversationSegment(value) {
22648
- return value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-") || "shared";
22649
- }
22650
- function metadataString3(metadata, key) {
23327
+ function metadataString4(metadata, key) {
22651
23328
  const value = metadata?.[key];
22652
23329
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
22653
23330
  }
@@ -22692,6 +23369,24 @@ function renderScoutTargetLabel(targetLabel) {
22692
23369
  }
22693
23370
  return trimmed.startsWith("@") ? trimmed : `@${trimmed}`;
22694
23371
  }
23372
+ function renderedScoutAskTarget(target) {
23373
+ switch (target.kind) {
23374
+ case "agent_id":
23375
+ return target.agentId.trim();
23376
+ case "agent_label":
23377
+ return target.label.trim();
23378
+ case "session_id":
23379
+ return `session:${target.sessionId.trim()}`;
23380
+ case "binding_ref":
23381
+ return `ref:${target.ref.trim()}`;
23382
+ case "project_path":
23383
+ return target.projectPath.trim();
23384
+ case "channel":
23385
+ return `channel:${target.channel.trim()}`;
23386
+ case "broadcast":
23387
+ return "broadcast";
23388
+ }
23389
+ }
22695
23390
  function scoutTargetDiagnosticFromDeliveryFailure(delivery) {
22696
23391
  const dispatch = delivery.kind === "question" ? delivery.question : delivery.rejection;
22697
23392
  if (dispatch.kind === "ambiguous") {
@@ -22827,18 +23522,18 @@ async function requireScoutBrokerContext(baseUrl = resolveScoutBrokerUrl()) {
22827
23522
  function buildMentionCandidate(snapshot, agent) {
22828
23523
  const endpoints = Object.values(snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === agent.id);
22829
23524
  const preferred = endpoints.find((endpoint) => endpoint.state === "active") ?? endpoints.find((endpoint) => endpoint.state === "idle" || endpoint.state === "waiting") ?? endpoints[0];
22830
- const harness = preferred?.harness ?? metadataString3(agent.metadata, "harness") ?? metadataString3(agent.metadata, "defaultHarness");
22831
- const profile = metadataString3(agent.metadata, "profile");
23525
+ const harness = preferred?.harness ?? metadataString4(agent.metadata, "harness") ?? metadataString4(agent.metadata, "defaultHarness");
23526
+ const profile = metadataString4(agent.metadata, "profile");
22832
23527
  return {
22833
23528
  agentId: agent.id,
22834
- definitionId: metadataString3(agent.metadata, "definitionId") || agent.id,
22835
- nodeQualifier: metadataString3(agent.metadata, "nodeQualifier"),
22836
- workspaceQualifier: metadataString3(agent.metadata, "workspaceQualifier"),
23529
+ definitionId: metadataString4(agent.metadata, "definitionId") || agent.id,
23530
+ nodeQualifier: metadataString4(agent.metadata, "nodeQualifier"),
23531
+ workspaceQualifier: metadataString4(agent.metadata, "workspaceQualifier"),
22837
23532
  ...harness ? { harness } : {},
22838
23533
  ...profile ? { profile } : {},
22839
23534
  aliases: [
22840
- metadataString3(agent.metadata, "selector"),
22841
- metadataString3(agent.metadata, "defaultSelector")
23535
+ metadataString4(agent.metadata, "selector"),
23536
+ metadataString4(agent.metadata, "defaultSelector")
22842
23537
  ].filter(Boolean)
22843
23538
  };
22844
23539
  }
@@ -23054,50 +23749,74 @@ function conversationDefinition(snapshot, nodeId, channel, senderId, targetParti
23054
23749
  const sharedParticipants = [...new Set([OPERATOR_ID, senderId, ...Object.keys(snapshot.agents)])].sort();
23055
23750
  const scopedParticipants = [...new Set([OPERATOR_ID, senderId, ...targetParticipantIds])].sort();
23056
23751
  if (normalizedChannel === "voice") {
23752
+ const naturalKey2 = namedChannelNaturalKey("voice");
23753
+ const existing2 = findConversationByIdentity(snapshot, naturalKey2);
23057
23754
  return {
23058
- id: BROKER_VOICE_CHANNEL_ID,
23755
+ id: existing2?.id ?? mintChannelId(randomUUID3),
23059
23756
  kind: "channel",
23060
23757
  title: "voice",
23061
23758
  visibility: "workspace",
23062
23759
  shareMode: resolveConversationShareMode(snapshot, nodeId, scopedParticipants, "local"),
23063
23760
  authorityNodeId: nodeId,
23064
23761
  participantIds: scopedParticipants,
23065
- metadata: { surface: "scout-cli", channel: "voice" }
23762
+ metadata: {
23763
+ surface: "scout-cli",
23764
+ channel: "voice",
23765
+ naturalKey: naturalKey2
23766
+ }
23066
23767
  };
23067
23768
  }
23068
23769
  if (normalizedChannel === "system") {
23770
+ const naturalKey2 = systemChannelNaturalKey("system");
23771
+ const existing2 = findConversationByIdentity(snapshot, naturalKey2);
23069
23772
  return {
23070
- id: BROKER_SYSTEM_CHANNEL_ID,
23773
+ id: existing2?.id ?? mintChannelId(randomUUID3),
23071
23774
  kind: "system",
23072
23775
  title: "system",
23073
23776
  visibility: "system",
23074
23777
  shareMode: "local",
23075
23778
  authorityNodeId: nodeId,
23076
23779
  participantIds: [OPERATOR_ID, senderId].sort(),
23077
- metadata: { surface: "scout-cli", channel: "system" }
23780
+ metadata: {
23781
+ surface: "scout-cli",
23782
+ channel: "system",
23783
+ naturalKey: naturalKey2
23784
+ }
23078
23785
  };
23079
23786
  }
23080
23787
  if (normalizedChannel === "shared") {
23788
+ const naturalKey2 = namedChannelNaturalKey("shared");
23789
+ const existing2 = findConversationByIdentity(snapshot, naturalKey2);
23081
23790
  return {
23082
- id: BROKER_SHARED_CHANNEL_ID,
23791
+ id: existing2?.id ?? mintChannelId(randomUUID3),
23083
23792
  kind: "channel",
23084
23793
  title: "shared-channel",
23085
23794
  visibility: "workspace",
23086
23795
  shareMode: "shared",
23087
23796
  authorityNodeId: nodeId,
23088
23797
  participantIds: sharedParticipants,
23089
- metadata: { surface: "scout-cli", channel: "shared" }
23798
+ metadata: {
23799
+ surface: "scout-cli",
23800
+ channel: "shared",
23801
+ naturalKey: naturalKey2
23802
+ }
23090
23803
  };
23091
23804
  }
23805
+ const naturalKey = namedChannelNaturalKey(normalizedChannel);
23806
+ const existing = findConversationByIdentity(snapshot, naturalKey);
23092
23807
  return {
23093
- id: `channel.${sanitizeConversationSegment(normalizedChannel)}`,
23808
+ id: existing?.id ?? mintChannelId(randomUUID3),
23094
23809
  kind: "channel",
23095
23810
  title: normalizedChannel,
23096
23811
  visibility: "workspace",
23097
23812
  shareMode: resolveConversationShareMode(snapshot, nodeId, scopedParticipants, "local"),
23098
23813
  authorityNodeId: nodeId,
23099
23814
  participantIds: scopedParticipants,
23100
- metadata: { surface: "scout-cli", channel: normalizedChannel }
23815
+ metadata: {
23816
+ surface: "scout-cli",
23817
+ channel: normalizedChannel,
23818
+ naturalKey
23819
+ }
23101
23820
  };
23102
23821
  }
23103
23822
  async function ensureBrokerConversation(baseUrl, snapshot, nodeId, channel, senderId, targetParticipantIds = []) {
@@ -23115,21 +23834,15 @@ async function ensureBrokerConversation(baseUrl, snapshot, nodeId, channel, send
23115
23834
  }
23116
23835
  return existing;
23117
23836
  }
23118
- function directConversationIdForActors(sourceId, targetId) {
23119
- if (sourceId === targetId) {
23120
- return `dm.${sourceId}.${targetId}`;
23121
- }
23122
- if (sourceId === OPERATOR_ID || targetId === OPERATOR_ID) {
23123
- const peerId = sourceId === OPERATOR_ID ? targetId : sourceId;
23124
- return `dm.${OPERATOR_ID}.${peerId}`;
23125
- }
23126
- return `dm.${[sourceId, targetId].sort().join(".")}`;
23837
+ function findConversationByIdentity(snapshot, naturalKey) {
23838
+ return Object.values(snapshot.conversations).find((conversation) => channelNaturalKeyFromMetadata(conversation.metadata) === naturalKey);
23127
23839
  }
23128
23840
  async function ensureBrokerDirectConversationBetween(baseUrl, snapshot, nodeId, sourceId, targetId) {
23129
- const conversationId = targetId === SCOUT_AGENT_ID && sourceId === OPERATOR_ID ? BROKER_SHARED_CHANNEL_ID : directConversationIdForActors(sourceId, targetId);
23130
23841
  const participantIds = [...new Set([sourceId, targetId])].sort();
23842
+ const naturalKey = directChannelNaturalKey(participantIds);
23843
+ const existing = findConversationByIdentity(snapshot, naturalKey);
23844
+ const conversationId = existing?.id ?? mintChannelId(randomUUID3);
23131
23845
  const nextShareMode = resolveConversationShareMode(snapshot, nodeId, participantIds, "local");
23132
- const existing = snapshot.conversations[conversationId];
23133
23846
  const alreadyMatches = existing && existing.kind === "direct" && existing.shareMode === nextShareMode && existing.visibility === "private" && existing.participantIds.join("\x00") === participantIds.join("\x00");
23134
23847
  if (alreadyMatches) {
23135
23848
  const preferredTargetId = targetId === OPERATOR_ID ? sourceId : targetId;
@@ -23151,6 +23864,7 @@ async function ensureBrokerDirectConversationBetween(baseUrl, snapshot, nodeId,
23151
23864
  participantIds,
23152
23865
  metadata: {
23153
23866
  surface: "scout",
23867
+ naturalKey,
23154
23868
  ...targetId === SCOUT_AGENT_ID && sourceId === OPERATOR_ID ? { role: "partner" } : {}
23155
23869
  }
23156
23870
  };
@@ -23411,23 +24125,30 @@ async function sendScoutDirectMessage(input) {
23411
24125
  };
23412
24126
  }
23413
24127
  async function askScoutQuestion(input) {
24128
+ const renderedTarget = input.targetLabel?.trim() || (input.target ? renderedScoutAskTarget(input.target) : "") || input.targetAgentId?.trim() || "";
23414
24129
  const broker = await loadScoutBrokerContext();
23415
24130
  if (!broker) {
23416
- return { usedBroker: false, unresolvedTarget: input.targetLabel };
24131
+ return { usedBroker: false, unresolvedTarget: renderedTarget };
23417
24132
  }
23418
24133
  const currentDirectory = input.currentDirectory ?? process.cwd();
23419
24134
  const senderId = await resolveConversationActorId(broker.baseUrl, broker.snapshot, broker.node.id, input.senderId, currentDirectory);
23420
- const normalizedTargetLabel = renderScoutTargetLabel(input.targetLabel);
23421
- const explicitTargetAgentId = input.targetAgentId?.trim() || broker.snapshot.agents[input.targetLabel.trim()]?.id;
24135
+ if (!renderedTarget) {
24136
+ return { usedBroker: true, unresolvedTarget: renderedTarget };
24137
+ }
24138
+ const normalizedTargetLabel = input.target?.kind === "project_path" ? "" : renderScoutTargetLabel(renderedTarget);
24139
+ const explicitTargetAgentId = input.targetAgentId?.trim() || (input.target ? undefined : broker.snapshot.agents[renderedTarget]?.id);
23422
24140
  if (explicitTargetAgentId) {
23423
24141
  await ensureTargetRelayAgentRegistered(broker.baseUrl, broker.snapshot, broker.node.id, explicitTargetAgentId, currentDirectory);
23424
24142
  }
23425
- const messageBody = input.body.trim().startsWith(normalizedTargetLabel) ? input.body.trim() : `${normalizedTargetLabel} ${input.body.trim()}`;
24143
+ const messageBody = normalizedTargetLabel && input.body.trim().startsWith(normalizedTargetLabel) ? input.body.trim() : normalizedTargetLabel ? `${normalizedTargetLabel} ${input.body.trim()}` : input.body.trim();
24144
+ const createdAt = input.createdAtMs ?? Date.now();
24145
+ const source = input.source?.trim() || "scout-cli";
23426
24146
  const delivery = await brokerPostDeliver(broker.baseUrl, {
23427
- id: `deliver-${(input.createdAtMs ?? Date.now()).toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
24147
+ id: `deliver-${createdAt.toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
23428
24148
  requesterId: senderId,
23429
24149
  requesterNodeId: broker.node.id,
23430
- targetLabel: input.targetLabel,
24150
+ ...input.target ? { target: input.target } : {},
24151
+ targetLabel: renderedTarget,
23431
24152
  targetAgentId: explicitTargetAgentId,
23432
24153
  body: messageBody,
23433
24154
  intent: "consult",
@@ -23435,21 +24156,23 @@ async function askScoutQuestion(input) {
23435
24156
  speechText: input.shouldSpeak ? stripScoutAgentSelectorLabels(messageBody) : undefined,
23436
24157
  execution: {
23437
24158
  ...input.executionHarness ? { harness: input.executionHarness } : {},
23438
- session: "new"
24159
+ ...input.executionModel?.trim() ? { model: input.executionModel.trim() } : {},
24160
+ session: input.executionSession ?? "new"
23439
24161
  },
24162
+ ...input.projectAgent ? { projectAgent: input.projectAgent } : {},
23440
24163
  ensureAwake: true,
23441
- createdAt: input.createdAtMs ?? Date.now(),
24164
+ createdAt,
23442
24165
  messageMetadata: {
23443
- source: "scout-cli"
24166
+ source
23444
24167
  },
23445
24168
  invocationMetadata: {
23446
- source: "scout-cli"
24169
+ source
23447
24170
  }
23448
24171
  });
23449
24172
  if (delivery.kind !== "delivery") {
23450
24173
  return {
23451
24174
  usedBroker: true,
23452
- unresolvedTarget: input.targetLabel,
24175
+ unresolvedTarget: renderedTarget,
23453
24176
  targetDiagnostic: scoutTargetDiagnosticFromDeliveryFailure(delivery)
23454
24177
  };
23455
24178
  }
@@ -23470,6 +24193,7 @@ async function loadScoutRelayConfig() {
23470
24193
  }
23471
24194
 
23472
24195
  // packages/web/server/core/conversations/service.ts
24196
+ init_src();
23473
24197
  var DEFAULT_CONVERSATION_KINDS = [
23474
24198
  "direct",
23475
24199
  "channel",
@@ -23511,10 +24235,38 @@ function endpointActivity(endpoint) {
23511
24235
  function endpointStartedAt(endpoint) {
23512
24236
  return Math.max(normalizeMetadataTimestamp(endpoint.metadata?.lastStartedAt), normalizeMetadataTimestamp(endpoint.metadata?.startedAt));
23513
24237
  }
23514
- function metadataString4(metadata, key) {
24238
+ function metadataString5(metadata, key) {
23515
24239
  const value = metadata?.[key];
23516
24240
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
23517
24241
  }
24242
+ function formatChannelAlias2(value) {
24243
+ const trimmed = value.trim();
24244
+ if (!trimmed)
24245
+ return "";
24246
+ return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
24247
+ }
24248
+ function conversationAlias(input) {
24249
+ const explicitAlias = metadataString5(input.metadata, "alias");
24250
+ if (explicitAlias)
24251
+ return explicitAlias;
24252
+ const channel = metadataString5(input.metadata, "channel");
24253
+ if (channel && channel !== "system") {
24254
+ return formatChannelAlias2(channel);
24255
+ }
24256
+ if (input.kind === "channel") {
24257
+ if (input.id.startsWith("channel.")) {
24258
+ return formatChannelAlias2(input.id.slice("channel.".length));
24259
+ }
24260
+ return formatChannelAlias2(input.title);
24261
+ }
24262
+ return null;
24263
+ }
24264
+ function conversationIdentityFields2(input) {
24265
+ return {
24266
+ alias: conversationAlias(input),
24267
+ naturalKey: channelNaturalKeyFromMetadata(input.metadata)
24268
+ };
24269
+ }
23518
24270
  function metadataBoolean2(metadata, key) {
23519
24271
  return metadata?.[key] === true;
23520
24272
  }
@@ -23599,17 +24351,19 @@ async function getScoutConversations(filters = {}) {
23599
24351
  return [];
23600
24352
  }
23601
24353
  const title = agentDisplayName(snapshot, agentId);
24354
+ const identityFields2 = conversationIdentityFields2(conversation);
23602
24355
  return [{
23603
24356
  id: conversation.id,
23604
24357
  kind: conversation.kind,
23605
24358
  title,
24359
+ ...identityFields2,
23606
24360
  participantIds: [...conversation.participantIds],
23607
24361
  authorityNodeId: conversation.authorityNodeId ?? null,
23608
24362
  authorityNodeName: snapshot.nodes?.[conversation.authorityNodeId]?.name ?? null,
23609
24363
  agentId,
23610
24364
  agentName: title,
23611
24365
  harness: endpoint?.harness ?? null,
23612
- currentBranch: metadataString4(endpoint?.metadata, "branch") ?? metadataString4(endpoint?.metadata, "workspaceQualifier") ?? metadataString4(agent.metadata, "branch") ?? metadataString4(agent.metadata, "workspaceQualifier"),
24366
+ currentBranch: metadataString5(endpoint?.metadata, "branch") ?? metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent.metadata, "branch") ?? metadataString5(agent.metadata, "workspaceQualifier"),
23613
24367
  preview: latestMessage?.body ?? null,
23614
24368
  messageCount,
23615
24369
  lastMessageAt: normalizeTimestampMs2(latestMessage?.createdAt),
@@ -23628,10 +24382,12 @@ async function getScoutConversations(filters = {}) {
23628
24382
  } else if (conversation.kind === "system") {
23629
24383
  return [];
23630
24384
  }
24385
+ const identityFields = conversationIdentityFields2(conversation);
23631
24386
  return [{
23632
24387
  id: conversation.id,
23633
24388
  kind: conversation.kind,
23634
24389
  title: conversation.title,
24390
+ ...identityFields,
23635
24391
  participantIds: [...conversation.participantIds],
23636
24392
  authorityNodeId: conversation.authorityNodeId ?? null,
23637
24393
  authorityNodeName: snapshot.nodes?.[conversation.authorityNodeId]?.name ?? null,
@@ -25109,7 +25865,7 @@ function metadataRecord3(metadata, key) {
25109
25865
  }
25110
25866
  return value;
25111
25867
  }
25112
- function metadataString5(metadata, key) {
25868
+ function metadataString6(metadata, key) {
25113
25869
  const value = metadata?.[key];
25114
25870
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
25115
25871
  }
@@ -25133,50 +25889,50 @@ function buildObserveMetadata(snapshot, sessionStart) {
25133
25889
  }
25134
25890
  if (snapshot.turns.length > 0)
25135
25891
  sessionMeta.turnCount = snapshot.turns.length;
25136
- if (metadataString5(providerMeta, "externalSessionId")) {
25137
- sessionMeta.externalSessionId = metadataString5(providerMeta, "externalSessionId");
25892
+ if (metadataString6(providerMeta, "externalSessionId")) {
25893
+ sessionMeta.externalSessionId = metadataString6(providerMeta, "externalSessionId");
25138
25894
  }
25139
- if (metadataString5(providerMeta, "threadId")) {
25140
- sessionMeta.threadId = metadataString5(providerMeta, "threadId");
25895
+ if (metadataString6(providerMeta, "threadId")) {
25896
+ sessionMeta.threadId = metadataString6(providerMeta, "threadId");
25141
25897
  }
25142
- if (metadataString5(providerMeta, "threadPath")) {
25143
- sessionMeta.threadPath = metadataString5(providerMeta, "threadPath");
25898
+ if (metadataString6(providerMeta, "threadPath")) {
25899
+ sessionMeta.threadPath = metadataString6(providerMeta, "threadPath");
25144
25900
  }
25145
- if (metadataString5(observeRuntime, "gitBranch")) {
25146
- sessionMeta.gitBranch = metadataString5(observeRuntime, "gitBranch");
25901
+ if (metadataString6(observeRuntime, "gitBranch")) {
25902
+ sessionMeta.gitBranch = metadataString6(observeRuntime, "gitBranch");
25147
25903
  }
25148
- if (metadataString5(observeRuntime, "cliVersion")) {
25149
- sessionMeta.cliVersion = metadataString5(observeRuntime, "cliVersion");
25904
+ if (metadataString6(observeRuntime, "cliVersion")) {
25905
+ sessionMeta.cliVersion = metadataString6(observeRuntime, "cliVersion");
25150
25906
  }
25151
- if (metadataString5(observeRuntime, "entrypoint")) {
25152
- sessionMeta.entrypoint = metadataString5(observeRuntime, "entrypoint");
25907
+ if (metadataString6(observeRuntime, "entrypoint")) {
25908
+ sessionMeta.entrypoint = metadataString6(observeRuntime, "entrypoint");
25153
25909
  }
25154
- if (metadataString5(observeRuntime, "originator")) {
25155
- sessionMeta.originator = metadataString5(observeRuntime, "originator");
25910
+ if (metadataString6(observeRuntime, "originator")) {
25911
+ sessionMeta.originator = metadataString6(observeRuntime, "originator");
25156
25912
  }
25157
- if (metadataString5(observeRuntime, "source")) {
25158
- sessionMeta.source = metadataString5(observeRuntime, "source");
25913
+ if (metadataString6(observeRuntime, "source")) {
25914
+ sessionMeta.source = metadataString6(observeRuntime, "source");
25159
25915
  }
25160
- if (metadataString5(observeRuntime, "permissionMode")) {
25161
- sessionMeta.permissionMode = metadataString5(observeRuntime, "permissionMode");
25916
+ if (metadataString6(observeRuntime, "permissionMode")) {
25917
+ sessionMeta.permissionMode = metadataString6(observeRuntime, "permissionMode");
25162
25918
  }
25163
- if (metadataString5(observeRuntime, "approvalPolicy")) {
25164
- sessionMeta.approvalPolicy = metadataString5(observeRuntime, "approvalPolicy");
25919
+ if (metadataString6(observeRuntime, "approvalPolicy")) {
25920
+ sessionMeta.approvalPolicy = metadataString6(observeRuntime, "approvalPolicy");
25165
25921
  }
25166
- if (metadataString5(observeRuntime, "sandbox")) {
25167
- sessionMeta.sandbox = metadataString5(observeRuntime, "sandbox");
25922
+ if (metadataString6(observeRuntime, "sandbox")) {
25923
+ sessionMeta.sandbox = metadataString6(observeRuntime, "sandbox");
25168
25924
  }
25169
- if (metadataString5(observeRuntime, "userType")) {
25170
- sessionMeta.userType = metadataString5(observeRuntime, "userType");
25925
+ if (metadataString6(observeRuntime, "userType")) {
25926
+ sessionMeta.userType = metadataString6(observeRuntime, "userType");
25171
25927
  }
25172
- if (metadataString5(observeRuntime, "effort")) {
25173
- sessionMeta.effort = metadataString5(observeRuntime, "effort");
25928
+ if (metadataString6(observeRuntime, "effort")) {
25929
+ sessionMeta.effort = metadataString6(observeRuntime, "effort");
25174
25930
  }
25175
- if (metadataString5(observeRuntime, "modelProvider")) {
25176
- sessionMeta.modelProvider = metadataString5(observeRuntime, "modelProvider");
25931
+ if (metadataString6(observeRuntime, "modelProvider")) {
25932
+ sessionMeta.modelProvider = metadataString6(observeRuntime, "modelProvider");
25177
25933
  }
25178
- if (metadataString5(observeRuntime, "timezone")) {
25179
- sessionMeta.timezone = metadataString5(observeRuntime, "timezone");
25934
+ if (metadataString6(observeRuntime, "timezone")) {
25935
+ sessionMeta.timezone = metadataString6(observeRuntime, "timezone");
25180
25936
  }
25181
25937
  const usageMeta = {};
25182
25938
  if (metadataNumber(observeUsage, "assistantMessages") !== undefined) {
@@ -25209,14 +25965,14 @@ function buildObserveMetadata(snapshot, sessionStart) {
25209
25965
  if (metadataNumber(observeUsage, "webFetchRequests") !== undefined) {
25210
25966
  usageMeta.webFetchRequests = metadataNumber(observeUsage, "webFetchRequests");
25211
25967
  }
25212
- if (metadataString5(observeUsage, "serviceTier")) {
25213
- usageMeta.serviceTier = metadataString5(observeUsage, "serviceTier");
25968
+ if (metadataString6(observeUsage, "serviceTier")) {
25969
+ usageMeta.serviceTier = metadataString6(observeUsage, "serviceTier");
25214
25970
  }
25215
- if (metadataString5(observeUsage, "speed")) {
25216
- usageMeta.speed = metadataString5(observeUsage, "speed");
25971
+ if (metadataString6(observeUsage, "speed")) {
25972
+ usageMeta.speed = metadataString6(observeUsage, "speed");
25217
25973
  }
25218
- if (metadataString5(observeUsage, "planType")) {
25219
- usageMeta.planType = metadataString5(observeUsage, "planType");
25974
+ if (metadataString6(observeUsage, "planType")) {
25975
+ usageMeta.planType = metadataString6(observeUsage, "planType");
25220
25976
  }
25221
25977
  const observedTopology = metadataRecord3(providerMeta, OBSERVED_HARNESS_TOPOLOGY_META_KEY);
25222
25978
  const hasSessionMeta = Object.keys(sessionMeta).length > 0;
@@ -25293,6 +26049,9 @@ function readHistorySnapshot(candidate) {
25293
26049
  adapterType: candidate.adapterType,
25294
26050
  baseTimestampMs: stat6.mtimeMs
25295
26051
  });
26052
+ if (replay.events.length === 0) {
26053
+ return null;
26054
+ }
25296
26055
  const nextEntry = {
25297
26056
  historyPath: candidate.path,
25298
26057
  snapshot: replay.snapshot,
@@ -30642,6 +31401,9 @@ init_src();
30642
31401
  // packages/runtime/src/drizzle-migrate.ts
30643
31402
  init_support_paths();
30644
31403
  if (false) {}
31404
+
31405
+ // packages/runtime/src/conversations/api.ts
31406
+ init_src();
30645
31407
  // packages/runtime/src/tailscale.ts
30646
31408
  import { readFile as readFile8 } from "fs/promises";
30647
31409
  import { execFile as execFile2 } from "child_process";
@@ -31316,8 +32078,8 @@ function candidateFromValue(key, value, root, now) {
31316
32078
  title: firstMetadataString(root, "title", "attentionTitle"),
31317
32079
  summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
31318
32080
  detail: firstMetadataString(root, "detail", "statusDetail"),
31319
- turnId: metadataString6(root, "turnId"),
31320
- blockId: metadataString6(root, "blockId"),
32081
+ turnId: metadataString7(root, "turnId"),
32082
+ blockId: metadataString7(root, "blockId"),
31321
32083
  version: metadataNumber2(root, "version"),
31322
32084
  updatedAt: metadataTimestamp(root, now),
31323
32085
  severity: nativeSeverity(root)
@@ -31336,8 +32098,8 @@ function candidateFromValue(key, value, root, now) {
31336
32098
  title: firstMetadataString(root, "title", "attentionTitle"),
31337
32099
  summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
31338
32100
  detail: firstMetadataString(root, "detail", "statusDetail"),
31339
- turnId: metadataString6(root, "turnId"),
31340
- blockId: metadataString6(root, "blockId"),
32101
+ turnId: metadataString7(root, "turnId"),
32102
+ blockId: metadataString7(root, "blockId"),
31341
32103
  version: metadataNumber2(root, "version"),
31342
32104
  updatedAt: metadataTimestamp(root, now),
31343
32105
  severity: nativeSeverity(root)
@@ -31366,8 +32128,8 @@ function candidateFromRecord(key, record, now, keyImpliesAttention) {
31366
32128
  title: firstMetadataString(record, "title", "label"),
31367
32129
  summary: firstMetadataString(record, "summary", "message", "reason", "description"),
31368
32130
  detail: firstMetadataString(record, "detail", "statusDetail", "hint"),
31369
- turnId: metadataString6(record, "turnId"),
31370
- blockId: metadataString6(record, "blockId"),
32131
+ turnId: metadataString7(record, "turnId"),
32132
+ blockId: metadataString7(record, "blockId"),
31371
32133
  version: metadataNumber2(record, "version"),
31372
32134
  updatedAt: metadataTimestamp(record, now),
31373
32135
  severity: nativeSeverity(record)
@@ -31387,7 +32149,7 @@ function stableNativeIdPart(value) {
31387
32149
  return stable || "blocked";
31388
32150
  }
31389
32151
  function nativeSeverity(record) {
31390
- const severity = metadataString6(record, "severity")?.toLowerCase();
32152
+ const severity = metadataString7(record, "severity")?.toLowerCase();
31391
32153
  if (severity === "critical" || severity === "warning" || severity === "info") {
31392
32154
  return severity;
31393
32155
  }
@@ -31396,7 +32158,7 @@ function nativeSeverity(record) {
31396
32158
  function metadataRecord4(value) {
31397
32159
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
31398
32160
  }
31399
- function metadataString6(record, key) {
32161
+ function metadataString7(record, key) {
31400
32162
  const value = record?.[key];
31401
32163
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
31402
32164
  }
@@ -31417,7 +32179,7 @@ function metadataTimestamp(record, fallback) {
31417
32179
  }
31418
32180
  function firstMetadataString(record, ...keys) {
31419
32181
  for (const key of keys) {
31420
- const value = metadataString6(record, key);
32182
+ const value = metadataString7(record, key);
31421
32183
  if (value) {
31422
32184
  return value;
31423
32185
  }
@@ -31695,7 +32457,7 @@ function endpointTmuxTarget(endpoint) {
31695
32457
  if (endpoint.transport !== "tmux") {
31696
32458
  return null;
31697
32459
  }
31698
- const sessionName = endpoint.sessionId ?? metadataString7(endpoint, "tmuxSession");
32460
+ const sessionName = endpoint.sessionId ?? metadataString8(endpoint, "tmuxSession");
31699
32461
  if (!sessionName) {
31700
32462
  return null;
31701
32463
  }
@@ -31813,7 +32575,7 @@ function buildStandaloneTmuxNode(tmuxSession) {
31813
32575
  };
31814
32576
  }
31815
32577
  function endpointTitle(endpoint, sessionName) {
31816
- const agentName = metadataString7(endpoint, "agentName") ?? metadataString7(endpoint, "definitionId") ?? endpoint.agentId;
32578
+ const agentName = metadataString8(endpoint, "agentName") ?? metadataString8(endpoint, "definitionId") ?? endpoint.agentId;
31817
32579
  return `${agentName} (${sessionName})`;
31818
32580
  }
31819
32581
  function layoutForIndex(index2) {
@@ -31861,7 +32623,7 @@ function fnv1a(value) {
31861
32623
  }
31862
32624
  return (hash >>> 0).toString(36);
31863
32625
  }
31864
- function metadataString7(endpoint, key) {
32626
+ function metadataString8(endpoint, key) {
31865
32627
  const value = endpoint.metadata?.[key];
31866
32628
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
31867
32629
  }
@@ -32521,7 +33283,7 @@ async function loadOpenScoutWebShellState() {
32521
33283
  }
32522
33284
 
32523
33285
  // packages/web/server/scoutbot-assistant.ts
32524
- import { randomUUID as randomUUID3 } from "crypto";
33286
+ import { randomUUID as randomUUID4 } from "crypto";
32525
33287
  var DEFAULT_MODEL = "gpt-4.1-mini";
32526
33288
  var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
32527
33289
  var PRESENTER_MODEL = "gpt-4o-mini";
@@ -32706,13 +33468,13 @@ function createScoutbotAssistantService(input) {
32706
33468
  }
32707
33469
  const now = Date.now();
32708
33470
  const userMessage = {
32709
- id: `msg_${randomUUID3()}`,
33471
+ id: `msg_${randomUUID4()}`,
32710
33472
  role: "user",
32711
33473
  body: trimmed,
32712
33474
  createdAt: now
32713
33475
  };
32714
33476
  const assistantMessage = {
32715
- id: `msg_${randomUUID3()}`,
33477
+ id: `msg_${randomUUID4()}`,
32716
33478
  role: "assistant",
32717
33479
  body: replyBody,
32718
33480
  createdAt: Date.now()
@@ -33175,7 +33937,7 @@ function parseBriefResponse(raw2, timing, mode = "tour") {
33175
33937
  const steps = normalizeBriefSteps(record.steps, timing.preparedAt, expiresAt);
33176
33938
  const fallbackSummary = raw2.replace(/\s+/g, " ").trim();
33177
33939
  return {
33178
- id: `brf_${randomUUID3()}`,
33940
+ id: `brf_${randomUUID4()}`,
33179
33941
  title: stringField(record.title, "One-minute brief"),
33180
33942
  summary: stringField(record.summary, fallbackSummary || "Scoutbot prepared a current control-plane brief."),
33181
33943
  preparedAt: timing.preparedAt,
@@ -33232,7 +33994,7 @@ function briefFromMarkdown(markdown, timing, mode) {
33232
33994
  observations
33233
33995
  };
33234
33996
  return {
33235
- id: `brf_${randomUUID3()}`,
33997
+ id: `brf_${randomUUID4()}`,
33236
33998
  title,
33237
33999
  summary: headline || fallbackSummary || "Scoutbot prepared a current control-plane brief.",
33238
34000
  preparedAt: timing.preparedAt,
@@ -33542,7 +34304,7 @@ function clampNumber(value, min, max) {
33542
34304
  function createSession(model) {
33543
34305
  const now = Date.now();
33544
34306
  return {
33545
- id: `rgr_${randomUUID3()}`,
34307
+ id: `rgr_${randomUUID4()}`,
33546
34308
  title: "New Scout Session",
33547
34309
  createdAt: now,
33548
34310
  updatedAt: now,
@@ -33738,7 +34500,7 @@ function pruneBriefings(maxRows) {
33738
34500
 
33739
34501
  // packages/web/server/scoutbot-reminders.ts
33740
34502
  init_support_paths();
33741
- import { randomUUID as randomUUID4 } from "crypto";
34503
+ import { randomUUID as randomUUID5 } from "crypto";
33742
34504
  import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
33743
34505
  import { dirname as dirname14, join as join27 } from "path";
33744
34506
 
@@ -33802,7 +34564,7 @@ function createScoutbotReminderStore(input = {}) {
33802
34564
  const title = firstNonEmptyString2(stringValue3(input2.title), titleFromBody(body)) ?? "Reminder";
33803
34565
  const context = recordValue(input2.context);
33804
34566
  const stored = {
33805
- id: `rem_${randomUUID4()}`,
34567
+ id: `rem_${randomUUID5()}`,
33806
34568
  title,
33807
34569
  body,
33808
34570
  status: "scheduled",
@@ -34309,9 +35071,9 @@ function renderHelp() {
34309
35071
  "- `/recent @agent` \u2014 latest messages from an agent",
34310
35072
  "- `/doing @agent` \u2014 active work for an agent",
34311
35073
  "- `/flight <id>` \u2014 flight status by id",
34312
- "- `/steer sid:<session>` \u2014 target this ScoutBot thread at a session",
35074
+ "- `/steer session:<session>` \u2014 target this ScoutBot thread at a session",
34313
35075
  "",
34314
- "Directives can appear anywhere: `eff:low`, `eff:high`, `sid:<session>`."
35076
+ "Directives can appear anywhere: `eff:low`, `eff:high`, `session:<session>`."
34315
35077
  ].join(`
34316
35078
  `);
34317
35079
  }
@@ -34352,7 +35114,7 @@ function renderStatus(snapshot, rawArg = "") {
34352
35114
  function renderSteer(parsed, rawArg) {
34353
35115
  const targetSessionId = parsed.directives.targetSessionId ?? firstSessionishToken(rawArg);
34354
35116
  if (!targetSessionId) {
34355
- return "Usage: `/steer sid:<session>`.";
35117
+ return "Usage: `/steer session:<session>`.";
34356
35118
  }
34357
35119
  return `Steering this ScoutBot thread to session ${targetSessionId}.`;
34358
35120
  }
@@ -34450,7 +35212,7 @@ function firstAgentishToken(value) {
34450
35212
  }
34451
35213
  function firstSessionishToken(value) {
34452
35214
  const token = value.trim().split(/\s+/).find(Boolean);
34453
- const normalized = token?.replace(/^sid:/i, "").replace(/[.,!?;]+$/g, "").trim();
35215
+ const normalized = token?.replace(/^(?:sid|session):/i, "").replace(/[.,!?;]+$/g, "").trim();
34454
35216
  return normalized || null;
34455
35217
  }
34456
35218
  function compact2(value, max = 220) {
@@ -34866,7 +35628,7 @@ async function ensureScoutbotRegistered(baseUrl, snapshot, nodeId, currentDirect
34866
35628
  await postJson(baseUrl, "/v1/actors", actor);
34867
35629
  }
34868
35630
  const existingAgent = snapshot.agents?.[SCOUTBOT_AGENT_ID];
34869
- if (!existingAgent || !hasScoutbotLabels(existingAgent) || !hasCurrentScoutbotAgentConfig(existingAgent)) {
35631
+ if (!existingAgent || !hasCurrentScoutbotAgentRegistration(existingAgent, nodeId)) {
34870
35632
  await postJson(baseUrl, "/v1/agents", agent);
34871
35633
  }
34872
35634
  const existingEndpoint = findScoutbotEndpoint(snapshot, nodeId);
@@ -34966,12 +35728,12 @@ function isInvalidScoutbotSessionEndpoint(endpoint) {
34966
35728
  return lastError.includes("no rollout found for thread id") || lastError.includes("codex_app_server session unavailable") || lastError.includes("session unavailable");
34967
35729
  }
34968
35730
  function hasCurrentScoutbotRuntimeConfig(endpoint) {
34969
- return metadataString8(endpoint.metadata, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt && metadataString8(endpoint.metadata, "reasoningEffort") === SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
35731
+ return metadataString9(endpoint.metadata, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt && metadataString9(endpoint.metadata, "reasoningEffort") === SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
34970
35732
  }
34971
35733
  function scoutbotEndpointTransportSessionId(endpoint) {
34972
35734
  if (isInvalidScoutbotSessionEndpoint(endpoint))
34973
35735
  return null;
34974
- return metadataString8(endpoint.metadata, "threadId") ?? metadataString8(endpoint.metadata, "externalSessionId") ?? endpoint.sessionId?.trim() ?? null;
35736
+ return metadataString9(endpoint.metadata, "threadId") ?? metadataString9(endpoint.metadata, "externalSessionId") ?? endpoint.sessionId?.trim() ?? null;
34975
35737
  }
34976
35738
  async function ensureScoutbotEndpointSession(baseUrl, endpoint, log) {
34977
35739
  try {
@@ -35122,7 +35884,7 @@ async function handleBlock(block, options) {
35122
35884
  }
35123
35885
  options.log.info(`handling thread=${thread.threadId} source=${shortId(message.id)} promptChars=${prompt.length}`);
35124
35886
  if (prefilter) {
35125
- const steerTargetSessionId = prefilter.metadata.matched_rule === "slash.steer" ? metadataString8(prefilter.metadata, "targetSessionId") : undefined;
35887
+ const steerTargetSessionId = prefilter.metadata.matched_rule === "slash.steer" ? metadataString9(prefilter.metadata, "targetSessionId") : undefined;
35126
35888
  if (steerTargetSessionId) {
35127
35889
  await options.threadMap.setThreadTransportSessionId(thread.threadId, steerTargetSessionId);
35128
35890
  options.log.info(`thread=${thread.threadId} steered transportSession=${shortId(steerTargetSessionId)}`);
@@ -35183,7 +35945,7 @@ async function postScoutbotInvocation(input) {
35183
35945
  const prompt = stripMentions(input.message.body);
35184
35946
  const parsed = parseScoutbotDirectives(prompt);
35185
35947
  const requestedReasoningEffort = parsed.directives.reasoningEffort ?? metadataReasoningEffort(input.message.metadata);
35186
- const transportSessionId = parsed.directives.targetSessionId ?? metadataString8(input.message.metadata, "targetSessionId") ?? input.thread.transportSessionId?.trim() ?? null;
35948
+ const transportSessionId = parsed.directives.targetSessionId ?? metadataString9(input.message.metadata, "targetSessionId") ?? input.thread.transportSessionId?.trim() ?? null;
35187
35949
  const task = parsed.body || parsed.messageBody || prompt;
35188
35950
  const context = scoutbotInvocationDirectiveContext({
35189
35951
  parsed,
@@ -35201,7 +35963,7 @@ async function postScoutbotInvocation(input) {
35201
35963
  messageId: input.message.id,
35202
35964
  ...Object.keys(context).length > 0 ? { context } : {},
35203
35965
  execution: {
35204
- session: "existing",
35966
+ session: transportSessionId ? "existing" : "new",
35205
35967
  ...transportSessionId ? { targetSessionId: transportSessionId } : {}
35206
35968
  },
35207
35969
  ensureAwake: true,
@@ -35277,16 +36039,16 @@ function extractMessage(event) {
35277
36039
  return typeof record.id === "string" && typeof record.conversationId === "string" ? record : null;
35278
36040
  }
35279
36041
  function scoutbotReplyTransportSessionId(message) {
35280
- return metadataString8(message.metadata, "targetSessionId") ?? metadataString8(message.metadata, "responderSessionId") ?? metadataString8(metadataObject2(message.metadata, "returnAddress"), "sessionId") ?? null;
36042
+ return metadataString9(message.metadata, "targetSessionId") ?? metadataString9(message.metadata, "responderSessionId") ?? metadataString9(metadataObject2(message.metadata, "returnAddress"), "sessionId") ?? null;
35281
36043
  }
35282
36044
  function isScoutbotAddressedMessage(message) {
35283
36045
  if (message.mentions?.some((mention) => mention.actorId === SCOUTBOT_AGENT_ID))
35284
36046
  return true;
35285
36047
  if (message.audience?.notify?.includes(SCOUTBOT_AGENT_ID))
35286
36048
  return true;
35287
- if (metadataString8(message.metadata, "destinationId") === SCOUTBOT_AGENT_ID)
36049
+ if (metadataString9(message.metadata, "destinationId") === SCOUTBOT_AGENT_ID)
35288
36050
  return true;
35289
- if (metadataString8(message.metadata, "relayTarget") === SCOUTBOT_AGENT_ID)
36051
+ if (metadataString9(message.metadata, "relayTarget") === SCOUTBOT_AGENT_ID)
35290
36052
  return true;
35291
36053
  if (metadataStringArrayIncludes(message.metadata, "relayTargetIds", SCOUTBOT_AGENT_ID))
35292
36054
  return true;
@@ -35310,18 +36072,18 @@ function findScoutbotFlightForMessage(snapshot, messageId, options = {}) {
35310
36072
  if (invocation?.messageId === messageId)
35311
36073
  return true;
35312
36074
  const returnAddress = metadataObject2(flight.metadata, "returnAddress");
35313
- return metadataString8(returnAddress, "replyToMessageId") === messageId;
36075
+ return metadataString9(returnAddress, "replyToMessageId") === messageId;
35314
36076
  });
35315
36077
  return candidates.sort((left, right) => (right.startedAt ?? 0) - (left.startedAt ?? 0))[0] ?? null;
35316
36078
  }
35317
36079
  function isScoutbotDirectDeliveryFlight(flight) {
35318
36080
  if (flight.targetAgentId !== SCOUTBOT_AGENT_ID)
35319
36081
  return false;
35320
- if (metadataString8(flight.metadata, "source") === "scoutbot")
36082
+ if (metadataString9(flight.metadata, "source") === "scoutbot")
35321
36083
  return false;
35322
- if (metadataString8(flight.metadata, "destinationId") === SCOUTBOT_AGENT_ID)
36084
+ if (metadataString9(flight.metadata, "destinationId") === SCOUTBOT_AGENT_ID)
35323
36085
  return true;
35324
- if (metadataString8(flight.metadata, "relayTarget") === SCOUTBOT_AGENT_ID)
36086
+ if (metadataString9(flight.metadata, "relayTarget") === SCOUTBOT_AGENT_ID)
35325
36087
  return true;
35326
36088
  return false;
35327
36089
  }
@@ -35349,15 +36111,18 @@ function hasScoutbotLabels(agent) {
35349
36111
  const labels = new Set(agent.labels ?? []);
35350
36112
  return labels.has("assistant") && labels.has("scout") && labels.has("scoutbot");
35351
36113
  }
36114
+ function hasCurrentScoutbotAgentRegistration(agent, nodeId) {
36115
+ return hasScoutbotLabels(agent) && hasCurrentScoutbotAgentConfig(agent) && agent.homeNodeId === nodeId && agent.authorityNodeId === nodeId && agent.advertiseScope === "local" && agent.wakePolicy === "keep_warm" && agent.metadata?.source === "scoutbot";
36116
+ }
35352
36117
  function hasCurrentScoutbotAgentConfig(agent) {
35353
36118
  const roleConfig = metadataObject2(agent.metadata, "roleConfig");
35354
- return metadataString8(roleConfig, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt;
36119
+ return metadataString9(roleConfig, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt;
35355
36120
  }
35356
36121
  function metadataObject2(metadata, key) {
35357
36122
  const value = metadata?.[key];
35358
36123
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
35359
36124
  }
35360
- function metadataString8(metadata, key) {
36125
+ function metadataString9(metadata, key) {
35361
36126
  const value = metadata?.[key];
35362
36127
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
35363
36128
  }
@@ -35370,7 +36135,7 @@ var SCOUTBOT_REASONING_EFFORTS = new Set([
35370
36135
  "xhigh"
35371
36136
  ]);
35372
36137
  function metadataReasoningEffort(metadata) {
35373
- const value = metadataString8(metadata, "reasoningEffort");
36138
+ const value = metadataString9(metadata, "reasoningEffort");
35374
36139
  return SCOUTBOT_REASONING_EFFORTS.has(value) ? value : undefined;
35375
36140
  }
35376
36141
  function metadataStringArrayIncludes(metadata, key, value) {
@@ -37162,7 +37927,7 @@ function listDirectoryEntries(input) {
37162
37927
  import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
37163
37928
  import { homedir as homedir25 } from "os";
37164
37929
  import { dirname as dirname19, join as join32 } from "path";
37165
- import { randomUUID as randomUUID5 } from "crypto";
37930
+ import { randomUUID as randomUUID6 } from "crypto";
37166
37931
  var VOX_CLIENT_ID = "openscout-web";
37167
37932
  var DEFAULT_VOX_RPC_PORT = 42137;
37168
37933
  var DEFAULT_VOX_RPC_HOST = "127.0.0.1";
@@ -37279,7 +38044,7 @@ async function callVoxRpc(method, params, options = {}) {
37279
38044
  const timeoutMs = options.timeoutMs ?? VOX_RPC_TIMEOUT_MS;
37280
38045
  const port = resolveVoxRpcPort();
37281
38046
  const socket = new WebSocket(`ws://${DEFAULT_VOX_RPC_HOST}:${port}`);
37282
- const id = randomUUID5();
38047
+ const id = randomUUID6();
37283
38048
  return await new Promise((resolve13, reject) => {
37284
38049
  let timeout;
37285
38050
  const cleanup = () => {
@@ -37422,7 +38187,7 @@ function defaultVoiceForModel(providers, modelId) {
37422
38187
 
37423
38188
  // packages/web/server/vantage-handoff.ts
37424
38189
  import { execFile as execFile5, execFileSync as execFileSync6, spawn as spawn6 } from "child_process";
37425
- import { randomUUID as randomUUID6 } from "crypto";
38190
+ import { randomUUID as randomUUID7 } from "crypto";
37426
38191
  import { appendFileSync, existsSync as existsSync24, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
37427
38192
  import { dirname as dirname20, join as join33, resolve as resolve13 } from "path";
37428
38193
  import { promisify as promisify5 } from "util";
@@ -37451,7 +38216,7 @@ async function createOpenScoutVantageHandoff(input) {
37451
38216
  selectedNativeSessionIds,
37452
38217
  now: input.now
37453
38218
  });
37454
- const handoffId = `handoff-${Date.now()}-${randomUUID6().slice(0, 8)}`;
38219
+ const handoffId = `handoff-${Date.now()}-${randomUUID7().slice(0, 8)}`;
37455
38220
  const { handoffPath, setupPath } = writeVantageHandoffFiles({
37456
38221
  handoffId,
37457
38222
  plan,
@@ -38065,8 +38830,9 @@ function inferDirectTargetAgentId(conversationId, session, senderId) {
38065
38830
  const operatorCandidates = new Set([
38066
38831
  senderId.trim(),
38067
38832
  "operator",
38833
+ process.env.OPENSCOUT_OPERATOR_NAME?.trim(),
38068
38834
  ...configuredOperatorActorIds()
38069
- ]);
38835
+ ].filter((candidate) => Boolean(candidate)));
38070
38836
  if (session.agentId) {
38071
38837
  const participants2 = session.participantIds.filter((participantId) => participantId.trim().length > 0);
38072
38838
  if (participants2.length === 0 || participants2.some((participantId) => operatorCandidates.has(participantId))) {
@@ -38090,7 +38856,7 @@ function inferDirectTargetAgentId(conversationId, session, senderId) {
38090
38856
  return participants[0] ?? null;
38091
38857
  }
38092
38858
  }
38093
- const parsedDirectConversation = conversationId ? parseDirectConversationId(conversationId) : null;
38859
+ const parsedDirectConversation = !session && conversationId ? parseDirectConversationId(conversationId) : null;
38094
38860
  if (parsedDirectConversation) {
38095
38861
  return parsedDirectConversation.agentId || null;
38096
38862
  }
@@ -38121,6 +38887,15 @@ function resolveConversationRouting(conversationId) {
38121
38887
  const existingConversationId = session && !directAgentId && !channel ? conversationId ?? null : null;
38122
38888
  return { directAgentId, channel, conversationId: existingConversationId, senderId };
38123
38889
  }
38890
+ function conversationKindAfterMemberMutation(kind, participantIds) {
38891
+ if (kind === "direct" && participantIds.length > 2) {
38892
+ return "group_direct";
38893
+ }
38894
+ if (kind === "group_direct" && participantIds.length <= 2) {
38895
+ return "direct";
38896
+ }
38897
+ return kind;
38898
+ }
38124
38899
  function buildAgentSessionCatalogPayload(input) {
38125
38900
  const runtimeDir = relayAgentRuntimeDirectory(input.agentId);
38126
38901
  const catalog = readSessionCatalogSync(runtimeDir);
@@ -39025,7 +39800,7 @@ async function buildAgentConfigurationSnapshot(currentDirectory) {
39025
39800
  workspaceRoots: settings?.discovery.workspaceRoots ?? [],
39026
39801
  hiddenProjectCount: settings?.discovery.hiddenProjectRoots.length ?? 0,
39027
39802
  defaultHarness: settings?.agents.defaultHarness ?? "claude",
39028
- defaultTransport: settings?.agents.defaultTransport ?? "claude_stream_json",
39803
+ defaultTransport: settings?.agents.defaultTransport ?? "tmux",
39029
39804
  defaultCapabilities: settings?.agents.defaultCapabilities ?? [],
39030
39805
  sessionPrefix: settings?.agents.sessionPrefix ?? "relay"
39031
39806
  },
@@ -39590,6 +40365,7 @@ async function createOpenScoutWebServer(options) {
39590
40365
  systemPrompt: optionalString(body.systemPrompt) ?? existing.systemPrompt,
39591
40366
  launchArgs: stringList(body.launchArgs, existing.launchArgs),
39592
40367
  model,
40368
+ channelEnabled: hasOwn(body, "channelEnabled") ? body.channelEnabled === true : existing.channelEnabled,
39593
40369
  capabilities: stringList(body.capabilities, existing.capabilities)
39594
40370
  });
39595
40371
  if (!nextConfig) {
@@ -39663,7 +40439,14 @@ async function createOpenScoutWebServer(options) {
39663
40439
  activityLimit: parseOptionalPositiveInt(c.req.query("activityLimit")),
39664
40440
  activityLookbackMs: parseOptionalPositiveInt(c.req.query("activityLookbackMs"))
39665
40441
  })));
39666
- app.get("/api/messages", (c) => c.json(queryRecentMessages(parseOptionalPositiveInt(c.req.query("limit"), 80) ?? 80, { conversationId: c.req.query("conversationId") || undefined })));
40442
+ app.get("/api/messages", (c) => {
40443
+ const cId = c.req.query("cId") || c.req.query("conversationId") || undefined;
40444
+ const messages2 = queryRecentMessages(parseOptionalPositiveInt(c.req.query("limit"), 80) ?? 80, { conversationId: cId });
40445
+ return c.json(messages2.map((message) => ({
40446
+ ...message,
40447
+ cId: message.conversationId
40448
+ })));
40449
+ });
39667
40450
  const rawHeuristicsFromRequest = async (c) => {
39668
40451
  const body = await c.req.json().catch(() => null);
39669
40452
  if (body && typeof body === "object" && !Array.isArray(body) && typeof body.raw === "string") {
@@ -39823,14 +40606,24 @@ async function createOpenScoutWebServer(options) {
39823
40606
  sessionId: c.req.query("sessionId") || undefined,
39824
40607
  targetAgentId: c.req.query("targetAgentId") || undefined
39825
40608
  })));
39826
- app.get("/api/conversations", async (c) => {
40609
+ const readCommsList = async (c) => {
39827
40610
  const rawLimit = Number(c.req.query("limit"));
39828
40611
  const rawKinds = c.req.query("kinds")?.trim();
39829
- return c.json(await getScoutConversations({
40612
+ return getScoutConversations({
39830
40613
  query: c.req.query("query") || undefined,
39831
40614
  limit: Number.isFinite(rawLimit) ? Math.min(250, Math.max(1, Math.floor(rawLimit))) : undefined,
39832
40615
  kinds: parseConversationKinds(rawKinds)
39833
- }));
40616
+ });
40617
+ };
40618
+ app.get("/api/comms", async (c) => {
40619
+ const items = await readCommsList(c);
40620
+ return c.json(items.map((item) => ({
40621
+ ...item,
40622
+ cId: item.id
40623
+ })));
40624
+ });
40625
+ app.get("/api/conversations", async (c) => {
40626
+ return c.json(await readCommsList(c));
39834
40627
  });
39835
40628
  app.get("/api/conversations/:id/read-cursors", async (c) => {
39836
40629
  try {
@@ -39860,13 +40653,16 @@ async function createOpenScoutWebServer(options) {
39860
40653
  }
39861
40654
  });
39862
40655
  const writeConversationMembers = async (conversationId, mutate) => {
39863
- const existing = queryConversationDefinitionById(conversationId);
40656
+ const currentSession = querySessionById(conversationId);
40657
+ const canonicalConversationId = currentSession?.id ?? conversationId;
40658
+ const existing = queryConversationDefinitionById(canonicalConversationId);
39864
40659
  if (!existing)
39865
40660
  return null;
39866
40661
  const nextParticipants = mutate(existing.participantIds);
40662
+ const nextKind = conversationKindAfterMemberMutation(existing.kind, nextParticipants);
39867
40663
  await upsertScoutConversation({
39868
40664
  id: existing.id,
39869
- kind: existing.kind,
40665
+ kind: nextKind,
39870
40666
  title: existing.title,
39871
40667
  visibility: existing.visibility,
39872
40668
  shareMode: existing.shareMode,
@@ -39877,7 +40673,11 @@ async function createOpenScoutWebServer(options) {
39877
40673
  ...existing.messageId ? { messageId: existing.messageId } : {},
39878
40674
  ...existing.metadata ? { metadata: existing.metadata } : {}
39879
40675
  });
39880
- return nextParticipants;
40676
+ return {
40677
+ kind: nextKind,
40678
+ participantIds: nextParticipants,
40679
+ session: querySessionById(existing.id)
40680
+ };
39881
40681
  };
39882
40682
  app.post("/api/conversations/:id/members", async (c) => {
39883
40683
  const conversationId = c.req.param("id");
@@ -39888,7 +40688,7 @@ async function createOpenScoutWebServer(options) {
39888
40688
  const next = await writeConversationMembers(conversationId, (current) => Array.from(new Set([...current, actorId])).sort());
39889
40689
  if (!next)
39890
40690
  return c.json({ error: "conversation not found" }, 404);
39891
- return c.json({ ok: true, participantIds: next });
40691
+ return c.json({ ok: true, ...next });
39892
40692
  });
39893
40693
  app.delete("/api/conversations/:id/members/:actorId", async (c) => {
39894
40694
  const conversationId = c.req.param("id");
@@ -39896,7 +40696,7 @@ async function createOpenScoutWebServer(options) {
39896
40696
  const next = await writeConversationMembers(conversationId, (current) => current.filter((id) => id !== actorId));
39897
40697
  if (!next)
39898
40698
  return c.json({ error: "conversation not found" }, 404);
39899
- return c.json({ ok: true, participantIds: next });
40699
+ return c.json({ ok: true, ...next });
39900
40700
  });
39901
40701
  app.get("/api/sessions", (c) => c.json(querySessions()));
39902
40702
  app.get("/api/session-ref/:id", async (c) => {
@@ -40221,11 +41021,12 @@ async function createOpenScoutWebServer(options) {
40221
41021
  }
40222
41022
  });
40223
41023
  app.post("/api/send", async (c) => {
40224
- const { body, conversationId, threadId } = await c.req.json();
41024
+ const { body, cId, conversationId, threadId } = await c.req.json();
40225
41025
  if (!body?.trim()) {
40226
41026
  return c.json({ error: "body is required" }, 400);
40227
41027
  }
40228
- if (!conversationId && scoutbotRunner) {
41028
+ const routeCId = cId ?? conversationId;
41029
+ if (!routeCId && scoutbotRunner) {
40229
41030
  try {
40230
41031
  const result2 = await scoutbotRunner.postOperatorMessage({
40231
41032
  body: body.trim(),
@@ -40240,7 +41041,7 @@ async function createOpenScoutWebServer(options) {
40240
41041
  return c.json({ error: message }, /unknown scoutbot thread/i.test(message) ? 404 : 500);
40241
41042
  }
40242
41043
  }
40243
- const { directAgentId, channel, conversationId: routedConversationId, senderId } = resolveConversationRouting(conversationId);
41044
+ const { directAgentId, channel, conversationId: routedConversationId, senderId } = resolveConversationRouting(routeCId);
40244
41045
  if (directAgentId) {
40245
41046
  if (directAgentId === SCOUTBOT_AGENT_ID && scoutbotRunner) {
40246
41047
  try {
@@ -40290,11 +41091,11 @@ async function createOpenScoutWebServer(options) {
40290
41091
  return c.json(result);
40291
41092
  });
40292
41093
  app.post("/api/ask", async (c) => {
40293
- const { body, conversationId } = await c.req.json();
41094
+ const { body, cId, conversationId } = await c.req.json();
40294
41095
  if (!body?.trim()) {
40295
41096
  return c.json({ error: "body is required" }, 400);
40296
41097
  }
40297
- const { directAgentId, senderId } = resolveConversationRouting(conversationId);
41098
+ const { directAgentId, senderId } = resolveConversationRouting(cId ?? conversationId);
40298
41099
  if (!directAgentId) {
40299
41100
  return c.json({
40300
41101
  error: "ask is only available in a direct conversation with one agent"
@@ -40341,7 +41142,7 @@ async function createOpenScoutWebServer(options) {
40341
41142
  signal: c.req.raw.signal
40342
41143
  }));
40343
41144
  } catch (error) {
40344
- const message = error instanceof Error ? error.message : "Vox speech failed";
41145
+ const message = error instanceof Error ? error.message : "Voice speech failed";
40345
41146
  return c.json({ error: message }, 503);
40346
41147
  }
40347
41148
  });
@@ -40406,28 +41207,28 @@ async function createOpenScoutWebServer(options) {
40406
41207
  }
40407
41208
  });
40408
41209
  app.get("/api/tail/discover", async (c) => {
40409
- const force = c.req.query("force") === "true";
40410
- const snapshot = await getTailDiscovery(force);
40411
- return c.json(snapshot);
41210
+ const url = new URL(scoutBrokerPaths.v1.tailDiscover, resolveScoutBrokerUrl());
41211
+ if (c.req.query("force") === "true" || c.req.query("force") === "1") {
41212
+ url.searchParams.set("force", "1");
41213
+ }
41214
+ const res = await fetch(url);
41215
+ if (!res.ok) {
41216
+ return c.json({ error: `broker tail discovery unavailable (${res.status})` }, 502);
41217
+ }
41218
+ return c.json(await res.json());
40412
41219
  });
40413
41220
  app.get("/api/tail/recent", async (c) => {
40414
41221
  const limitParam = parseOptionalPositiveInt(c.req.query("limit"), 500) ?? 500;
40415
- const bufferedEvents = snapshotRecentEvents(limitParam);
40416
- if (c.req.query("transcripts") !== "true") {
40417
- return c.json({ events: bufferedEvents });
40418
- }
40419
- const transcriptEvents = await readRecentTranscriptEvents(limitParam, {
40420
- perTranscriptLineLimit: Math.min(200, Math.max(50, limitParam))
40421
- });
40422
- const eventsById = new Map;
40423
- for (const event of transcriptEvents) {
40424
- eventsById.set(event.id, event);
41222
+ const url = new URL(scoutBrokerPaths.v1.tailRecent, resolveScoutBrokerUrl());
41223
+ url.searchParams.set("limit", String(limitParam));
41224
+ if (c.req.query("transcripts") === "true" || c.req.query("transcripts") === "1") {
41225
+ url.searchParams.set("transcripts", "true");
40425
41226
  }
40426
- for (const event of bufferedEvents) {
40427
- eventsById.set(event.id, event);
41227
+ const res = await fetch(url);
41228
+ if (!res.ok) {
41229
+ return c.json({ error: `broker tail unavailable (${res.status})` }, 502);
40428
41230
  }
40429
- const events2 = [...eventsById.values()].sort((left, right) => right.ts - left.ts).slice(0, limitParam);
40430
- return c.json({ events: events2 });
41231
+ return c.json(await res.json());
40431
41232
  });
40432
41233
  app.get("/api/broadcast/recent", (c) => {
40433
41234
  const limitParam = parseOptionalPositiveInt(c.req.query("limit"), 50) ?? 50;
@@ -40571,7 +41372,7 @@ function resolveOpenScoutWebApplicationServerIdentity(env = process.env, _machin
40571
41372
  // packages/web/server/relay.ts
40572
41373
  import { mkdir as mkdir9 } from "fs/promises";
40573
41374
  import { join as join35 } from "path";
40574
- import { randomUUID as randomUUID7 } from "crypto";
41375
+ import { randomUUID as randomUUID8 } from "crypto";
40575
41376
  var UPLOAD_DIR = "/tmp/scout-uploads";
40576
41377
  async function handleRelayUpload(req) {
40577
41378
  const { name, data } = await req.json();
@@ -40579,7 +41380,7 @@ async function handleRelayUpload(req) {
40579
41380
  return Response.json({ error: "Missing name or data" }, { status: 400 });
40580
41381
  }
40581
41382
  await mkdir9(UPLOAD_DIR, { recursive: true });
40582
- const filename = `${randomUUID7()}-${name}`;
41383
+ const filename = `${randomUUID8()}-${name}`;
40583
41384
  const filepath = join35(UPLOAD_DIR, filename);
40584
41385
  await Bun.write(filepath, Buffer.from(data, "base64"));
40585
41386
  return Response.json({ path: filepath });