@openscout/scout 0.2.61 → 0.2.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/client/assets/{arc.es-DgeMPL-I.js → arc.es-nBIiEfYv.js} +1 -1
- package/dist/client/assets/index-BdwfgbIm.js +140 -0
- package/dist/client/assets/index-tcdw22Rl.css +1 -0
- package/dist/client/index.html +3 -2
- package/dist/main.mjs +1432 -922
- package/dist/pair-supervisor.mjs +671 -304
- package/dist/scout-control-plane-web.mjs +9353 -8304
- package/package.json +2 -2
- package/dist/client/assets/index-25TJx0iN.js +0 -140
- package/dist/client/assets/index-B1kglsyi.css +0 -1
package/dist/pair-supervisor.mjs
CHANGED
|
@@ -56,6 +56,9 @@ function resolvedPairingConfig() {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// ../../apps/desktop/src/core/pairing/supervisor.ts
|
|
60
|
+
import { spawn as spawn4 } from "child_process";
|
|
61
|
+
|
|
59
62
|
// ../../node_modules/.bun/uqr@0.1.3/node_modules/uqr/dist/index.mjs
|
|
60
63
|
var QrCodeDataType = /* @__PURE__ */ ((QrCodeDataType2) => {
|
|
61
64
|
QrCodeDataType2[QrCodeDataType2["Border"] = -1] = "Border";
|
|
@@ -685,7 +688,7 @@ function renderQRCode(payload) {
|
|
|
685
688
|
// ../../apps/desktop/src/core/pairing/runtime/relay-runtime.ts
|
|
686
689
|
import { execSync } from "child_process";
|
|
687
690
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readdirSync } from "fs";
|
|
688
|
-
import { homedir as homedir2 } from "os";
|
|
691
|
+
import { homedir as homedir2, networkInterfaces } from "os";
|
|
689
692
|
import { join } from "path";
|
|
690
693
|
|
|
691
694
|
// ../../apps/desktop/src/core/pairing/runtime/log.ts
|
|
@@ -716,6 +719,7 @@ function startRelay(port, options = {}) {
|
|
|
716
719
|
const roomByBridgeKey = new Map;
|
|
717
720
|
const server = Bun.serve({
|
|
718
721
|
port,
|
|
722
|
+
hostname: "0.0.0.0",
|
|
719
723
|
...options.tls ? {
|
|
720
724
|
tls: {
|
|
721
725
|
cert: Bun.file(options.tls.cert),
|
|
@@ -974,24 +978,42 @@ function readTailscaleStatus() {
|
|
|
974
978
|
return null;
|
|
975
979
|
}
|
|
976
980
|
}
|
|
977
|
-
function resolveRelayEndpointForTailscaleStatus(port, tailscale) {
|
|
981
|
+
function resolveRelayEndpointForTailscaleStatus(port, tailscale, options = {}) {
|
|
978
982
|
const backendState = tailscale?.backendState?.trim().toLowerCase() ?? "";
|
|
979
983
|
const hostname = tailscale?.dnsName ?? null;
|
|
980
984
|
const tailscaleRunning = backendState === "running" && tailscale?.online !== false && Boolean(hostname);
|
|
981
|
-
const tls =
|
|
982
|
-
|
|
985
|
+
const tls = tailscaleRunning ? options.tls !== undefined ? options.tls : resolveTls(hostname) : null;
|
|
986
|
+
const scheme = tls ? "wss" : "ws";
|
|
987
|
+
const connectUrl = `${scheme}://127.0.0.1:${port}`;
|
|
988
|
+
const localAddress = options.localAddress !== undefined ? normalizedOptionalAddress(options.localAddress) : findLocalNetworkAddress();
|
|
989
|
+
const localRelayUrl = localAddress ? `${scheme}://${localAddress}:${port}` : null;
|
|
990
|
+
const tailnetRelayUrl = tailscaleRunning && hostname ? `${scheme}://${hostname}:${port}` : null;
|
|
991
|
+
const fallbackRelayUrls = tailnetRelayUrl && localRelayUrl ? [tailnetRelayUrl] : [];
|
|
992
|
+
if (localRelayUrl) {
|
|
983
993
|
return {
|
|
984
|
-
relayUrl:
|
|
994
|
+
relayUrl: localRelayUrl,
|
|
995
|
+
connectUrl,
|
|
996
|
+
fallbackRelayUrls,
|
|
997
|
+
options: tls ? { tls } : {}
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
if (tailnetRelayUrl && tls) {
|
|
1001
|
+
return {
|
|
1002
|
+
relayUrl: tailnetRelayUrl,
|
|
1003
|
+
connectUrl,
|
|
1004
|
+
fallbackRelayUrls: [],
|
|
985
1005
|
options: { tls }
|
|
986
1006
|
};
|
|
987
1007
|
}
|
|
988
|
-
if (
|
|
1008
|
+
if (tailnetRelayUrl) {
|
|
989
1009
|
pairingLog.warn("relay", "tailscale is running without TLS; falling back to insecure websocket relay", {
|
|
990
1010
|
hostname,
|
|
991
1011
|
port
|
|
992
1012
|
});
|
|
993
1013
|
return {
|
|
994
|
-
relayUrl:
|
|
1014
|
+
relayUrl: tailnetRelayUrl,
|
|
1015
|
+
connectUrl,
|
|
1016
|
+
fallbackRelayUrls: [],
|
|
995
1017
|
options: {}
|
|
996
1018
|
};
|
|
997
1019
|
}
|
|
@@ -1004,7 +1026,9 @@ function resolveRelayEndpointForTailscaleStatus(port, tailscale) {
|
|
|
1004
1026
|
});
|
|
1005
1027
|
}
|
|
1006
1028
|
return {
|
|
1007
|
-
relayUrl:
|
|
1029
|
+
relayUrl: connectUrl,
|
|
1030
|
+
connectUrl,
|
|
1031
|
+
fallbackRelayUrls: [],
|
|
1008
1032
|
options: {}
|
|
1009
1033
|
};
|
|
1010
1034
|
}
|
|
@@ -1057,16 +1081,87 @@ function resolveRelayEndpoint(port) {
|
|
|
1057
1081
|
}
|
|
1058
1082
|
function startManagedRelay(port = 7889) {
|
|
1059
1083
|
const endpoint = resolveRelayEndpoint(port);
|
|
1060
|
-
pairingLog.info("relay", "starting managed relay", {
|
|
1084
|
+
pairingLog.info("relay", "starting managed relay", {
|
|
1085
|
+
relay: endpoint.relayUrl,
|
|
1086
|
+
connectUrl: endpoint.connectUrl,
|
|
1087
|
+
fallbackRelayUrls: endpoint.fallbackRelayUrls,
|
|
1088
|
+
port
|
|
1089
|
+
});
|
|
1061
1090
|
const relay = startRelay(port, endpoint.options);
|
|
1062
1091
|
return {
|
|
1063
1092
|
relayUrl: endpoint.relayUrl,
|
|
1093
|
+
connectUrl: endpoint.connectUrl,
|
|
1094
|
+
fallbackRelayUrls: endpoint.fallbackRelayUrls,
|
|
1064
1095
|
stop() {
|
|
1065
1096
|
pairingLog.info("relay", "stopping managed relay", { relay: endpoint.relayUrl, port });
|
|
1066
1097
|
relay.stop();
|
|
1067
1098
|
}
|
|
1068
1099
|
};
|
|
1069
1100
|
}
|
|
1101
|
+
function normalizedOptionalAddress(address) {
|
|
1102
|
+
const trimmed = address?.trim();
|
|
1103
|
+
return trimmed ? trimmed : null;
|
|
1104
|
+
}
|
|
1105
|
+
function findLocalNetworkAddress() {
|
|
1106
|
+
const candidates = [];
|
|
1107
|
+
const interfaces = networkInterfaces();
|
|
1108
|
+
for (const [name, entries] of Object.entries(interfaces)) {
|
|
1109
|
+
for (const entry of entries ?? []) {
|
|
1110
|
+
if (entry.internal || entry.family !== "IPv4") {
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (!isPrivateIPv4(entry.address) || isLinkLocalIPv4(entry.address) || isTailscaleIPv4(entry.address)) {
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
candidates.push({ name, address: entry.address });
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
candidates.sort((left, right) => {
|
|
1120
|
+
const leftScore = localInterfaceScore(left.name);
|
|
1121
|
+
const rightScore = localInterfaceScore(right.name);
|
|
1122
|
+
if (leftScore !== rightScore) {
|
|
1123
|
+
return leftScore - rightScore;
|
|
1124
|
+
}
|
|
1125
|
+
return left.address.localeCompare(right.address);
|
|
1126
|
+
});
|
|
1127
|
+
return candidates[0]?.address ?? null;
|
|
1128
|
+
}
|
|
1129
|
+
function localInterfaceScore(name) {
|
|
1130
|
+
if (/^en\d+$/i.test(name)) {
|
|
1131
|
+
return 0;
|
|
1132
|
+
}
|
|
1133
|
+
if (/^bridge\d*$/i.test(name)) {
|
|
1134
|
+
return 2;
|
|
1135
|
+
}
|
|
1136
|
+
return 1;
|
|
1137
|
+
}
|
|
1138
|
+
function isPrivateIPv4(address) {
|
|
1139
|
+
const octets = parseIPv4(address);
|
|
1140
|
+
if (!octets) {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
const [first, second] = octets;
|
|
1144
|
+
return first === 10 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168;
|
|
1145
|
+
}
|
|
1146
|
+
function isLinkLocalIPv4(address) {
|
|
1147
|
+
const octets = parseIPv4(address);
|
|
1148
|
+
return Boolean(octets && octets[0] === 169 && octets[1] === 254);
|
|
1149
|
+
}
|
|
1150
|
+
function isTailscaleIPv4(address) {
|
|
1151
|
+
const octets = parseIPv4(address);
|
|
1152
|
+
return Boolean(octets && octets[0] === 100 && octets[1] >= 64 && octets[1] <= 127);
|
|
1153
|
+
}
|
|
1154
|
+
function parseIPv4(address) {
|
|
1155
|
+
const octets = address.split(".");
|
|
1156
|
+
if (octets.length !== 4) {
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
const numbers = octets.map((octet) => Number(octet));
|
|
1160
|
+
if (numbers.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
return numbers;
|
|
1164
|
+
}
|
|
1070
1165
|
|
|
1071
1166
|
// ../../apps/desktop/src/core/pairing/runtime/runtime-state.ts
|
|
1072
1167
|
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -3152,15 +3247,30 @@ function isTrustedPeer(publicKeyHex) {
|
|
|
3152
3247
|
}
|
|
3153
3248
|
var QR_VERSION = 1;
|
|
3154
3249
|
var QR_EXPIRY_MS = 5 * 60 * 1000;
|
|
3155
|
-
function createQRPayload(bridgePublicKey, relayUrl) {
|
|
3250
|
+
function createQRPayload(bridgePublicKey, relayUrl, fallbackRelayUrls = []) {
|
|
3251
|
+
const fallbackRelays = normalizedRelayUrls(fallbackRelayUrls, relayUrl);
|
|
3156
3252
|
return {
|
|
3157
3253
|
v: QR_VERSION,
|
|
3158
3254
|
relay: relayUrl,
|
|
3255
|
+
...fallbackRelays.length > 0 ? { fallbackRelays } : {},
|
|
3159
3256
|
room: crypto.randomUUID(),
|
|
3160
3257
|
publicKey: bytesToHex2(bridgePublicKey),
|
|
3161
3258
|
expiresAt: Date.now() + QR_EXPIRY_MS
|
|
3162
3259
|
};
|
|
3163
3260
|
}
|
|
3261
|
+
function normalizedRelayUrls(urls, primaryRelayUrl) {
|
|
3262
|
+
const seen = new Set([primaryRelayUrl.trim()]);
|
|
3263
|
+
const normalized = [];
|
|
3264
|
+
for (const url of urls) {
|
|
3265
|
+
const trimmed = url.trim();
|
|
3266
|
+
if (!trimmed || seen.has(trimmed)) {
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
seen.add(trimmed);
|
|
3270
|
+
normalized.push(trimmed);
|
|
3271
|
+
}
|
|
3272
|
+
return normalized;
|
|
3273
|
+
}
|
|
3164
3274
|
function bytesToHex2(bytes) {
|
|
3165
3275
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3166
3276
|
}
|
|
@@ -3394,7 +3504,7 @@ function writeJsonAtomically(filePath, value) {
|
|
|
3394
3504
|
}
|
|
3395
3505
|
|
|
3396
3506
|
// ../../apps/desktop/src/core/pairing/runtime/runtime.ts
|
|
3397
|
-
import { homedir as
|
|
3507
|
+
import { homedir as homedir19 } from "os";
|
|
3398
3508
|
// ../agent-sessions/src/protocol/adapter.ts
|
|
3399
3509
|
class BaseAdapter {
|
|
3400
3510
|
config;
|
|
@@ -4026,6 +4136,15 @@ function stringifyUnknown(value) {
|
|
|
4026
4136
|
return String(value);
|
|
4027
4137
|
}
|
|
4028
4138
|
}
|
|
4139
|
+
function isRecord(value) {
|
|
4140
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4141
|
+
}
|
|
4142
|
+
function maybeString(value) {
|
|
4143
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
4144
|
+
}
|
|
4145
|
+
function maybeNumber(value) {
|
|
4146
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
4147
|
+
}
|
|
4029
4148
|
function renderToolResultContent(content) {
|
|
4030
4149
|
if (typeof content === "string") {
|
|
4031
4150
|
return content;
|
|
@@ -4081,6 +4200,7 @@ class ClaudeCodeHistoryParser {
|
|
|
4081
4200
|
blockById = new Map;
|
|
4082
4201
|
activeStreamBlocks = new Map;
|
|
4083
4202
|
sawStreamTextThisTurn = false;
|
|
4203
|
+
assistantUsageByMessageId = new Map;
|
|
4084
4204
|
constructor(session, baseTimestampMs) {
|
|
4085
4205
|
this.session = session;
|
|
4086
4206
|
this.baseTimestampMs = baseTimestampMs;
|
|
@@ -4090,6 +4210,7 @@ class ClaudeCodeHistoryParser {
|
|
|
4090
4210
|
let parsedLineCount = 0;
|
|
4091
4211
|
let skippedLineCount = 0;
|
|
4092
4212
|
let lineCount = 0;
|
|
4213
|
+
let lastCapturedAt = this.baseTimestampMs;
|
|
4093
4214
|
for (let index = 0;index < lines.length; index += 1) {
|
|
4094
4215
|
const rawLine = lines[index];
|
|
4095
4216
|
const trimmed = rawLine.trim();
|
|
@@ -4110,12 +4231,19 @@ class ClaudeCodeHistoryParser {
|
|
|
4110
4231
|
continue;
|
|
4111
4232
|
}
|
|
4112
4233
|
const capturedAt = extractRecordTimestamp(record) ?? this.baseTimestampMs + index;
|
|
4234
|
+
lastCapturedAt = capturedAt;
|
|
4113
4235
|
if (this.handleRecord(record, capturedAt)) {
|
|
4114
4236
|
parsedLineCount += 1;
|
|
4115
4237
|
} else {
|
|
4116
4238
|
skippedLineCount += 1;
|
|
4117
4239
|
}
|
|
4118
4240
|
}
|
|
4241
|
+
if (this.persistObserveUsageMetadata()) {
|
|
4242
|
+
this.emitEvent(lastCapturedAt, {
|
|
4243
|
+
event: "session:update",
|
|
4244
|
+
session: { ...this.session }
|
|
4245
|
+
});
|
|
4246
|
+
}
|
|
4119
4247
|
return {
|
|
4120
4248
|
events: this.events,
|
|
4121
4249
|
lineCount,
|
|
@@ -4124,6 +4252,7 @@ class ClaudeCodeHistoryParser {
|
|
|
4124
4252
|
};
|
|
4125
4253
|
}
|
|
4126
4254
|
handleRecord(record, capturedAt) {
|
|
4255
|
+
this.captureRecordMetadata(record);
|
|
4127
4256
|
const type = typeof record.type === "string" ? record.type : null;
|
|
4128
4257
|
if (!type) {
|
|
4129
4258
|
return false;
|
|
@@ -4133,7 +4262,7 @@ class ClaudeCodeHistoryParser {
|
|
|
4133
4262
|
this.handleSystem(record, capturedAt);
|
|
4134
4263
|
return true;
|
|
4135
4264
|
case "user":
|
|
4136
|
-
this.
|
|
4265
|
+
this.handleUser(record, capturedAt);
|
|
4137
4266
|
return true;
|
|
4138
4267
|
case "assistant":
|
|
4139
4268
|
this.handleAssistant(record, capturedAt);
|
|
@@ -4157,6 +4286,130 @@ class ClaudeCodeHistoryParser {
|
|
|
4157
4286
|
return false;
|
|
4158
4287
|
}
|
|
4159
4288
|
}
|
|
4289
|
+
captureRecordMetadata(record) {
|
|
4290
|
+
const runtime = this.ensureObserveMetaRecord("observeRuntime");
|
|
4291
|
+
this.assignObserveString(runtime, "entrypoint", record.entrypoint);
|
|
4292
|
+
this.assignObserveString(runtime, "cliVersion", record.version);
|
|
4293
|
+
this.assignObserveString(runtime, "gitBranch", record.gitBranch);
|
|
4294
|
+
this.assignObserveString(runtime, "permissionMode", record.permissionMode);
|
|
4295
|
+
this.assignObserveString(runtime, "userType", record.userType);
|
|
4296
|
+
const recordCwd = maybeString(record.cwd);
|
|
4297
|
+
if (recordCwd && !this.session.cwd) {
|
|
4298
|
+
this.session.cwd = recordCwd;
|
|
4299
|
+
}
|
|
4300
|
+
const message = isRecord(record.message) ? record.message : null;
|
|
4301
|
+
const model = maybeString(message?.model);
|
|
4302
|
+
if (model && !this.session.model) {
|
|
4303
|
+
this.session.model = model;
|
|
4304
|
+
}
|
|
4305
|
+
const usage = this.readClaudeUsageEntry(record);
|
|
4306
|
+
if (!usage) {
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
const messageId = maybeString(message?.id) ?? maybeString(record.requestId) ?? maybeString(record.uuid);
|
|
4310
|
+
if (!messageId) {
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
this.assistantUsageByMessageId.set(messageId, usage);
|
|
4314
|
+
}
|
|
4315
|
+
readClaudeUsageEntry(record) {
|
|
4316
|
+
const type = maybeString(record.type);
|
|
4317
|
+
const message = isRecord(record.message) ? record.message : null;
|
|
4318
|
+
const role = maybeString(message?.role);
|
|
4319
|
+
if (type !== "assistant" && role !== "assistant") {
|
|
4320
|
+
return null;
|
|
4321
|
+
}
|
|
4322
|
+
const usage = isRecord(message?.usage) ? message.usage : null;
|
|
4323
|
+
const serverToolUse = isRecord(usage?.server_tool_use) ? usage.server_tool_use : null;
|
|
4324
|
+
const entry = {
|
|
4325
|
+
inputTokens: maybeNumber(usage?.input_tokens) ?? 0,
|
|
4326
|
+
outputTokens: maybeNumber(usage?.output_tokens) ?? 0,
|
|
4327
|
+
cacheReadInputTokens: maybeNumber(usage?.cache_read_input_tokens) ?? 0,
|
|
4328
|
+
cacheCreationInputTokens: maybeNumber(usage?.cache_creation_input_tokens) ?? 0,
|
|
4329
|
+
webSearchRequests: maybeNumber(serverToolUse?.web_search_requests) ?? 0,
|
|
4330
|
+
webFetchRequests: maybeNumber(serverToolUse?.web_fetch_requests) ?? 0,
|
|
4331
|
+
...maybeString(message?.service_tier) ? { serviceTier: maybeString(message?.service_tier) } : {},
|
|
4332
|
+
...maybeString(message?.speed) ? { speed: maybeString(message?.speed) } : {}
|
|
4333
|
+
};
|
|
4334
|
+
const hasUsage = entry.inputTokens > 0 || entry.outputTokens > 0 || entry.cacheReadInputTokens > 0 || entry.cacheCreationInputTokens > 0 || entry.webSearchRequests > 0 || entry.webFetchRequests > 0 || Boolean(entry.serviceTier) || Boolean(entry.speed);
|
|
4335
|
+
return hasUsage ? entry : null;
|
|
4336
|
+
}
|
|
4337
|
+
persistObserveUsageMetadata() {
|
|
4338
|
+
if (this.assistantUsageByMessageId.size === 0) {
|
|
4339
|
+
return false;
|
|
4340
|
+
}
|
|
4341
|
+
const usage = this.ensureObserveMetaRecord("observeUsage");
|
|
4342
|
+
let inputTokens = 0;
|
|
4343
|
+
let outputTokens = 0;
|
|
4344
|
+
let cacheReadInputTokens = 0;
|
|
4345
|
+
let cacheCreationInputTokens = 0;
|
|
4346
|
+
let webSearchRequests = 0;
|
|
4347
|
+
let webFetchRequests = 0;
|
|
4348
|
+
let serviceTier;
|
|
4349
|
+
let speed;
|
|
4350
|
+
for (const entry of this.assistantUsageByMessageId.values()) {
|
|
4351
|
+
inputTokens += entry.inputTokens;
|
|
4352
|
+
outputTokens += entry.outputTokens;
|
|
4353
|
+
cacheReadInputTokens += entry.cacheReadInputTokens;
|
|
4354
|
+
cacheCreationInputTokens += entry.cacheCreationInputTokens;
|
|
4355
|
+
webSearchRequests += entry.webSearchRequests;
|
|
4356
|
+
webFetchRequests += entry.webFetchRequests;
|
|
4357
|
+
if (entry.serviceTier) {
|
|
4358
|
+
serviceTier = entry.serviceTier;
|
|
4359
|
+
}
|
|
4360
|
+
if (entry.speed) {
|
|
4361
|
+
speed = entry.speed;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
let changed = false;
|
|
4365
|
+
const assignNumber = (key, value) => {
|
|
4366
|
+
if (usage[key] !== value) {
|
|
4367
|
+
usage[key] = value;
|
|
4368
|
+
changed = true;
|
|
4369
|
+
}
|
|
4370
|
+
};
|
|
4371
|
+
const assignString = (key, value) => {
|
|
4372
|
+
if (usage[key] !== value) {
|
|
4373
|
+
usage[key] = value;
|
|
4374
|
+
changed = true;
|
|
4375
|
+
}
|
|
4376
|
+
};
|
|
4377
|
+
assignNumber("assistantMessages", this.assistantUsageByMessageId.size);
|
|
4378
|
+
if (inputTokens > 0)
|
|
4379
|
+
assignNumber("inputTokens", inputTokens);
|
|
4380
|
+
if (outputTokens > 0)
|
|
4381
|
+
assignNumber("outputTokens", outputTokens);
|
|
4382
|
+
if (cacheReadInputTokens > 0)
|
|
4383
|
+
assignNumber("cacheReadInputTokens", cacheReadInputTokens);
|
|
4384
|
+
if (cacheCreationInputTokens > 0)
|
|
4385
|
+
assignNumber("cacheCreationInputTokens", cacheCreationInputTokens);
|
|
4386
|
+
if (webSearchRequests > 0)
|
|
4387
|
+
assignNumber("webSearchRequests", webSearchRequests);
|
|
4388
|
+
if (webFetchRequests > 0)
|
|
4389
|
+
assignNumber("webFetchRequests", webFetchRequests);
|
|
4390
|
+
if (serviceTier)
|
|
4391
|
+
assignString("serviceTier", serviceTier);
|
|
4392
|
+
if (speed)
|
|
4393
|
+
assignString("speed", speed);
|
|
4394
|
+
return changed;
|
|
4395
|
+
}
|
|
4396
|
+
ensureObserveMetaRecord(key) {
|
|
4397
|
+
const providerMeta = isRecord(this.session.providerMeta) ? this.session.providerMeta : {};
|
|
4398
|
+
this.session.providerMeta = providerMeta;
|
|
4399
|
+
const existing = providerMeta[key];
|
|
4400
|
+
if (isRecord(existing)) {
|
|
4401
|
+
return existing;
|
|
4402
|
+
}
|
|
4403
|
+
const next = {};
|
|
4404
|
+
providerMeta[key] = next;
|
|
4405
|
+
return next;
|
|
4406
|
+
}
|
|
4407
|
+
assignObserveString(target, key, value) {
|
|
4408
|
+
const next = maybeString(value);
|
|
4409
|
+
if (next) {
|
|
4410
|
+
target[key] = next;
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4160
4413
|
handleSystem(record, capturedAt) {
|
|
4161
4414
|
if (record.subtype !== "init") {
|
|
4162
4415
|
return;
|
|
@@ -4186,19 +4439,41 @@ class ClaudeCodeHistoryParser {
|
|
|
4186
4439
|
});
|
|
4187
4440
|
}
|
|
4188
4441
|
}
|
|
4442
|
+
handleUser(record, capturedAt) {
|
|
4443
|
+
const message = record.message && typeof record.message === "object" ? record.message : null;
|
|
4444
|
+
const content = message?.content;
|
|
4445
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
4446
|
+
const toolResults = content.filter((entry) => {
|
|
4447
|
+
return !!entry && typeof entry === "object" && !Array.isArray(entry) && entry.type === "tool_result";
|
|
4448
|
+
});
|
|
4449
|
+
if (toolResults.length === content.length) {
|
|
4450
|
+
for (const toolResult of toolResults) {
|
|
4451
|
+
this.handleToolResult({
|
|
4452
|
+
...toolResult,
|
|
4453
|
+
tool_use_id: typeof toolResult.tool_use_id === "string" ? toolResult.tool_use_id : toolResult.id,
|
|
4454
|
+
is_error: toolResult.is_error === true
|
|
4455
|
+
}, capturedAt);
|
|
4456
|
+
}
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
this.startTurn(capturedAt);
|
|
4461
|
+
}
|
|
4189
4462
|
handleAssistant(record, capturedAt) {
|
|
4190
4463
|
const turn = this.ensureTurn(capturedAt);
|
|
4191
|
-
if (this.sawStreamTextThisTurn) {
|
|
4192
|
-
return;
|
|
4193
|
-
}
|
|
4194
4464
|
const content = record.message && typeof record.message === "object" ? record.message.content : record.content;
|
|
4195
4465
|
if (!Array.isArray(content)) {
|
|
4466
|
+
this.maybeEndTurnFromAssistant(record, capturedAt);
|
|
4196
4467
|
return;
|
|
4197
4468
|
}
|
|
4469
|
+
const skipTextBlocks = this.sawStreamTextThisTurn;
|
|
4198
4470
|
for (const part of content) {
|
|
4199
4471
|
const contentPart = part;
|
|
4200
4472
|
const contentType = typeof contentPart.type === "string" ? contentPart.type : "";
|
|
4201
4473
|
if (contentType === "thinking" || contentType === "reasoning") {
|
|
4474
|
+
if (skipTextBlocks) {
|
|
4475
|
+
continue;
|
|
4476
|
+
}
|
|
4202
4477
|
const block = this.startBlock(turn, capturedAt, {
|
|
4203
4478
|
type: "reasoning",
|
|
4204
4479
|
text: typeof contentPart.thinking === "string" ? contentPart.thinking : typeof contentPart.text === "string" ? contentPart.text : "",
|
|
@@ -4206,14 +4481,20 @@ class ClaudeCodeHistoryParser {
|
|
|
4206
4481
|
});
|
|
4207
4482
|
this.emitBlockEnd(capturedAt, turn, block, "completed");
|
|
4208
4483
|
} else if (contentType === "text") {
|
|
4484
|
+
if (skipTextBlocks) {
|
|
4485
|
+
continue;
|
|
4486
|
+
}
|
|
4209
4487
|
const block = this.startBlock(turn, capturedAt, {
|
|
4210
4488
|
type: "text",
|
|
4211
4489
|
text: typeof contentPart.text === "string" ? contentPart.text : "",
|
|
4212
4490
|
status: "completed"
|
|
4213
4491
|
});
|
|
4214
4492
|
this.emitBlockEnd(capturedAt, turn, block, "completed");
|
|
4493
|
+
} else if (contentType === "tool_use") {
|
|
4494
|
+
this.handleToolUse(contentPart, capturedAt);
|
|
4215
4495
|
}
|
|
4216
4496
|
}
|
|
4497
|
+
this.maybeEndTurnFromAssistant(record, capturedAt);
|
|
4217
4498
|
}
|
|
4218
4499
|
handleStreamEvent(record, capturedAt) {
|
|
4219
4500
|
const turn = this.ensureTurn(capturedAt);
|
|
@@ -4435,6 +4716,12 @@ class ClaudeCodeHistoryParser {
|
|
|
4435
4716
|
this.emitError(turn, capturedAt, message);
|
|
4436
4717
|
this.endTurn("failed", capturedAt);
|
|
4437
4718
|
}
|
|
4719
|
+
maybeEndTurnFromAssistant(record, capturedAt) {
|
|
4720
|
+
const stopReason = record.message && typeof record.message === "object" ? record.message.stop_reason : record.stop_reason;
|
|
4721
|
+
if (stopReason === "end_turn") {
|
|
4722
|
+
this.endTurn("completed", capturedAt);
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4438
4725
|
startTurn(capturedAt) {
|
|
4439
4726
|
if (this.currentTurn) {
|
|
4440
4727
|
this.endTurn("stopped", capturedAt);
|
|
@@ -8539,11 +8826,11 @@ function formatTimestamp(value) {
|
|
|
8539
8826
|
// ../../apps/desktop/src/core/pairing/runtime/bridge/server.ts
|
|
8540
8827
|
import { readdirSync as readdirSync3, readFileSync as readFileSync9, realpathSync, statSync as statSync3 } from "fs";
|
|
8541
8828
|
import { execSync as execSync3 } from "child_process";
|
|
8542
|
-
import { basename as
|
|
8543
|
-
import { homedir as
|
|
8829
|
+
import { basename as basename7, isAbsolute as isAbsolute3, join as join18, relative as relative2 } from "path";
|
|
8830
|
+
import { homedir as homedir16 } from "os";
|
|
8544
8831
|
|
|
8545
8832
|
// ../../apps/desktop/src/core/mobile/service.ts
|
|
8546
|
-
import { basename as
|
|
8833
|
+
import { basename as basename6, resolve as resolve7 } from "path";
|
|
8547
8834
|
|
|
8548
8835
|
// ../runtime/src/harness-catalog.ts
|
|
8549
8836
|
import { execFileSync } from "child_process";
|
|
@@ -10154,41 +10441,6 @@ function diagnoseAgentIdentity(identity, candidates) {
|
|
|
10154
10441
|
var parseAgentSelector = parseAgentIdentity;
|
|
10155
10442
|
var formatAgentSelector = formatAgentIdentity;
|
|
10156
10443
|
var resolveAgentSelector = resolveAgentIdentity;
|
|
10157
|
-
// ../protocol/dist/scout-agent-card.js
|
|
10158
|
-
function buildScoutReturnAddress(input) {
|
|
10159
|
-
const next = {
|
|
10160
|
-
actorId: input.actorId,
|
|
10161
|
-
handle: input.handle.trim()
|
|
10162
|
-
};
|
|
10163
|
-
if (input.displayName?.trim()) {
|
|
10164
|
-
next.displayName = input.displayName.trim();
|
|
10165
|
-
}
|
|
10166
|
-
if (input.selector?.trim()) {
|
|
10167
|
-
next.selector = input.selector.trim();
|
|
10168
|
-
}
|
|
10169
|
-
if (input.defaultSelector?.trim()) {
|
|
10170
|
-
next.defaultSelector = input.defaultSelector.trim();
|
|
10171
|
-
}
|
|
10172
|
-
if (input.conversationId?.trim()) {
|
|
10173
|
-
next.conversationId = input.conversationId.trim();
|
|
10174
|
-
}
|
|
10175
|
-
if (input.replyToMessageId?.trim()) {
|
|
10176
|
-
next.replyToMessageId = input.replyToMessageId.trim();
|
|
10177
|
-
}
|
|
10178
|
-
if (input.nodeId?.trim()) {
|
|
10179
|
-
next.nodeId = input.nodeId.trim();
|
|
10180
|
-
}
|
|
10181
|
-
if (input.projectRoot?.trim()) {
|
|
10182
|
-
next.projectRoot = input.projectRoot.trim();
|
|
10183
|
-
}
|
|
10184
|
-
if (input.sessionId?.trim()) {
|
|
10185
|
-
next.sessionId = input.sessionId.trim();
|
|
10186
|
-
}
|
|
10187
|
-
if (input.metadata && Object.keys(input.metadata).length > 0) {
|
|
10188
|
-
next.metadata = input.metadata;
|
|
10189
|
-
}
|
|
10190
|
-
return next;
|
|
10191
|
-
}
|
|
10192
10444
|
// ../runtime/src/user-project-hints.ts
|
|
10193
10445
|
import { readdir, readFile as readFile3, stat } from "fs/promises";
|
|
10194
10446
|
import { homedir as homedir11 } from "os";
|
|
@@ -12239,9 +12491,9 @@ async function ensureRelayAgentConfigured(value, options = {}) {
|
|
|
12239
12491
|
|
|
12240
12492
|
// ../runtime/src/local-agents.ts
|
|
12241
12493
|
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
12242
|
-
import { existsSync as
|
|
12494
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8 } from "fs";
|
|
12243
12495
|
import { mkdir as mkdir6, rm as rm5, stat as stat3, writeFile as writeFile6 } from "fs/promises";
|
|
12244
|
-
import { basename as
|
|
12496
|
+
import { basename as basename5, dirname as dirname7, join as join16, resolve as resolve6 } from "path";
|
|
12245
12497
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12246
12498
|
|
|
12247
12499
|
// ../runtime/src/claude-stream-json.ts
|
|
@@ -13585,10 +13837,116 @@ function buildCollaborationContractPrompt(agentId) {
|
|
|
13585
13837
|
|
|
13586
13838
|
// ../runtime/src/broker-service.ts
|
|
13587
13839
|
import { spawnSync } from "child_process";
|
|
13588
|
-
import { existsSync as
|
|
13840
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
13841
|
+
import { homedir as homedir14 } from "os";
|
|
13842
|
+
import { dirname as dirname6, join as join15, resolve as resolve5 } from "path";
|
|
13843
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13844
|
+
|
|
13845
|
+
// ../runtime/src/tool-resolution.ts
|
|
13846
|
+
import { accessSync as accessSync2, constants as constants4, existsSync as existsSync12 } from "fs";
|
|
13589
13847
|
import { homedir as homedir13 } from "os";
|
|
13590
13848
|
import { basename as basename3, dirname as dirname5, join as join14, resolve as resolve4 } from "path";
|
|
13591
|
-
|
|
13849
|
+
var DEFAULT_COMMON_EXECUTABLE_DIRECTORIES = [
|
|
13850
|
+
join14(homedir13(), ".bun", "bin"),
|
|
13851
|
+
"/opt/homebrew/bin",
|
|
13852
|
+
"/usr/local/bin"
|
|
13853
|
+
];
|
|
13854
|
+
function expandHomePath3(value, env = process.env) {
|
|
13855
|
+
const home = env.HOME ?? homedir13();
|
|
13856
|
+
if (value === "~") {
|
|
13857
|
+
return home;
|
|
13858
|
+
}
|
|
13859
|
+
if (value.startsWith("~/")) {
|
|
13860
|
+
return join14(home, value.slice(2));
|
|
13861
|
+
}
|
|
13862
|
+
return value;
|
|
13863
|
+
}
|
|
13864
|
+
function isExecutablePath(candidate) {
|
|
13865
|
+
if (!candidate) {
|
|
13866
|
+
return false;
|
|
13867
|
+
}
|
|
13868
|
+
try {
|
|
13869
|
+
accessSync2(candidate, constants4.X_OK);
|
|
13870
|
+
return true;
|
|
13871
|
+
} catch {
|
|
13872
|
+
return false;
|
|
13873
|
+
}
|
|
13874
|
+
}
|
|
13875
|
+
function splitPathEntries(env = process.env) {
|
|
13876
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
13877
|
+
return (env.PATH ?? "").split(separator).filter(Boolean);
|
|
13878
|
+
}
|
|
13879
|
+
function dedupeDirectories(values, env) {
|
|
13880
|
+
const seen = new Set;
|
|
13881
|
+
const resolvedEntries = [];
|
|
13882
|
+
for (const value of values) {
|
|
13883
|
+
const normalized = resolve4(expandHomePath3(value, env));
|
|
13884
|
+
if (seen.has(normalized)) {
|
|
13885
|
+
continue;
|
|
13886
|
+
}
|
|
13887
|
+
seen.add(normalized);
|
|
13888
|
+
resolvedEntries.push(normalized);
|
|
13889
|
+
}
|
|
13890
|
+
return resolvedEntries;
|
|
13891
|
+
}
|
|
13892
|
+
function resolveExecutableFromSearch(options) {
|
|
13893
|
+
const env = options.env ?? process.env;
|
|
13894
|
+
const envKeys = options.envKeys ?? [];
|
|
13895
|
+
for (const envKey of envKeys) {
|
|
13896
|
+
const explicit = env[envKey]?.trim();
|
|
13897
|
+
if (!explicit) {
|
|
13898
|
+
continue;
|
|
13899
|
+
}
|
|
13900
|
+
const expanded = expandHomePath3(explicit, env);
|
|
13901
|
+
if (isExecutablePath(expanded)) {
|
|
13902
|
+
return { path: resolve4(expanded), source: "env" };
|
|
13903
|
+
}
|
|
13904
|
+
const foundOnPath = findExecutableOnSearchPath(explicit, env);
|
|
13905
|
+
if (foundOnPath) {
|
|
13906
|
+
return { path: foundOnPath.path, source: "env" };
|
|
13907
|
+
}
|
|
13908
|
+
}
|
|
13909
|
+
const commonDirectories = dedupeDirectories([...options.commonDirectories ?? DEFAULT_COMMON_EXECUTABLE_DIRECTORIES], env);
|
|
13910
|
+
const searchDirectories = dedupeDirectories([
|
|
13911
|
+
...splitPathEntries(env),
|
|
13912
|
+
...options.extraDirectories ?? [],
|
|
13913
|
+
...commonDirectories
|
|
13914
|
+
], env);
|
|
13915
|
+
for (const directory of searchDirectories) {
|
|
13916
|
+
for (const name of options.names) {
|
|
13917
|
+
const candidate = join14(directory, name);
|
|
13918
|
+
if (isExecutablePath(candidate)) {
|
|
13919
|
+
return {
|
|
13920
|
+
path: candidate,
|
|
13921
|
+
source: commonDirectories.includes(directory) ? "common-path" : "path"
|
|
13922
|
+
};
|
|
13923
|
+
}
|
|
13924
|
+
}
|
|
13925
|
+
}
|
|
13926
|
+
return null;
|
|
13927
|
+
}
|
|
13928
|
+
function resolveBunExecutable2(env = process.env) {
|
|
13929
|
+
return resolveExecutableFromSearch({
|
|
13930
|
+
env,
|
|
13931
|
+
envKeys: ["OPENSCOUT_BUN_BIN", "SCOUT_BUN_BIN", "BUN_BIN"],
|
|
13932
|
+
names: ["bun"]
|
|
13933
|
+
});
|
|
13934
|
+
}
|
|
13935
|
+
function findExecutableOnSearchPath(name, env) {
|
|
13936
|
+
const searchDirectories = dedupeDirectories([
|
|
13937
|
+
...splitPathEntries(env),
|
|
13938
|
+
...DEFAULT_COMMON_EXECUTABLE_DIRECTORIES
|
|
13939
|
+
], env);
|
|
13940
|
+
for (const directory of searchDirectories) {
|
|
13941
|
+
const candidate = join14(directory, name);
|
|
13942
|
+
if (isExecutablePath(candidate)) {
|
|
13943
|
+
return { path: candidate, source: "path" };
|
|
13944
|
+
}
|
|
13945
|
+
}
|
|
13946
|
+
return null;
|
|
13947
|
+
}
|
|
13948
|
+
|
|
13949
|
+
// ../runtime/src/broker-service.ts
|
|
13592
13950
|
function isTmpPath(p) {
|
|
13593
13951
|
return /^\/(?:private\/)?tmp\//.test(p);
|
|
13594
13952
|
}
|
|
@@ -13627,17 +13985,17 @@ function runtimePackageDir() {
|
|
|
13627
13985
|
const fromCwd = findWorkspaceRuntimeDir(process.cwd());
|
|
13628
13986
|
if (fromCwd)
|
|
13629
13987
|
return fromCwd;
|
|
13630
|
-
const moduleDir =
|
|
13631
|
-
return
|
|
13988
|
+
const moduleDir = dirname6(fileURLToPath4(import.meta.url));
|
|
13989
|
+
return resolve5(moduleDir, "..");
|
|
13632
13990
|
}
|
|
13633
13991
|
function isInstalledRuntimePackageDir(candidate) {
|
|
13634
|
-
return
|
|
13992
|
+
return existsSync13(join15(candidate, "package.json")) && existsSync13(join15(candidate, "bin", "openscout-runtime.mjs"));
|
|
13635
13993
|
}
|
|
13636
13994
|
function findGlobalRuntimeDir() {
|
|
13637
13995
|
const candidates = [
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13996
|
+
join15(homedir14(), ".bun", "node_modules", "@openscout", "runtime"),
|
|
13997
|
+
join15(homedir14(), ".bun", "install", "global", "node_modules", "@openscout", "runtime"),
|
|
13998
|
+
join15(homedir14(), ".bun", "install", "global", "node_modules", "@openscout", "scout", "node_modules", "@openscout", "runtime")
|
|
13641
13999
|
];
|
|
13642
14000
|
for (const c of candidates) {
|
|
13643
14001
|
if (isInstalledRuntimePackageDir(c))
|
|
@@ -13647,11 +14005,11 @@ function findGlobalRuntimeDir() {
|
|
|
13647
14005
|
const result = spawnSync("which", ["scout"], { encoding: "utf8", timeout: 3000 });
|
|
13648
14006
|
const scoutBin = result.stdout?.trim();
|
|
13649
14007
|
if (scoutBin) {
|
|
13650
|
-
const scoutPkg =
|
|
13651
|
-
const nested =
|
|
14008
|
+
const scoutPkg = resolve5(scoutBin, "..", "..");
|
|
14009
|
+
const nested = join15(scoutPkg, "node_modules", "@openscout", "runtime");
|
|
13652
14010
|
if (isInstalledRuntimePackageDir(nested))
|
|
13653
14011
|
return nested;
|
|
13654
|
-
const sibling =
|
|
14012
|
+
const sibling = resolve5(scoutPkg, "..", "runtime");
|
|
13655
14013
|
if (isInstalledRuntimePackageDir(sibling))
|
|
13656
14014
|
return sibling;
|
|
13657
14015
|
}
|
|
@@ -13659,38 +14017,24 @@ function findGlobalRuntimeDir() {
|
|
|
13659
14017
|
return null;
|
|
13660
14018
|
}
|
|
13661
14019
|
function findWorkspaceRuntimeDir(startDir) {
|
|
13662
|
-
let current =
|
|
14020
|
+
let current = resolve5(startDir);
|
|
13663
14021
|
while (true) {
|
|
13664
|
-
const candidate =
|
|
13665
|
-
if (
|
|
14022
|
+
const candidate = join15(current, "packages", "runtime");
|
|
14023
|
+
if (existsSync13(join15(candidate, "package.json")) && existsSync13(join15(candidate, "src"))) {
|
|
13666
14024
|
return candidate;
|
|
13667
14025
|
}
|
|
13668
|
-
const parent =
|
|
14026
|
+
const parent = dirname6(current);
|
|
13669
14027
|
if (parent === current)
|
|
13670
14028
|
return null;
|
|
13671
14029
|
current = parent;
|
|
13672
14030
|
}
|
|
13673
14031
|
}
|
|
13674
|
-
function
|
|
13675
|
-
const
|
|
13676
|
-
if (
|
|
13677
|
-
return
|
|
14032
|
+
function resolveBunExecutable3() {
|
|
14033
|
+
const bun = resolveBunExecutable2(process.env);
|
|
14034
|
+
if (bun) {
|
|
14035
|
+
return bun.path;
|
|
13678
14036
|
}
|
|
13679
|
-
|
|
13680
|
-
return process.execPath;
|
|
13681
|
-
}
|
|
13682
|
-
const pathEntries = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
13683
|
-
for (const entry of pathEntries) {
|
|
13684
|
-
const candidate = join14(entry, "bun");
|
|
13685
|
-
if (existsSync12(candidate)) {
|
|
13686
|
-
return candidate;
|
|
13687
|
-
}
|
|
13688
|
-
}
|
|
13689
|
-
const homeBun = join14(homedir13(), ".bun", "bin", "bun");
|
|
13690
|
-
if (existsSync12(homeBun)) {
|
|
13691
|
-
return homeBun;
|
|
13692
|
-
}
|
|
13693
|
-
return "bun";
|
|
14037
|
+
throw new Error("Unable to locate Bun for broker service management. Install Bun or set OPENSCOUT_BUN_BIN.");
|
|
13694
14038
|
}
|
|
13695
14039
|
function resolveBrokerServiceMode() {
|
|
13696
14040
|
const explicit = (process.env.OPENSCOUT_BROKER_SERVICE_MODE ?? "").trim().toLowerCase();
|
|
@@ -13729,15 +14073,15 @@ function resolveBrokerServiceConfig() {
|
|
|
13729
14073
|
const label = resolveBrokerServiceLabel(mode);
|
|
13730
14074
|
const uid = typeof process.getuid === "function" ? process.getuid() : Number.parseInt(process.env.UID ?? "0", 10);
|
|
13731
14075
|
const supportPaths = resolveOpenScoutSupportPaths();
|
|
13732
|
-
const defaultSupportDir =
|
|
14076
|
+
const defaultSupportDir = join15(homedir14(), "Library", "Application Support", "OpenScout");
|
|
13733
14077
|
const supportDirectory = isTmpPath(supportPaths.supportDirectory) ? defaultSupportDir : supportPaths.supportDirectory;
|
|
13734
|
-
const logsDirectory =
|
|
13735
|
-
const controlHome = isTmpPath(supportPaths.controlHome) ?
|
|
14078
|
+
const logsDirectory = join15(supportDirectory, "logs", "broker");
|
|
14079
|
+
const controlHome = isTmpPath(supportPaths.controlHome) ? join15(homedir14(), ".openscout", "control-plane") : supportPaths.controlHome;
|
|
13736
14080
|
const advertiseScope = resolveAdvertiseScope();
|
|
13737
14081
|
const brokerHost = resolveBrokerHost(advertiseScope);
|
|
13738
14082
|
const brokerPort = Number.parseInt(process.env.OPENSCOUT_BROKER_PORT ?? String(DEFAULT_BROKER_PORT), 10);
|
|
13739
14083
|
const brokerUrl = process.env.OPENSCOUT_BROKER_URL ?? buildDefaultBrokerUrl(brokerHost, brokerPort);
|
|
13740
|
-
const launchAgentPath =
|
|
14084
|
+
const launchAgentPath = join15(homedir14(), "Library", "LaunchAgents", `${label}.plist`);
|
|
13741
14085
|
return {
|
|
13742
14086
|
label,
|
|
13743
14087
|
mode,
|
|
@@ -13747,11 +14091,11 @@ function resolveBrokerServiceConfig() {
|
|
|
13747
14091
|
launchAgentPath,
|
|
13748
14092
|
supportDirectory,
|
|
13749
14093
|
logsDirectory,
|
|
13750
|
-
stdoutLogPath:
|
|
13751
|
-
stderrLogPath:
|
|
14094
|
+
stdoutLogPath: join15(logsDirectory, "stdout.log"),
|
|
14095
|
+
stderrLogPath: join15(logsDirectory, "stderr.log"),
|
|
13752
14096
|
controlHome,
|
|
13753
14097
|
runtimePackageDir: runtimePackageDir(),
|
|
13754
|
-
bunExecutable:
|
|
14098
|
+
bunExecutable: resolveBunExecutable3(),
|
|
13755
14099
|
brokerHost,
|
|
13756
14100
|
brokerPort,
|
|
13757
14101
|
brokerUrl,
|
|
@@ -13768,7 +14112,7 @@ function renderLaunchAgentPlist(config) {
|
|
|
13768
14112
|
OPENSCOUT_BROKER_SERVICE_MODE: config.mode,
|
|
13769
14113
|
OPENSCOUT_BROKER_SERVICE_LABEL: config.label,
|
|
13770
14114
|
OPENSCOUT_ADVERTISE_SCOPE: config.advertiseScope,
|
|
13771
|
-
HOME:
|
|
14115
|
+
HOME: homedir14(),
|
|
13772
14116
|
PATH: launchPath,
|
|
13773
14117
|
...collectOptionalEnvVars([
|
|
13774
14118
|
"OPENSCOUT_MESH_ID",
|
|
@@ -13794,7 +14138,7 @@ function renderLaunchAgentPlist(config) {
|
|
|
13794
14138
|
<key>ProgramArguments</key>
|
|
13795
14139
|
<array>
|
|
13796
14140
|
<string>${xmlEscape(config.bunExecutable)}</string>
|
|
13797
|
-
<string>${xmlEscape(
|
|
14141
|
+
<string>${xmlEscape(join15(config.runtimePackageDir, "bin", "openscout-runtime.mjs"))}</string>
|
|
13798
14142
|
<string>broker</string>
|
|
13799
14143
|
</array>
|
|
13800
14144
|
<key>WorkingDirectory</key>
|
|
@@ -13829,7 +14173,7 @@ function collectOptionalEnvVars(keys) {
|
|
|
13829
14173
|
}
|
|
13830
14174
|
function resolveLaunchAgentPATH() {
|
|
13831
14175
|
const entries = [
|
|
13832
|
-
|
|
14176
|
+
join15(homedir14(), ".bun", "bin"),
|
|
13833
14177
|
...(process.env.PATH ?? "").split(":").filter(Boolean),
|
|
13834
14178
|
"/opt/homebrew/bin",
|
|
13835
14179
|
"/usr/local/bin",
|
|
@@ -13844,7 +14188,7 @@ function xmlEscape(value) {
|
|
|
13844
14188
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
13845
14189
|
}
|
|
13846
14190
|
function ensureParentDirectory(filePath) {
|
|
13847
|
-
mkdirSync8(
|
|
14191
|
+
mkdirSync8(dirname6(filePath), { recursive: true });
|
|
13848
14192
|
}
|
|
13849
14193
|
function ensureServiceDirectories(config) {
|
|
13850
14194
|
ensureOpenScoutCleanSlateSync();
|
|
@@ -13877,7 +14221,7 @@ function launchctlPath() {
|
|
|
13877
14221
|
return "/bin/launchctl";
|
|
13878
14222
|
}
|
|
13879
14223
|
function readLogLines(path2) {
|
|
13880
|
-
if (!
|
|
14224
|
+
if (!existsSync13(path2)) {
|
|
13881
14225
|
return [];
|
|
13882
14226
|
}
|
|
13883
14227
|
return readFileSync7(path2, "utf8").split(`
|
|
@@ -13977,7 +14321,7 @@ async function brokerServiceStatus(config = resolveBrokerServiceConfig()) {
|
|
|
13977
14321
|
ensureServiceDirectories(config);
|
|
13978
14322
|
const launchctl = inspectLaunchctl(config);
|
|
13979
14323
|
const health = await fetchHealthSnapshot(config);
|
|
13980
|
-
const installed =
|
|
14324
|
+
const installed = existsSync13(config.launchAgentPath);
|
|
13981
14325
|
const lastLogLine = health.reachable ? readLastLogLine([config.stdoutLogPath, config.stderrLogPath]) : readLastLogLine([config.stderrLogPath, config.stdoutLogPath]);
|
|
13982
14326
|
return {
|
|
13983
14327
|
label: config.label,
|
|
@@ -14020,7 +14364,7 @@ async function startBrokerService(config = resolveBrokerServiceConfig()) {
|
|
|
14020
14364
|
throw new Error(status.lastLogLine ?? status.health.error ?? "Broker service did not become healthy.");
|
|
14021
14365
|
}
|
|
14022
14366
|
function sleep(ms) {
|
|
14023
|
-
return new Promise((
|
|
14367
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
14024
14368
|
}
|
|
14025
14369
|
async function stopBrokerService(config = resolveBrokerServiceConfig()) {
|
|
14026
14370
|
runCommand(launchctlPath(), ["bootout", config.serviceTarget], { allowFailure: true });
|
|
@@ -14039,7 +14383,7 @@ async function restartBrokerService(config = resolveBrokerServiceConfig()) {
|
|
|
14039
14383
|
}
|
|
14040
14384
|
async function uninstallBrokerService(config = resolveBrokerServiceConfig()) {
|
|
14041
14385
|
await stopBrokerService(config);
|
|
14042
|
-
if (
|
|
14386
|
+
if (existsSync13(config.launchAgentPath)) {
|
|
14043
14387
|
rmSync2(config.launchAgentPath, { force: true });
|
|
14044
14388
|
}
|
|
14045
14389
|
return brokerServiceStatus(config);
|
|
@@ -14109,8 +14453,8 @@ var LOCAL_AGENT_SYSTEM_PROMPT_TEMPLATE_HINT = [
|
|
|
14109
14453
|
].join("");
|
|
14110
14454
|
|
|
14111
14455
|
// ../runtime/src/local-agents.ts
|
|
14112
|
-
var MODULE_DIRECTORY =
|
|
14113
|
-
var OPENSCOUT_REPO_ROOT =
|
|
14456
|
+
var MODULE_DIRECTORY = dirname7(fileURLToPath5(import.meta.url));
|
|
14457
|
+
var OPENSCOUT_REPO_ROOT = resolve6(MODULE_DIRECTORY, "..", "..", "..");
|
|
14114
14458
|
var DEFAULT_LOCAL_AGENT_CAPABILITIES = ["chat", "invoke", "deliver"];
|
|
14115
14459
|
var DEFAULT_LOCAL_AGENT_HARNESS = "claude";
|
|
14116
14460
|
function resolveRelayHub() {
|
|
@@ -14118,18 +14462,18 @@ function resolveRelayHub() {
|
|
|
14118
14462
|
}
|
|
14119
14463
|
function resolveProjectsRoot(projectPath) {
|
|
14120
14464
|
if (process.env.OPENSCOUT_PROJECTS_ROOT?.trim()) {
|
|
14121
|
-
return
|
|
14465
|
+
return resolve6(process.env.OPENSCOUT_PROJECTS_ROOT.trim());
|
|
14122
14466
|
}
|
|
14123
14467
|
try {
|
|
14124
14468
|
const supportPaths = resolveOpenScoutSupportPaths();
|
|
14125
|
-
if (!
|
|
14126
|
-
return
|
|
14469
|
+
if (!existsSync14(supportPaths.settingsPath)) {
|
|
14470
|
+
return dirname7(projectPath);
|
|
14127
14471
|
}
|
|
14128
14472
|
const raw = JSON.parse(readFileSync8(supportPaths.settingsPath, "utf8"));
|
|
14129
14473
|
const workspaceRoot = raw.discovery?.workspaceRoots?.find((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
14130
|
-
return workspaceRoot ?
|
|
14474
|
+
return workspaceRoot ? resolve6(workspaceRoot) : dirname7(projectPath);
|
|
14131
14475
|
} catch {
|
|
14132
|
-
return
|
|
14476
|
+
return dirname7(projectPath);
|
|
14133
14477
|
}
|
|
14134
14478
|
}
|
|
14135
14479
|
function resolveBrokerUrl() {
|
|
@@ -14139,7 +14483,7 @@ function nowSeconds2() {
|
|
|
14139
14483
|
return Math.floor(Date.now() / 1000);
|
|
14140
14484
|
}
|
|
14141
14485
|
function scoutCliPath() {
|
|
14142
|
-
return
|
|
14486
|
+
return join16(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs");
|
|
14143
14487
|
}
|
|
14144
14488
|
function legacyNodeBrokerRelayCommand() {
|
|
14145
14489
|
return `node ${JSON.stringify(scoutCliPath())}`;
|
|
@@ -14152,13 +14496,13 @@ function titleCaseLocalAgentName(value) {
|
|
|
14152
14496
|
}
|
|
14153
14497
|
function resolveScoutSkillPath() {
|
|
14154
14498
|
const candidatePaths = [
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14499
|
+
join16(OPENSCOUT_REPO_ROOT, ".agents", "skills", "scout", "SKILL.md"),
|
|
14500
|
+
join16(process.env.HOME ?? "", ".agents", "skills", "scout", "SKILL.md"),
|
|
14501
|
+
join16(OPENSCOUT_REPO_ROOT, ".agents", "skills", "relay-agent-comms", "SKILL.md"),
|
|
14502
|
+
join16(process.env.HOME ?? "", ".agents", "skills", "relay-agent-comms", "SKILL.md")
|
|
14159
14503
|
];
|
|
14160
14504
|
for (const path2 of candidatePaths) {
|
|
14161
|
-
if (
|
|
14505
|
+
if (existsSync14(path2)) {
|
|
14162
14506
|
return path2;
|
|
14163
14507
|
}
|
|
14164
14508
|
}
|
|
@@ -14209,7 +14553,7 @@ function buildLocalAgentCollaborationPrompt(context) {
|
|
|
14209
14553
|
function buildLocalAgentTmuxProtocolPrompt(context) {
|
|
14210
14554
|
return [
|
|
14211
14555
|
"Relay protocol:",
|
|
14212
|
-
` - Read recent context with: ${context.relayCommand}
|
|
14556
|
+
` - Read recent context when needed with: ${context.relayCommand} latest --agent ${context.agentId} --limit 20`,
|
|
14213
14557
|
` - Tell one agent with: ${context.relayCommand} send --as ${context.agentId} "@<agent> your message"`,
|
|
14214
14558
|
` - Ask one agent and stay attached with: ${context.relayCommand} ask --to <agent> --as ${context.agentId} "your request"`,
|
|
14215
14559
|
"",
|
|
@@ -14221,9 +14565,10 @@ function buildLocalAgentTmuxProtocolPrompt(context) {
|
|
|
14221
14565
|
" - If you need multiple agents, use separate DMs or an explicit channel; do not guess a venue from multiple mentions",
|
|
14222
14566
|
" - Do not use channel.shared for ordinary delegation or follow-up; reserve it for explicit group updates or broadcasts",
|
|
14223
14567
|
" - If a short @handle may be ambiguous, resolve the exact target before sending; do not guess and do not fall back to shared",
|
|
14568
|
+
" - Treat known offline / on-demand agents as wakeable: use send or ask first and let the broker wake them; only surface ambiguity or unknown-target failures to the operator",
|
|
14224
14569
|
" - Use send for tells and status; use ask when the meaning is 'do this and get back to me'",
|
|
14225
14570
|
" - When replying to an [ask:<id>] request, include the same [ask:<id>] tag in your reply",
|
|
14226
|
-
" - Use the broker-backed
|
|
14571
|
+
" - Use the broker-backed latest command above only when recent context is needed before responding",
|
|
14227
14572
|
` - Follow the scout skill at ${context.scoutSkill} for agent-to-agent communication`
|
|
14228
14573
|
].join(`
|
|
14229
14574
|
`);
|
|
@@ -14234,7 +14579,7 @@ function buildLocalAgentDirectProtocolPrompt(context) {
|
|
|
14234
14579
|
" - You are invoked directly by the OpenScout broker",
|
|
14235
14580
|
" - Return your final answer in the assistant message for the current turn",
|
|
14236
14581
|
" - Do not shell out to send the final answer through relay yourself",
|
|
14237
|
-
` - If you need recent
|
|
14582
|
+
` - If you need recent broker context, inspect it with: ${context.relayCommand} latest --agent ${context.agentId} --limit 20`,
|
|
14238
14583
|
` - If you need to tell one agent something, use: ${context.relayCommand} send --as ${context.agentId} "@<agent> your message"`,
|
|
14239
14584
|
` - If you need another agent to do work, use: ${context.relayCommand} ask --to <agent> --as ${context.agentId} "your request"`,
|
|
14240
14585
|
" - Default Scout loop: resolve identity, resolve one target, choose DM vs explicit channel, keep follow-up in that same venue",
|
|
@@ -14242,6 +14587,7 @@ function buildLocalAgentDirectProtocolPrompt(context) {
|
|
|
14242
14587
|
" - If you need multiple agents, use separate DMs or an explicit channel; do not guess a venue from multiple mentions",
|
|
14243
14588
|
" - Do not use channel.shared for ordinary delegation or follow-up; reserve it for explicit group updates or broadcasts",
|
|
14244
14589
|
" - If a short @handle may be ambiguous, resolve the exact target before sending; do not guess and do not fall back to shared",
|
|
14590
|
+
" - Treat known offline / on-demand agents as wakeable: use send or ask first and let the broker wake them; only surface ambiguity or unknown-target failures to the operator",
|
|
14245
14591
|
" - Use send for tells and status; use ask when the meaning is 'do this and get back to me'",
|
|
14246
14592
|
` - Follow the scout skill at ${context.scoutSkill} for agent-to-agent communication`
|
|
14247
14593
|
].join(`
|
|
@@ -14304,7 +14650,7 @@ function buildLegacySimpleRelayPrompt(agentId, projectName, projectPath, relayCo
|
|
|
14304
14650
|
"A primary agent may call into you for context, execution, follow-through, and handoff.",
|
|
14305
14651
|
"",
|
|
14306
14652
|
`You have full access to the codebase at ${projectPath}.`,
|
|
14307
|
-
`There is a structured relay event stream at ${
|
|
14653
|
+
`There is a structured relay event stream at ${join16(relayHub, "channel.jsonl")} shared by all agents.`,
|
|
14308
14654
|
"",
|
|
14309
14655
|
"Your job:",
|
|
14310
14656
|
` - Respond to @${agentId} mentions from other agents`,
|
|
@@ -14408,17 +14754,17 @@ async function writeLocalAgentRegistry(registry) {
|
|
|
14408
14754
|
}
|
|
14409
14755
|
await writeRelayAgentOverrides(nextOverrides);
|
|
14410
14756
|
}
|
|
14411
|
-
function
|
|
14757
|
+
function expandHomePath4(value) {
|
|
14412
14758
|
if (value === "~") {
|
|
14413
14759
|
return process.env.HOME ?? process.cwd();
|
|
14414
14760
|
}
|
|
14415
14761
|
if (value.startsWith("~/")) {
|
|
14416
|
-
return
|
|
14762
|
+
return join16(process.env.HOME ?? process.cwd(), value.slice(2));
|
|
14417
14763
|
}
|
|
14418
14764
|
return value;
|
|
14419
14765
|
}
|
|
14420
14766
|
function normalizeProjectPath(value) {
|
|
14421
|
-
return
|
|
14767
|
+
return resolve6(expandHomePath4(value.trim() || "."));
|
|
14422
14768
|
}
|
|
14423
14769
|
function normalizeTmuxSessionName2(value, agentId) {
|
|
14424
14770
|
const fallback = `relay-${agentId}`;
|
|
@@ -14681,7 +15027,7 @@ function recordForHarness(record, harnessOverride) {
|
|
|
14681
15027
|
function normalizeLocalAgentRecord(agentId, record) {
|
|
14682
15028
|
const cwd = normalizeProjectPath(record.cwd || process.cwd());
|
|
14683
15029
|
const projectRoot = normalizeProjectPath(record.projectRoot || cwd);
|
|
14684
|
-
const project = record.project?.trim() ||
|
|
15030
|
+
const project = record.project?.trim() || basename5(projectRoot);
|
|
14685
15031
|
const definitionId = record.definitionId?.trim() || agentId;
|
|
14686
15032
|
const defaultHarness = activeLocalHarness(record);
|
|
14687
15033
|
const harnessProfiles = normalizeLocalHarnessProfiles(agentId, {
|
|
@@ -14746,7 +15092,7 @@ function localAgentRecordFromResolvedConfig(config) {
|
|
|
14746
15092
|
function localAgentRecordFromRelayAgentOverride(agentId, override) {
|
|
14747
15093
|
return normalizeLocalAgentRecord(agentId, {
|
|
14748
15094
|
definitionId: override.definitionId ?? agentId,
|
|
14749
|
-
project: override.projectName ??
|
|
15095
|
+
project: override.projectName ?? basename5(override.projectRoot || override.runtime?.cwd || agentId),
|
|
14750
15096
|
projectRoot: override.projectRoot ?? override.runtime?.cwd,
|
|
14751
15097
|
tmuxSession: override.runtime?.sessionId ?? `relay-${agentId}`,
|
|
14752
15098
|
cwd: override.runtime?.cwd ?? override.projectRoot,
|
|
@@ -14842,7 +15188,7 @@ function buildLocalAgentBootstrapPrompt(_harness, _systemPrompt, initialMessage)
|
|
|
14842
15188
|
function buildLocalAgentLaunchCommand(agentName, record, projectPath, promptFile, workerScript) {
|
|
14843
15189
|
const extraArgs = shellQuoteArguments(normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(record.harness), record.launchArgs));
|
|
14844
15190
|
if (normalizeLocalAgentHarness(record.harness) === "codex") {
|
|
14845
|
-
return `exec bash ${JSON.stringify(workerScript ??
|
|
15191
|
+
return `exec bash ${JSON.stringify(workerScript ?? join16(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
|
|
14846
15192
|
}
|
|
14847
15193
|
return [
|
|
14848
15194
|
"claude",
|
|
@@ -14888,7 +15234,7 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
14888
15234
|
return normalizedRecord;
|
|
14889
15235
|
}
|
|
14890
15236
|
const projectPath = normalizedRecord.cwd;
|
|
14891
|
-
const projectName = normalizedRecord.project ||
|
|
15237
|
+
const projectName = normalizedRecord.project || basename5(projectPath);
|
|
14892
15238
|
const systemPromptTemplate = normalizedRecord.systemPrompt || buildLocalAgentSystemPromptTemplate();
|
|
14893
15239
|
const systemPrompt = renderLocalAgentSystemPromptTemplate(systemPromptTemplate, buildLocalAgentTemplateContext(agentName, projectName, projectPath), { transport: normalizedRecord.transport });
|
|
14894
15240
|
const agentRuntimeDir = relayAgentRuntimeDirectory(agentName);
|
|
@@ -14896,7 +15242,7 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
14896
15242
|
await mkdir6(agentRuntimeDir, { recursive: true });
|
|
14897
15243
|
await mkdir6(logsDir, { recursive: true });
|
|
14898
15244
|
if (normalizedRecord.transport === "codex_app_server") {
|
|
14899
|
-
await writeFile6(
|
|
15245
|
+
await writeFile6(join16(agentRuntimeDir, "prompt.txt"), systemPrompt);
|
|
14900
15246
|
await ensureCodexAppServerAgentOnline(buildCodexAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
|
|
14901
15247
|
const registry2 = await readLocalAgentRegistry();
|
|
14902
15248
|
registry2[agentName] = {
|
|
@@ -14908,7 +15254,7 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
14908
15254
|
return registry2[agentName];
|
|
14909
15255
|
}
|
|
14910
15256
|
if (normalizedRecord.transport === "claude_stream_json") {
|
|
14911
|
-
await writeFile6(
|
|
15257
|
+
await writeFile6(join16(agentRuntimeDir, "prompt.txt"), systemPrompt);
|
|
14912
15258
|
await ensureClaudeStreamJsonAgentOnline(buildClaudeAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
|
|
14913
15259
|
const registry2 = await readLocalAgentRegistry();
|
|
14914
15260
|
registry2[agentName] = {
|
|
@@ -14921,17 +15267,17 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
14921
15267
|
}
|
|
14922
15268
|
const initialMessage = buildLocalAgentInitialMessage(projectName, agentName);
|
|
14923
15269
|
const bootstrapPrompt = buildLocalAgentBootstrapPrompt(normalizeLocalAgentHarness(normalizedRecord.harness), systemPrompt, initialMessage);
|
|
14924
|
-
const queueDirectory =
|
|
14925
|
-
const processingDirectory =
|
|
14926
|
-
const processedDirectory =
|
|
14927
|
-
const promptFile =
|
|
14928
|
-
const initialFile =
|
|
14929
|
-
const launchScript =
|
|
14930
|
-
const workerScript =
|
|
14931
|
-
const codexSessionIdFile =
|
|
14932
|
-
const stateFile =
|
|
14933
|
-
const stdoutLogFile =
|
|
14934
|
-
const stderrLogFile =
|
|
15270
|
+
const queueDirectory = join16(agentRuntimeDir, "queue");
|
|
15271
|
+
const processingDirectory = join16(queueDirectory, "processing");
|
|
15272
|
+
const processedDirectory = join16(queueDirectory, "processed");
|
|
15273
|
+
const promptFile = join16(agentRuntimeDir, "prompt.txt");
|
|
15274
|
+
const initialFile = join16(agentRuntimeDir, "initial.txt");
|
|
15275
|
+
const launchScript = join16(agentRuntimeDir, "launch.sh");
|
|
15276
|
+
const workerScript = join16(agentRuntimeDir, "codex-worker.sh");
|
|
15277
|
+
const codexSessionIdFile = join16(agentRuntimeDir, "codex-session-id.txt");
|
|
15278
|
+
const stateFile = join16(agentRuntimeDir, "state.json");
|
|
15279
|
+
const stdoutLogFile = join16(logsDir, "stdout.log");
|
|
15280
|
+
const stderrLogFile = join16(logsDir, "stderr.log");
|
|
14935
15281
|
await writeFile6(promptFile, systemPrompt);
|
|
14936
15282
|
await writeFile6(initialFile, bootstrapPrompt);
|
|
14937
15283
|
await writeFile6(stderrLogFile, "");
|
|
@@ -14993,10 +15339,10 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
14993
15339
|
if (isLocalAgentSessionAlive(normalizedRecord.tmuxSession)) {
|
|
14994
15340
|
break;
|
|
14995
15341
|
}
|
|
14996
|
-
await new Promise((
|
|
15342
|
+
await new Promise((resolve7) => setTimeout(resolve7, 100));
|
|
14997
15343
|
}
|
|
14998
15344
|
if (!isLocalAgentSessionAlive(normalizedRecord.tmuxSession)) {
|
|
14999
|
-
const stderrTail =
|
|
15345
|
+
const stderrTail = existsSync14(stderrLogFile) ? readFileSync8(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
|
|
15000
15346
|
`).trim() : "";
|
|
15001
15347
|
throw new Error(stderrTail ? `Relay agent ${agentName} failed to stay online:
|
|
15002
15348
|
${stderrTail}` : `Relay agent ${agentName} failed to stay online.`);
|
|
@@ -15051,6 +15397,7 @@ async function startLocalAgent(input) {
|
|
|
15051
15397
|
const preferredHarness = input.harness ? normalizeLocalAgentHarness(input.harness) : undefined;
|
|
15052
15398
|
const currentDirectory = input.currentDirectory ?? projectPath;
|
|
15053
15399
|
const effectiveCwd = input.cwdOverride ? normalizeProjectPath(input.cwdOverride) : undefined;
|
|
15400
|
+
const shouldEnsureOnline = input.ensureOnline !== false;
|
|
15054
15401
|
const requestedDefinitionId = input.agentName?.trim() ? normalizeAgentSelectorSegment(input.agentName.trim()) : "";
|
|
15055
15402
|
if (input.agentName?.trim() && !requestedDefinitionId) {
|
|
15056
15403
|
throw new Error(`Invalid agent name "${input.agentName}".`);
|
|
@@ -15098,7 +15445,7 @@ async function startLocalAgent(input) {
|
|
|
15098
15445
|
}
|
|
15099
15446
|
if (!matchingOverride) {
|
|
15100
15447
|
const configDefinitionId = coldProjectConfig?.agent?.id?.trim() ? normalizeAgentSelectorSegment(coldProjectConfig.agent.id.trim()) : "";
|
|
15101
|
-
const definitionId = requestedDefinitionId || configDefinitionId || normalizeAgentSelectorSegment(
|
|
15448
|
+
const definitionId = requestedDefinitionId || configDefinitionId || normalizeAgentSelectorSegment(basename5(projectRoot)) || "agent";
|
|
15102
15449
|
const configDisplayName = coldProjectConfig?.agent?.displayName?.trim() || "";
|
|
15103
15450
|
const effectiveDisplayName = input.displayName || configDisplayName || titleCaseLocalAgentName(definitionId);
|
|
15104
15451
|
const instance = buildRelayAgentInstance(definitionId, projectRoot);
|
|
@@ -15115,7 +15462,7 @@ async function startLocalAgent(input) {
|
|
|
15115
15462
|
agentId: instance.id,
|
|
15116
15463
|
definitionId,
|
|
15117
15464
|
displayName: effectiveDisplayName,
|
|
15118
|
-
projectName:
|
|
15465
|
+
projectName: basename5(projectRoot),
|
|
15119
15466
|
projectRoot,
|
|
15120
15467
|
projectConfigPath: coldProjectConfigPath,
|
|
15121
15468
|
source: "manual",
|
|
@@ -15155,7 +15502,7 @@ async function startLocalAgent(input) {
|
|
|
15155
15502
|
agentId: instance.id,
|
|
15156
15503
|
definitionId: requestedDefinitionId,
|
|
15157
15504
|
displayName: input.displayName || titleCaseLocalAgentName(requestedDefinitionId),
|
|
15158
|
-
projectName: matchingOverride.projectName ??
|
|
15505
|
+
projectName: matchingOverride.projectName ?? basename5(matchingProjectRoot),
|
|
15159
15506
|
projectRoot: matchingProjectRoot,
|
|
15160
15507
|
projectConfigPath: matchingOverride.projectConfigPath ?? null,
|
|
15161
15508
|
source: "manual",
|
|
@@ -15200,12 +15547,14 @@ async function startLocalAgent(input) {
|
|
|
15200
15547
|
}
|
|
15201
15548
|
targetAgentId = matchingAgentId;
|
|
15202
15549
|
}
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15550
|
+
if (shouldEnsureOnline) {
|
|
15551
|
+
await ensureLocalAgentBindingOnline(targetAgentId, process.env.OPENSCOUT_NODE_ID ?? "local", {
|
|
15552
|
+
includeDiscovered: false,
|
|
15553
|
+
currentDirectory,
|
|
15554
|
+
ensureCurrentProjectConfig: false,
|
|
15555
|
+
harness: preferredHarness
|
|
15556
|
+
});
|
|
15557
|
+
}
|
|
15209
15558
|
const finalOverrides = await readRelayAgentOverrides();
|
|
15210
15559
|
const finalOverride = finalOverrides[targetAgentId];
|
|
15211
15560
|
if (!finalOverride) {
|
|
@@ -15271,8 +15620,8 @@ async function interruptLocalAgent(agentId) {
|
|
|
15271
15620
|
function readPersistedClaudeSessionId(agentName) {
|
|
15272
15621
|
try {
|
|
15273
15622
|
const runtimeDir = relayAgentRuntimeDirectory(agentName);
|
|
15274
|
-
const catalogPath =
|
|
15275
|
-
if (
|
|
15623
|
+
const catalogPath = join16(runtimeDir, "session-catalog.json");
|
|
15624
|
+
if (existsSync14(catalogPath)) {
|
|
15276
15625
|
const raw = readFileSync8(catalogPath, "utf8").trim();
|
|
15277
15626
|
if (raw) {
|
|
15278
15627
|
const catalog = JSON.parse(raw);
|
|
@@ -15281,8 +15630,8 @@ function readPersistedClaudeSessionId(agentName) {
|
|
|
15281
15630
|
}
|
|
15282
15631
|
}
|
|
15283
15632
|
}
|
|
15284
|
-
const legacyPath =
|
|
15285
|
-
if (
|
|
15633
|
+
const legacyPath = join16(runtimeDir, "claude-session-id.txt");
|
|
15634
|
+
if (existsSync14(legacyPath)) {
|
|
15286
15635
|
const value = readFileSync8(legacyPath, "utf8").trim();
|
|
15287
15636
|
return value || null;
|
|
15288
15637
|
}
|
|
@@ -15476,6 +15825,7 @@ var scoutBrokerPaths = {
|
|
|
15476
15825
|
endpoints: "/v1/endpoints",
|
|
15477
15826
|
conversations: "/v1/conversations",
|
|
15478
15827
|
invocations: "/v1/invocations",
|
|
15828
|
+
deliver: "/v1/deliver",
|
|
15479
15829
|
activity: "/v1/activity",
|
|
15480
15830
|
collaborationRecords: "/v1/collaboration/records",
|
|
15481
15831
|
collaborationEvents: "/v1/collaboration/events"
|
|
@@ -15505,29 +15855,6 @@ function titleCaseName(value) {
|
|
|
15505
15855
|
function displayNameForBrokerActor(snapshot, actorId) {
|
|
15506
15856
|
return snapshot.agents[actorId]?.displayName ?? snapshot.actors[actorId]?.displayName ?? titleCaseName(metadataString2(snapshot.agents[actorId]?.metadata, "definitionId") || actorId);
|
|
15507
15857
|
}
|
|
15508
|
-
function firstEndpointForActor(snapshot, actorId) {
|
|
15509
|
-
return Object.values(snapshot.endpoints).filter((endpoint) => endpoint.agentId === actorId).sort((lhs, rhs) => lhs.id.localeCompare(rhs.id))[0];
|
|
15510
|
-
}
|
|
15511
|
-
function buildScoutReturnAddress2(snapshot, actorId, options = {}) {
|
|
15512
|
-
const agent = snapshot.agents[actorId];
|
|
15513
|
-
const actor = snapshot.actors[actorId];
|
|
15514
|
-
const endpoint = firstEndpointForActor(snapshot, actorId);
|
|
15515
|
-
const selector = agent?.selector?.trim() || metadataString2(agent?.metadata, "selector") || metadataString2(actor?.metadata, "selector");
|
|
15516
|
-
const defaultSelector = agent?.defaultSelector?.trim() || metadataString2(agent?.metadata, "defaultSelector") || metadataString2(actor?.metadata, "defaultSelector");
|
|
15517
|
-
const projectRoot = endpoint?.projectRoot ?? endpoint?.cwd ?? metadataString2(agent?.metadata, "projectRoot") ?? metadataString2(actor?.metadata, "projectRoot");
|
|
15518
|
-
return buildScoutReturnAddress({
|
|
15519
|
-
actorId,
|
|
15520
|
-
handle: agent?.handle?.trim() || actor?.handle?.trim() || actorId,
|
|
15521
|
-
displayName: agent?.displayName || actor?.displayName,
|
|
15522
|
-
selector,
|
|
15523
|
-
defaultSelector,
|
|
15524
|
-
conversationId: options.conversationId,
|
|
15525
|
-
replyToMessageId: options.replyToMessageId,
|
|
15526
|
-
nodeId: endpoint?.nodeId || agent?.authorityNodeId || agent?.homeNodeId,
|
|
15527
|
-
projectRoot,
|
|
15528
|
-
sessionId: endpoint?.sessionId
|
|
15529
|
-
});
|
|
15530
|
-
}
|
|
15531
15858
|
function metadataString2(metadata, key) {
|
|
15532
15859
|
const value = metadata?.[key];
|
|
15533
15860
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
@@ -15830,74 +16157,42 @@ async function openScoutPeerSession(input) {
|
|
|
15830
16157
|
};
|
|
15831
16158
|
}
|
|
15832
16159
|
async function sendScoutDirectMessage(input) {
|
|
15833
|
-
const currentDirectory = input.currentDirectory ?? process.cwd();
|
|
15834
|
-
const directSession = await openScoutPeerSession({
|
|
15835
|
-
sourceId: OPERATOR_ID,
|
|
15836
|
-
targetId: input.agentId,
|
|
15837
|
-
currentDirectory
|
|
15838
|
-
});
|
|
15839
16160
|
const broker = await requireScoutBrokerContext();
|
|
15840
16161
|
const createdAt = Date.now();
|
|
15841
|
-
const messageId = `msg-${createdAt.toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
15842
16162
|
const source = input.source?.trim() || "scout-mobile";
|
|
15843
|
-
const
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
replyToMessageId: messageId
|
|
15849
|
-
});
|
|
15850
|
-
await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.messages, {
|
|
15851
|
-
id: messageId,
|
|
15852
|
-
conversationId: directSession.conversation.id,
|
|
15853
|
-
replyToMessageId: input.replyToMessageId ?? undefined,
|
|
15854
|
-
actorId: OPERATOR_ID,
|
|
15855
|
-
originNodeId: broker.node.id,
|
|
15856
|
-
class: "agent",
|
|
16163
|
+
const delivery = await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.deliver, {
|
|
16164
|
+
id: `deliver-${createdAt.toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
16165
|
+
requesterId: OPERATOR_ID,
|
|
16166
|
+
requesterNodeId: broker.node.id,
|
|
16167
|
+
targetAgentId: input.agentId,
|
|
15857
16168
|
body: input.body.trim(),
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
},
|
|
15863
|
-
visibility: "private",
|
|
15864
|
-
policy: "durable",
|
|
16169
|
+
intent: "consult",
|
|
16170
|
+
replyToMessageId: input.replyToMessageId ?? undefined,
|
|
16171
|
+
execution: input.executionHarness ? { harness: input.executionHarness } : undefined,
|
|
16172
|
+
ensureAwake: true,
|
|
15865
16173
|
createdAt,
|
|
15866
|
-
|
|
16174
|
+
messageMetadata: {
|
|
15867
16175
|
source,
|
|
15868
16176
|
destinationKind: "direct",
|
|
15869
|
-
destinationId:
|
|
16177
|
+
destinationId: input.agentId,
|
|
15870
16178
|
referenceMessageIds: input.referenceMessageIds ?? [],
|
|
15871
16179
|
clientMessageId: input.clientMessageId ?? null,
|
|
15872
|
-
returnAddress,
|
|
15873
16180
|
...input.deviceId ? { deviceId: input.deviceId } : {}
|
|
15874
|
-
}
|
|
15875
|
-
|
|
15876
|
-
const invocationResponse = await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.invocations, {
|
|
15877
|
-
id: `inv-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
15878
|
-
requesterId: OPERATOR_ID,
|
|
15879
|
-
requesterNodeId: broker.node.id,
|
|
15880
|
-
targetAgentId,
|
|
15881
|
-
action: "consult",
|
|
15882
|
-
task: input.body.trim(),
|
|
15883
|
-
conversationId: directSession.conversation.id,
|
|
15884
|
-
messageId,
|
|
15885
|
-
execution: input.executionHarness ? { harness: input.executionHarness } : undefined,
|
|
15886
|
-
ensureAwake: true,
|
|
15887
|
-
stream: false,
|
|
15888
|
-
createdAt,
|
|
15889
|
-
metadata: {
|
|
16181
|
+
},
|
|
16182
|
+
invocationMetadata: {
|
|
15890
16183
|
source,
|
|
15891
16184
|
destinationKind: "direct",
|
|
15892
|
-
destinationId:
|
|
15893
|
-
returnAddress,
|
|
16185
|
+
destinationId: input.agentId,
|
|
15894
16186
|
...input.deviceId ? { deviceId: input.deviceId } : {}
|
|
15895
16187
|
}
|
|
15896
16188
|
});
|
|
16189
|
+
if (delivery.kind !== "delivery") {
|
|
16190
|
+
throw new Error(delivery.kind === "question" ? delivery.question.detail : delivery.rejection.detail);
|
|
16191
|
+
}
|
|
15897
16192
|
return {
|
|
15898
|
-
conversationId:
|
|
15899
|
-
messageId,
|
|
15900
|
-
flight:
|
|
16193
|
+
conversationId: delivery.conversation.id,
|
|
16194
|
+
messageId: delivery.message.id,
|
|
16195
|
+
flight: delivery.flight
|
|
15901
16196
|
};
|
|
15902
16197
|
}
|
|
15903
16198
|
async function loadScoutActivityItems(options = {}) {
|
|
@@ -15924,11 +16219,11 @@ async function upScoutAgent(input) {
|
|
|
15924
16219
|
|
|
15925
16220
|
// ../../apps/desktop/src/server/db-queries.ts
|
|
15926
16221
|
import { Database } from "bun:sqlite";
|
|
15927
|
-
import { homedir as
|
|
15928
|
-
import { join as
|
|
16222
|
+
import { homedir as homedir15 } from "os";
|
|
16223
|
+
import { join as join17 } from "path";
|
|
15929
16224
|
function resolveDbPath() {
|
|
15930
|
-
const controlHome = process.env.OPENSCOUT_CONTROL_HOME ??
|
|
15931
|
-
return
|
|
16225
|
+
const controlHome = process.env.OPENSCOUT_CONTROL_HOME ?? join17(homedir15(), ".openscout", "control-plane");
|
|
16226
|
+
return join17(controlHome, "control-plane.sqlite");
|
|
15932
16227
|
}
|
|
15933
16228
|
var _db = null;
|
|
15934
16229
|
function db() {
|
|
@@ -15939,7 +16234,7 @@ function db() {
|
|
|
15939
16234
|
}
|
|
15940
16235
|
return _db;
|
|
15941
16236
|
}
|
|
15942
|
-
var HOME =
|
|
16237
|
+
var HOME = homedir15();
|
|
15943
16238
|
function compact(p) {
|
|
15944
16239
|
if (!p)
|
|
15945
16240
|
return null;
|
|
@@ -16595,8 +16890,8 @@ async function createScoutSession(input, currentDirectory, deviceId) {
|
|
|
16595
16890
|
if (!rawWorkspaceId) {
|
|
16596
16891
|
throw new Error(`Invalid workspaceId.`);
|
|
16597
16892
|
}
|
|
16598
|
-
const workspaceRoot =
|
|
16599
|
-
const projectName =
|
|
16893
|
+
const workspaceRoot = resolve7(rawWorkspaceId);
|
|
16894
|
+
const projectName = basename6(workspaceRoot) || workspaceRoot;
|
|
16600
16895
|
const workspace = {
|
|
16601
16896
|
id: workspaceRoot,
|
|
16602
16897
|
title: projectName,
|
|
@@ -16705,17 +17000,17 @@ async function deriveNewAgentName(projectName, branch, harness) {
|
|
|
16705
17000
|
}
|
|
16706
17001
|
async function createGitWorktree(projectRoot, agentName) {
|
|
16707
17002
|
const { execSync: execSync3 } = await import("child_process");
|
|
16708
|
-
const { join:
|
|
16709
|
-
const { mkdirSync: mkdirSync9, existsSync:
|
|
17003
|
+
const { join: join18 } = await import("path");
|
|
17004
|
+
const { mkdirSync: mkdirSync9, existsSync: existsSync15 } = await import("fs");
|
|
16710
17005
|
try {
|
|
16711
17006
|
execSync3("git rev-parse --git-dir", { cwd: projectRoot, stdio: "pipe" });
|
|
16712
17007
|
} catch {
|
|
16713
17008
|
return null;
|
|
16714
17009
|
}
|
|
16715
17010
|
const branchName = `scout/${agentName}`;
|
|
16716
|
-
const worktreeDir =
|
|
16717
|
-
const worktreePath =
|
|
16718
|
-
if (
|
|
17011
|
+
const worktreeDir = join18(projectRoot, ".scout-worktrees");
|
|
17012
|
+
const worktreePath = join18(worktreeDir, agentName);
|
|
17013
|
+
if (existsSync15(worktreePath)) {
|
|
16719
17014
|
return { path: worktreePath, branch: branchName };
|
|
16720
17015
|
}
|
|
16721
17016
|
mkdirSync9(worktreeDir, { recursive: true });
|
|
@@ -16919,9 +17214,9 @@ async function handleRPCInner(bridge, req, deviceId) {
|
|
|
16919
17214
|
}
|
|
16920
17215
|
case "session/resume": {
|
|
16921
17216
|
const p = req.params;
|
|
16922
|
-
const sessionFilename =
|
|
17217
|
+
const sessionFilename = basename7(p.sessionPath, ".jsonl");
|
|
16923
17218
|
const parentDir = p.sessionPath.substring(0, p.sessionPath.lastIndexOf("/"));
|
|
16924
|
-
const dirName =
|
|
17219
|
+
const dirName = basename7(parentDir);
|
|
16925
17220
|
let cwd;
|
|
16926
17221
|
if (dirName.startsWith("-")) {
|
|
16927
17222
|
const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
|
|
@@ -16988,7 +17283,7 @@ async function handleRPCInner(bridge, req, deviceId) {
|
|
|
16988
17283
|
return { id: req.id, error: { code: -32000, message: "Workspace target is not a directory" } };
|
|
16989
17284
|
}
|
|
16990
17285
|
const adapterType = p.adapter ?? "claude-code";
|
|
16991
|
-
const name = p.name ??
|
|
17286
|
+
const name = p.name ?? basename7(projectPath);
|
|
16992
17287
|
const session = await bridge.createSession(adapterType, {
|
|
16993
17288
|
name,
|
|
16994
17289
|
cwd: projectPath
|
|
@@ -17228,13 +17523,13 @@ function resolveMobileCurrentDirectory() {
|
|
|
17228
17523
|
}
|
|
17229
17524
|
}
|
|
17230
17525
|
function resolveWorkspaceRoot(root) {
|
|
17231
|
-
const expandedRoot = root.replace(/^~/,
|
|
17526
|
+
const expandedRoot = root.replace(/^~/, homedir16());
|
|
17232
17527
|
return realpathSync(expandedRoot);
|
|
17233
17528
|
}
|
|
17234
17529
|
function resolveWorkspacePath(root, requestedPath) {
|
|
17235
17530
|
const normalizedRoot = resolveWorkspaceRoot(root);
|
|
17236
|
-
const expandedPath = requestedPath?.replace(/^~/,
|
|
17237
|
-
const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath :
|
|
17531
|
+
const expandedPath = requestedPath?.replace(/^~/, homedir16());
|
|
17532
|
+
const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath : join18(normalizedRoot, expandedPath) : normalizedRoot;
|
|
17238
17533
|
const resolvedCandidate = realpathSync(candidate);
|
|
17239
17534
|
const rel = relative2(normalizedRoot, resolvedCandidate);
|
|
17240
17535
|
if (rel === "" || !rel.startsWith("..") && !isAbsolute3(rel)) {
|
|
@@ -17264,7 +17559,7 @@ function listDirectories(dirPath) {
|
|
|
17264
17559
|
continue;
|
|
17265
17560
|
if (name === "node_modules" || name === ".build" || name === "target")
|
|
17266
17561
|
continue;
|
|
17267
|
-
const fullPath =
|
|
17562
|
+
const fullPath = join18(dirPath, name);
|
|
17268
17563
|
try {
|
|
17269
17564
|
const stat4 = statSync3(fullPath);
|
|
17270
17565
|
if (!stat4.isDirectory())
|
|
@@ -17287,7 +17582,7 @@ function listDirectories(dirPath) {
|
|
|
17287
17582
|
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
17288
17583
|
}
|
|
17289
17584
|
async function discoverSessionFiles(maxAgeDays, limit) {
|
|
17290
|
-
const home =
|
|
17585
|
+
const home = homedir16();
|
|
17291
17586
|
const results = [];
|
|
17292
17587
|
const searchPaths = [
|
|
17293
17588
|
{ pattern: `${home}/.claude/projects`, agent: "claude-code" },
|
|
@@ -31829,7 +32124,7 @@ import { Database as Database2 } from "bun:sqlite";
|
|
|
31829
32124
|
import { mkdirSync as mkdirSync9, readFileSync as readFileSync10 } from "fs";
|
|
31830
32125
|
import { connect as connectHttp2 } from "http2";
|
|
31831
32126
|
import { createPrivateKey, sign as signWithKey } from "crypto";
|
|
31832
|
-
import { dirname as
|
|
32127
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
31833
32128
|
var MOBILE_PUSH_SCHEMA = `
|
|
31834
32129
|
CREATE TABLE IF NOT EXISTS mobile_push_registrations (
|
|
31835
32130
|
id TEXT PRIMARY KEY,
|
|
@@ -31857,7 +32152,7 @@ var dbHandle = null;
|
|
|
31857
32152
|
var dbPath = null;
|
|
31858
32153
|
var cachedApnsJwt = null;
|
|
31859
32154
|
function resolveControlPlaneDbPath() {
|
|
31860
|
-
return
|
|
32155
|
+
return join19(resolveOpenScoutSupportPaths().controlHome, "control-plane.sqlite");
|
|
31861
32156
|
}
|
|
31862
32157
|
function ensureMobilePushSchema(database) {
|
|
31863
32158
|
database.exec("PRAGMA busy_timeout = 5000;");
|
|
@@ -31871,7 +32166,7 @@ function writeDb() {
|
|
|
31871
32166
|
dbHandle = null;
|
|
31872
32167
|
}
|
|
31873
32168
|
if (!dbHandle) {
|
|
31874
|
-
mkdirSync9(
|
|
32169
|
+
mkdirSync9(dirname8(nextPath), { recursive: true });
|
|
31875
32170
|
dbHandle = new Database2(nextPath, { create: true });
|
|
31876
32171
|
dbPath = nextPath;
|
|
31877
32172
|
ensureMobilePushSchema(dbHandle);
|
|
@@ -32087,7 +32382,7 @@ async function sendApnsAlertToRegistration(registration, alert) {
|
|
|
32087
32382
|
},
|
|
32088
32383
|
...alert.payload ? { scout: alert.payload } : {}
|
|
32089
32384
|
});
|
|
32090
|
-
return new Promise((
|
|
32385
|
+
return new Promise((resolve8, reject) => {
|
|
32091
32386
|
const client = connectHttp2(authority);
|
|
32092
32387
|
client.once("error", reject);
|
|
32093
32388
|
const request = client.request({
|
|
@@ -32125,7 +32420,7 @@ async function sendApnsAlertToRegistration(registration, alert) {
|
|
|
32125
32420
|
}
|
|
32126
32421
|
}
|
|
32127
32422
|
if (status === 200) {
|
|
32128
|
-
|
|
32423
|
+
resolve8({
|
|
32129
32424
|
delivered: true,
|
|
32130
32425
|
skipped: false,
|
|
32131
32426
|
status,
|
|
@@ -32137,7 +32432,7 @@ async function sendApnsAlertToRegistration(registration, alert) {
|
|
|
32137
32432
|
if (reason === "BadDeviceToken" || reason === "Unregistered") {
|
|
32138
32433
|
deleteMobilePushRegistrationByToken(registration.pushToken);
|
|
32139
32434
|
}
|
|
32140
|
-
|
|
32435
|
+
resolve8({
|
|
32141
32436
|
delivered: false,
|
|
32142
32437
|
skipped: false,
|
|
32143
32438
|
status,
|
|
@@ -32200,8 +32495,8 @@ async function broadcastApnsAlertToActiveMobileDevices(alert) {
|
|
|
32200
32495
|
// ../../apps/desktop/src/core/pairing/runtime/bridge/router.ts
|
|
32201
32496
|
import { readFileSync as readFileSync11, readdirSync as readdirSync4, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
32202
32497
|
import { execSync as execSync4 } from "child_process";
|
|
32203
|
-
import { basename as
|
|
32204
|
-
import { homedir as
|
|
32498
|
+
import { basename as basename8, isAbsolute as isAbsolute4, join as join20, relative as relative3 } from "path";
|
|
32499
|
+
import { homedir as homedir17 } from "os";
|
|
32205
32500
|
var t = initTRPC.context().create();
|
|
32206
32501
|
var logged = t.middleware(async ({ path: path2, type, next }) => {
|
|
32207
32502
|
const start = Date.now();
|
|
@@ -32228,13 +32523,13 @@ function resolveMobileCurrentDirectory2() {
|
|
|
32228
32523
|
}
|
|
32229
32524
|
}
|
|
32230
32525
|
function resolveWorkspaceRoot2(root) {
|
|
32231
|
-
const expandedRoot = root.replace(/^~/,
|
|
32526
|
+
const expandedRoot = root.replace(/^~/, homedir17());
|
|
32232
32527
|
return realpathSync2(expandedRoot);
|
|
32233
32528
|
}
|
|
32234
32529
|
function resolveWorkspacePath2(root, requestedPath) {
|
|
32235
32530
|
const normalizedRoot = resolveWorkspaceRoot2(root);
|
|
32236
|
-
const expandedPath = requestedPath?.replace(/^~/,
|
|
32237
|
-
const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath :
|
|
32531
|
+
const expandedPath = requestedPath?.replace(/^~/, homedir17());
|
|
32532
|
+
const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath : join20(normalizedRoot, expandedPath) : normalizedRoot;
|
|
32238
32533
|
const resolvedCandidate = realpathSync2(candidate);
|
|
32239
32534
|
const rel = relative3(normalizedRoot, resolvedCandidate);
|
|
32240
32535
|
if (rel === "" || !rel.startsWith("..") && !isAbsolute4(rel)) {
|
|
@@ -32264,7 +32559,7 @@ function listDirectories2(dirPath) {
|
|
|
32264
32559
|
continue;
|
|
32265
32560
|
if (name === "node_modules" || name === ".build" || name === "target")
|
|
32266
32561
|
continue;
|
|
32267
|
-
const fullPath =
|
|
32562
|
+
const fullPath = join20(dirPath, name);
|
|
32268
32563
|
try {
|
|
32269
32564
|
const stat4 = statSync4(fullPath);
|
|
32270
32565
|
if (!stat4.isDirectory())
|
|
@@ -32303,7 +32598,7 @@ function detectAgent2(filePath) {
|
|
|
32303
32598
|
return "unknown";
|
|
32304
32599
|
}
|
|
32305
32600
|
async function discoverSessionFiles2(maxAgeDays, limit) {
|
|
32306
|
-
const home =
|
|
32601
|
+
const home = homedir17();
|
|
32307
32602
|
const results = [];
|
|
32308
32603
|
const searchPaths = [
|
|
32309
32604
|
{ pattern: `${home}/.claude/projects`, agent: "claude-code" },
|
|
@@ -32385,23 +32680,23 @@ function bridgeEventIterable(bridge, signal) {
|
|
|
32385
32680
|
return {
|
|
32386
32681
|
[Symbol.asyncIterator]() {
|
|
32387
32682
|
const buffer = [];
|
|
32388
|
-
let
|
|
32683
|
+
let resolve8 = null;
|
|
32389
32684
|
let done = false;
|
|
32390
32685
|
const unsub = bridge.onEvent((event) => {
|
|
32391
32686
|
if (done)
|
|
32392
32687
|
return;
|
|
32393
32688
|
buffer.push(event);
|
|
32394
|
-
if (
|
|
32395
|
-
|
|
32396
|
-
|
|
32689
|
+
if (resolve8) {
|
|
32690
|
+
resolve8();
|
|
32691
|
+
resolve8 = null;
|
|
32397
32692
|
}
|
|
32398
32693
|
});
|
|
32399
32694
|
const cleanup = () => {
|
|
32400
32695
|
done = true;
|
|
32401
32696
|
unsub();
|
|
32402
|
-
if (
|
|
32403
|
-
|
|
32404
|
-
|
|
32697
|
+
if (resolve8) {
|
|
32698
|
+
resolve8();
|
|
32699
|
+
resolve8 = null;
|
|
32405
32700
|
}
|
|
32406
32701
|
};
|
|
32407
32702
|
signal?.addEventListener("abort", cleanup, { once: true });
|
|
@@ -32414,7 +32709,7 @@ function bridgeEventIterable(bridge, signal) {
|
|
|
32414
32709
|
return { done: false, value: buffer.shift() };
|
|
32415
32710
|
}
|
|
32416
32711
|
await new Promise((r) => {
|
|
32417
|
-
|
|
32712
|
+
resolve8 = r;
|
|
32418
32713
|
});
|
|
32419
32714
|
}
|
|
32420
32715
|
},
|
|
@@ -32543,9 +32838,9 @@ var sessionRouter = t.router({
|
|
|
32543
32838
|
adapterType: exports_external.string().optional(),
|
|
32544
32839
|
name: exports_external.string().optional()
|
|
32545
32840
|
})).mutation(async ({ input, ctx }) => {
|
|
32546
|
-
const sessionFilename =
|
|
32841
|
+
const sessionFilename = basename8(input.sessionPath, ".jsonl");
|
|
32547
32842
|
const parentDir = input.sessionPath.substring(0, input.sessionPath.lastIndexOf("/"));
|
|
32548
|
-
const dirName =
|
|
32843
|
+
const dirName = basename8(parentDir);
|
|
32549
32844
|
let cwd;
|
|
32550
32845
|
if (dirName.startsWith("-")) {
|
|
32551
32846
|
const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
|
|
@@ -32845,7 +33140,7 @@ var workspaceRouter = t.router({
|
|
|
32845
33140
|
});
|
|
32846
33141
|
}
|
|
32847
33142
|
const adapterType = input.adapter ?? "claude-code";
|
|
32848
|
-
const name = input.name ??
|
|
33143
|
+
const name = input.name ?? basename8(projectPath);
|
|
32849
33144
|
return ctx.bridge.createSession(adapterType, {
|
|
32850
33145
|
name,
|
|
32851
33146
|
cwd: projectPath
|
|
@@ -33092,7 +33387,8 @@ var BACKOFF_MULTIPLIER = 2;
|
|
|
33092
33387
|
var LEGACY_CLIENT_ID = "__legacy__";
|
|
33093
33388
|
function connectToRelay(relayUrl, identity2, bridge, options = {}) {
|
|
33094
33389
|
const { secure = true, events: events2 } = options;
|
|
33095
|
-
const
|
|
33390
|
+
const publicRelayUrl = options.publicRelayUrl?.trim() || relayUrl;
|
|
33391
|
+
const qrPayload = createQRPayload(identity2.publicKey, publicRelayUrl, options.fallbackRelayUrls);
|
|
33096
33392
|
let ws = null;
|
|
33097
33393
|
let eventUnsub = null;
|
|
33098
33394
|
let stopped = false;
|
|
@@ -33106,12 +33402,12 @@ function connectToRelay(relayUrl, identity2, bridge, options = {}) {
|
|
|
33106
33402
|
const bridgeKeyHex = bytesToHex2(identity2.publicKey);
|
|
33107
33403
|
const url2 = buildRelayUrl(relayUrl, qrPayload.room, bridgeKeyHex);
|
|
33108
33404
|
console.log(`[relay-client] connecting to relay (room: ${qrPayload.room})`);
|
|
33109
|
-
events2?.onConnecting?.({ relayUrl, room: qrPayload.room });
|
|
33405
|
+
events2?.onConnecting?.({ relayUrl: publicRelayUrl, room: qrPayload.room });
|
|
33110
33406
|
ws = new WebSocket(url2, relayWebSocketOptions(relayUrl));
|
|
33111
33407
|
ws.addEventListener("open", () => {
|
|
33112
33408
|
console.log(`[relay-client] connected to relay (room: ${qrPayload.room})`);
|
|
33113
33409
|
backoff = INITIAL_BACKOFF_MS;
|
|
33114
|
-
events2?.onConnected?.({ relayUrl, room: qrPayload.room });
|
|
33410
|
+
events2?.onConnected?.({ relayUrl: publicRelayUrl, room: qrPayload.room });
|
|
33115
33411
|
});
|
|
33116
33412
|
ws.addEventListener("message", (event) => {
|
|
33117
33413
|
handleRelaySocketMessage(event.data);
|
|
@@ -33119,7 +33415,7 @@ function connectToRelay(relayUrl, identity2, bridge, options = {}) {
|
|
|
33119
33415
|
ws.addEventListener("close", (event) => {
|
|
33120
33416
|
console.log(`[relay-client] disconnected (code: ${event.code}, reason: ${event.reason})`);
|
|
33121
33417
|
events2?.onClosed?.({
|
|
33122
|
-
relayUrl,
|
|
33418
|
+
relayUrl: publicRelayUrl,
|
|
33123
33419
|
room: qrPayload.room,
|
|
33124
33420
|
code: event.code,
|
|
33125
33421
|
reason: event.reason
|
|
@@ -33130,7 +33426,7 @@ function connectToRelay(relayUrl, identity2, bridge, options = {}) {
|
|
|
33130
33426
|
ws.addEventListener("error", () => {
|
|
33131
33427
|
const error48 = new Error("Relay websocket error");
|
|
33132
33428
|
console.error("[relay-client] connection error");
|
|
33133
|
-
events2?.onError?.({ relayUrl, room: qrPayload.room, error: error48 });
|
|
33429
|
+
events2?.onError?.({ relayUrl: publicRelayUrl, room: qrPayload.room, error: error48 });
|
|
33134
33430
|
});
|
|
33135
33431
|
}
|
|
33136
33432
|
function handleRelaySocketMessage(raw) {
|
|
@@ -33184,7 +33480,7 @@ function connectToRelay(relayUrl, identity2, bridge, options = {}) {
|
|
|
33184
33480
|
replaceOlderPeerForSameDevice(peer);
|
|
33185
33481
|
ensureBridgeEventSubscription();
|
|
33186
33482
|
console.log(`[relay-client] secure handshake complete (peer: ${pubHex.slice(0, 12)}..., device: ${peer.deviceId}, client: ${clientId})`);
|
|
33187
|
-
events2?.onPaired?.({ relayUrl, room: qrPayload.room, remotePublicKey });
|
|
33483
|
+
events2?.onPaired?.({ relayUrl: publicRelayUrl, room: qrPayload.room, remotePublicKey });
|
|
33188
33484
|
sendExistingSessions((json2) => {
|
|
33189
33485
|
if (peer.transport.isReady()) {
|
|
33190
33486
|
peer.transport.send(json2);
|
|
@@ -33197,7 +33493,7 @@ function connectToRelay(relayUrl, identity2, bridge, options = {}) {
|
|
|
33197
33493
|
onError: (err) => {
|
|
33198
33494
|
console.error("[relay-client] secure transport error:", err.message);
|
|
33199
33495
|
log.error("trns:cry", `decrypt failed for ${clientId} \u2014 resetting handshake: ${err.message}`);
|
|
33200
|
-
events2?.onError?.({ relayUrl, room: qrPayload.room, error: err });
|
|
33496
|
+
events2?.onError?.({ relayUrl: publicRelayUrl, room: qrPayload.room, error: err });
|
|
33201
33497
|
sendRelayClose(clientId, 4002, "Transport reset");
|
|
33202
33498
|
teardownSecurePeer(clientId);
|
|
33203
33499
|
},
|
|
@@ -33396,7 +33692,7 @@ function connectToRelay(relayUrl, identity2, bridge, options = {}) {
|
|
|
33396
33692
|
if (stopped)
|
|
33397
33693
|
return;
|
|
33398
33694
|
console.log(`[relay-client] reconnecting in ${backoff}ms...`);
|
|
33399
|
-
events2?.onReconnectScheduled?.({ relayUrl, room: qrPayload.room, delayMs: backoff });
|
|
33695
|
+
events2?.onReconnectScheduled?.({ relayUrl: publicRelayUrl, room: qrPayload.room, delayMs: backoff });
|
|
33400
33696
|
reconnectTimer = setTimeout(() => {
|
|
33401
33697
|
reconnectTimer = null;
|
|
33402
33698
|
connect();
|
|
@@ -35392,8 +35688,8 @@ var Unpromise = class Unpromise2 {
|
|
|
35392
35688
|
status: "fulfilled",
|
|
35393
35689
|
value
|
|
35394
35690
|
};
|
|
35395
|
-
subscribers === null || subscribers === undefined || subscribers.forEach(({ resolve:
|
|
35396
|
-
|
|
35691
|
+
subscribers === null || subscribers === undefined || subscribers.forEach(({ resolve: resolve8 }) => {
|
|
35692
|
+
resolve8(value);
|
|
35397
35693
|
});
|
|
35398
35694
|
});
|
|
35399
35695
|
if ("catch" in thenReturn)
|
|
@@ -35501,15 +35797,15 @@ function resolveSelfTuple(promise2) {
|
|
|
35501
35797
|
return Unpromise.proxy(promise2).then(() => [promise2]);
|
|
35502
35798
|
}
|
|
35503
35799
|
function withResolvers() {
|
|
35504
|
-
let
|
|
35800
|
+
let resolve8;
|
|
35505
35801
|
let reject;
|
|
35506
35802
|
const promise2 = new Promise((_resolve, _reject) => {
|
|
35507
|
-
|
|
35803
|
+
resolve8 = _resolve;
|
|
35508
35804
|
reject = _reject;
|
|
35509
35805
|
});
|
|
35510
35806
|
return {
|
|
35511
35807
|
promise: promise2,
|
|
35512
|
-
resolve:
|
|
35808
|
+
resolve: resolve8,
|
|
35513
35809
|
reject
|
|
35514
35810
|
};
|
|
35515
35811
|
}
|
|
@@ -35555,8 +35851,8 @@ function timerResource(ms) {
|
|
|
35555
35851
|
return makeResource({ start() {
|
|
35556
35852
|
if (timer)
|
|
35557
35853
|
throw new Error("Timer already started");
|
|
35558
|
-
const promise2 = new Promise((
|
|
35559
|
-
timer = setTimeout(() =>
|
|
35854
|
+
const promise2 = new Promise((resolve8) => {
|
|
35855
|
+
timer = setTimeout(() => resolve8(disposablePromiseTimerResult), ms);
|
|
35560
35856
|
});
|
|
35561
35857
|
return promise2;
|
|
35562
35858
|
} }, () => {
|
|
@@ -35759,15 +36055,15 @@ function _takeWithGrace() {
|
|
|
35759
36055
|
return _takeWithGrace.apply(this, arguments);
|
|
35760
36056
|
}
|
|
35761
36057
|
function createDeferred() {
|
|
35762
|
-
let
|
|
36058
|
+
let resolve8;
|
|
35763
36059
|
let reject;
|
|
35764
36060
|
const promise2 = new Promise((res, rej) => {
|
|
35765
|
-
|
|
36061
|
+
resolve8 = res;
|
|
35766
36062
|
reject = rej;
|
|
35767
36063
|
});
|
|
35768
36064
|
return {
|
|
35769
36065
|
promise: promise2,
|
|
35770
|
-
resolve:
|
|
36066
|
+
resolve: resolve8,
|
|
35771
36067
|
reject
|
|
35772
36068
|
};
|
|
35773
36069
|
}
|
|
@@ -36998,14 +37294,14 @@ function parseTRPCMessage(obj, transformer) {
|
|
|
36998
37294
|
}
|
|
36999
37295
|
// ../../apps/desktop/src/core/pairing/runtime/bridge/server-trpc.ts
|
|
37000
37296
|
import { realpathSync as realpathSync3 } from "fs";
|
|
37001
|
-
import { homedir as
|
|
37297
|
+
import { homedir as homedir18 } from "os";
|
|
37002
37298
|
function resolveCurrentDirectory() {
|
|
37003
37299
|
try {
|
|
37004
37300
|
const config2 = resolveConfig();
|
|
37005
37301
|
const configuredRoot = config2.workspace?.root;
|
|
37006
37302
|
if (!configuredRoot)
|
|
37007
37303
|
return process.cwd();
|
|
37008
|
-
const expanded = configuredRoot.replace(/^~/,
|
|
37304
|
+
const expanded = configuredRoot.replace(/^~/, homedir18());
|
|
37009
37305
|
return realpathSync3(expanded);
|
|
37010
37306
|
} catch {
|
|
37011
37307
|
return process.cwd();
|
|
@@ -37152,8 +37448,8 @@ function startBridgeServerTRPC(options) {
|
|
|
37152
37448
|
}
|
|
37153
37449
|
const iterable = isObservable(result) ? observableToAsyncIterable(result, abortController.signal) : result;
|
|
37154
37450
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
37155
|
-
const abortPromise = new Promise((
|
|
37156
|
-
abortController.signal.addEventListener("abort", () =>
|
|
37451
|
+
const abortPromise = new Promise((resolve8) => {
|
|
37452
|
+
abortController.signal.addEventListener("abort", () => resolve8("abort"), {
|
|
37157
37453
|
once: true
|
|
37158
37454
|
});
|
|
37159
37455
|
});
|
|
@@ -37498,6 +37794,8 @@ async function startPairingRuntime(options) {
|
|
|
37498
37794
|
}
|
|
37499
37795
|
const relayConnection = connectToRelay(relayUrl, identity2, bridge, {
|
|
37500
37796
|
secure: true,
|
|
37797
|
+
publicRelayUrl: options?.advertisedRelayUrl ?? undefined,
|
|
37798
|
+
fallbackRelayUrls: options?.fallbackRelayUrls,
|
|
37501
37799
|
events: options?.relayEvents
|
|
37502
37800
|
});
|
|
37503
37801
|
await autoStartConfiguredSessions(bridge, config2.sessions);
|
|
@@ -37525,7 +37823,7 @@ async function autoStartConfiguredSessions(bridge, sessions3) {
|
|
|
37525
37823
|
try {
|
|
37526
37824
|
const session = await bridge.createSession(entry.adapter, {
|
|
37527
37825
|
name: entry.name,
|
|
37528
|
-
cwd: entry.cwd?.replace(/^~/,
|
|
37826
|
+
cwd: entry.cwd?.replace(/^~/, homedir19()),
|
|
37529
37827
|
options: entry.options
|
|
37530
37828
|
});
|
|
37531
37829
|
console.log(`[bridge] session started: ${session.name} (${entry.adapter})`);
|
|
@@ -37539,6 +37837,7 @@ async function autoStartConfiguredSessions(bridge, sessions3) {
|
|
|
37539
37837
|
// ../../apps/desktop/src/core/pairing/supervisor.ts
|
|
37540
37838
|
var SCOUT_PAIR_REFRESH_LEEWAY_MS = 30000;
|
|
37541
37839
|
var SCOUT_PAIR_RESTART_DELAY_MS = 2000;
|
|
37840
|
+
var BONJOUR_SERVICE_TYPE = "_scout-pair._tcp";
|
|
37542
37841
|
async function runScoutPairingSupervisor() {
|
|
37543
37842
|
clearStalePairingRuntimeFiles();
|
|
37544
37843
|
const existingPid = readPairingRuntimePid();
|
|
@@ -37560,7 +37859,8 @@ async function runScoutPairingSupervisor() {
|
|
|
37560
37859
|
refreshTimer: null,
|
|
37561
37860
|
intentionalStop: false,
|
|
37562
37861
|
runtime: null,
|
|
37563
|
-
relay: null
|
|
37862
|
+
relay: null,
|
|
37863
|
+
bonjour: null
|
|
37564
37864
|
};
|
|
37565
37865
|
const shutdown = async () => {
|
|
37566
37866
|
state.intentionalStop = true;
|
|
@@ -37588,6 +37888,8 @@ async function startSupervisorRuntime(state) {
|
|
|
37588
37888
|
const config2 = resolvedPairingConfig();
|
|
37589
37889
|
const relayPort = config2.port + 1;
|
|
37590
37890
|
const resolvedRelayUrl = config2.relay?.trim() || null;
|
|
37891
|
+
const identity2 = loadOrCreateIdentity();
|
|
37892
|
+
const publicKeyHex = bytesToHex2(identity2.publicKey);
|
|
37591
37893
|
writeCurrent(state, {
|
|
37592
37894
|
status: "starting",
|
|
37593
37895
|
statusLabel: "Starting",
|
|
@@ -37599,12 +37901,24 @@ async function startSupervisorRuntime(state) {
|
|
|
37599
37901
|
});
|
|
37600
37902
|
const emitStatus = createStatusWriter(state);
|
|
37601
37903
|
try {
|
|
37602
|
-
const
|
|
37904
|
+
const managedRelay = resolvedRelayUrl ? null : (() => {
|
|
37603
37905
|
state.relay = startManagedRelay(relayPort);
|
|
37604
|
-
return state.relay
|
|
37906
|
+
return state.relay;
|
|
37605
37907
|
})();
|
|
37606
|
-
|
|
37908
|
+
const activeRelayUrl = resolvedRelayUrl ?? managedRelay?.relayUrl;
|
|
37909
|
+
const connectRelayUrl = resolvedRelayUrl ?? managedRelay?.connectUrl ?? managedRelay?.relayUrl;
|
|
37910
|
+
if (!activeRelayUrl || !connectRelayUrl) {
|
|
37911
|
+
throw new Error("Scout pairing relay URL is not configured.");
|
|
37912
|
+
}
|
|
37913
|
+
state.bonjour = managedRelay ? startBonjourRelayAdvertisement({
|
|
37914
|
+
port: relayPort,
|
|
37607
37915
|
relayUrl: activeRelayUrl,
|
|
37916
|
+
publicKeyHex
|
|
37917
|
+
}) : null;
|
|
37918
|
+
state.runtime = await startPairingRuntime({
|
|
37919
|
+
relayUrl: connectRelayUrl,
|
|
37920
|
+
advertisedRelayUrl: activeRelayUrl,
|
|
37921
|
+
fallbackRelayUrls: managedRelay?.fallbackRelayUrls,
|
|
37608
37922
|
relayEvents: {
|
|
37609
37923
|
onConnecting() {
|
|
37610
37924
|
emitStatus("connecting", `Connecting to ${activeRelayUrl}`);
|
|
@@ -37627,7 +37941,6 @@ async function startSupervisorRuntime(state) {
|
|
|
37627
37941
|
if (!payload) {
|
|
37628
37942
|
throw new Error("Scout pairing runtime did not produce a QR payload.");
|
|
37629
37943
|
}
|
|
37630
|
-
const identity2 = loadOrCreateIdentity();
|
|
37631
37944
|
writeCurrent(state, {
|
|
37632
37945
|
status: "connecting",
|
|
37633
37946
|
statusLabel: "Pairing Ready",
|
|
@@ -37644,7 +37957,7 @@ async function startSupervisorRuntime(state) {
|
|
|
37644
37957
|
},
|
|
37645
37958
|
childPid: null
|
|
37646
37959
|
}, {
|
|
37647
|
-
identityFingerprint:
|
|
37960
|
+
identityFingerprint: publicKeyHex.slice(0, 16),
|
|
37648
37961
|
trustedPeerCount: trustedPeerCount()
|
|
37649
37962
|
});
|
|
37650
37963
|
schedulePairingRefresh(state, payload);
|
|
@@ -37718,6 +38031,9 @@ function clearRefreshTimer(state) {
|
|
|
37718
38031
|
}
|
|
37719
38032
|
}
|
|
37720
38033
|
async function stopSupervisorRuntime(state) {
|
|
38034
|
+
try {
|
|
38035
|
+
state.bonjour?.stop();
|
|
38036
|
+
} catch {}
|
|
37721
38037
|
try {
|
|
37722
38038
|
await state.runtime?.stop();
|
|
37723
38039
|
} catch {}
|
|
@@ -37726,6 +38042,57 @@ async function stopSupervisorRuntime(state) {
|
|
|
37726
38042
|
} catch {}
|
|
37727
38043
|
state.runtime = null;
|
|
37728
38044
|
state.relay = null;
|
|
38045
|
+
state.bonjour = null;
|
|
38046
|
+
}
|
|
38047
|
+
function startBonjourRelayAdvertisement(input) {
|
|
38048
|
+
if (process.platform !== "darwin") {
|
|
38049
|
+
return null;
|
|
38050
|
+
}
|
|
38051
|
+
const scheme = relayScheme(input.relayUrl);
|
|
38052
|
+
const fingerprint = input.publicKeyHex.slice(0, 16);
|
|
38053
|
+
const serviceName = `OpenScout ${fingerprint}`;
|
|
38054
|
+
const args = [
|
|
38055
|
+
"-R",
|
|
38056
|
+
serviceName,
|
|
38057
|
+
BONJOUR_SERVICE_TYPE,
|
|
38058
|
+
"local.",
|
|
38059
|
+
String(input.port),
|
|
38060
|
+
"v=1",
|
|
38061
|
+
`pk=${input.publicKeyHex}`,
|
|
38062
|
+
`fp=${fingerprint}`,
|
|
38063
|
+
`scheme=${scheme}`
|
|
38064
|
+
];
|
|
38065
|
+
let processRef = null;
|
|
38066
|
+
try {
|
|
38067
|
+
processRef = spawn4("/usr/bin/dns-sd", args, { stdio: "ignore" });
|
|
38068
|
+
processRef.on("error", (error48) => {
|
|
38069
|
+
console.warn(`[pairing] bonjour advertisement failed: ${error48.message}`);
|
|
38070
|
+
});
|
|
38071
|
+
processRef.on("exit", (code, signal) => {
|
|
38072
|
+
if (code !== 0 && signal !== "SIGTERM") {
|
|
38073
|
+
console.warn(`[pairing] bonjour advertisement exited (code=${code ?? "null"}, signal=${signal ?? "null"})`);
|
|
38074
|
+
}
|
|
38075
|
+
});
|
|
38076
|
+
console.log(`[pairing] bonjour advertising ${serviceName} on ${BONJOUR_SERVICE_TYPE} port ${input.port}`);
|
|
38077
|
+
} catch (error48) {
|
|
38078
|
+
const detail = error48 instanceof Error ? error48.message : String(error48);
|
|
38079
|
+
console.warn(`[pairing] bonjour advertisement unavailable: ${detail}`);
|
|
38080
|
+
return null;
|
|
38081
|
+
}
|
|
38082
|
+
return {
|
|
38083
|
+
stop() {
|
|
38084
|
+
processRef?.kill("SIGTERM");
|
|
38085
|
+
processRef = null;
|
|
38086
|
+
}
|
|
38087
|
+
};
|
|
38088
|
+
}
|
|
38089
|
+
function relayScheme(relayUrl) {
|
|
38090
|
+
try {
|
|
38091
|
+
const protocol = new URL(relayUrl).protocol;
|
|
38092
|
+
return protocol === "wss:" ? "wss" : "ws";
|
|
38093
|
+
} catch {
|
|
38094
|
+
return relayUrl.startsWith("wss://") ? "wss" : "ws";
|
|
38095
|
+
}
|
|
37729
38096
|
}
|
|
37730
38097
|
function writeCurrent(state, patch, overrides = {}) {
|
|
37731
38098
|
state.current = writePairingRuntimeSnapshot({
|