@openscout/scout 0.2.68 → 0.2.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/openscout-runtime.mjs +11 -0
- package/dist/client/assets/addon-fit-DtSNdpy5.js +1 -0
- package/dist/client/assets/addon-webgl-YYNKPQUg.js +82 -0
- package/dist/client/assets/index-5-XOQrKH.js +186 -0
- package/dist/client/assets/index-BnA2HAbt.css +1 -0
- package/dist/client/assets/xterm-fBtFsYpq.js +34 -0
- package/dist/client/index.html +3 -3
- package/dist/main.mjs +2097 -504
- package/dist/openscout-terminal-relay.mjs +678 -0
- package/dist/pair-supervisor.mjs +1728 -183
- package/dist/runtime/base-daemon.mjs +19 -0
- package/dist/runtime/broker-daemon.mjs +2502 -382
- package/dist/runtime/broker-process-manager.mjs +11 -0
- package/dist/runtime/mesh-discover.mjs +11 -0
- package/dist/scout-control-plane-web.mjs +3220 -480
- package/dist/scout-web-server.mjs +3220 -480
- package/package.json +4 -2
- package/dist/client/assets/addon-fit_hudsonkit_false-DSvcGNZx.js +0 -1
- package/dist/client/assets/addon-webgl_hudsonkit_false-1rrLEmjL.js +0 -1
- package/dist/client/assets/index-R2y6GOWl.js +0 -184
- package/dist/client/assets/index-bOmc2x1_.css +0 -1
- package/dist/client/assets/xterm_hudsonkit_false-B2TbElXJ.js +0 -1
package/dist/pair-supervisor.mjs
CHANGED
|
@@ -7996,6 +7996,17 @@ function assertValidUnblockRequestEvent(event, record) {
|
|
|
7996
7996
|
throw new Error(errors.join("; "));
|
|
7997
7997
|
}
|
|
7998
7998
|
}
|
|
7999
|
+
// ../protocol/src/lifecycle.ts
|
|
8000
|
+
var TERMINAL_INVOCATION_STATES;
|
|
8001
|
+
var init_lifecycle = __esm(() => {
|
|
8002
|
+
TERMINAL_INVOCATION_STATES = new Set([
|
|
8003
|
+
"completed",
|
|
8004
|
+
"failed",
|
|
8005
|
+
"cancelled",
|
|
8006
|
+
"expired"
|
|
8007
|
+
]);
|
|
8008
|
+
});
|
|
8009
|
+
|
|
7999
8010
|
// ../protocol/src/permission-policy.ts
|
|
8000
8011
|
function normalizeScoutPermissionProfile(value) {
|
|
8001
8012
|
const normalized = value?.trim().replaceAll("-", "_");
|
|
@@ -8048,15 +8059,20 @@ var init_scout_composer = __esm(() => {
|
|
|
8048
8059
|
});
|
|
8049
8060
|
// ../protocol/src/inbox.ts
|
|
8050
8061
|
var init_inbox = () => {};
|
|
8062
|
+
// ../protocol/src/cursor-transport.ts
|
|
8063
|
+
var init_cursor_transport = () => {};
|
|
8064
|
+
|
|
8051
8065
|
// ../protocol/src/index.ts
|
|
8052
8066
|
var init_src2 = __esm(() => {
|
|
8053
8067
|
init_agent_identity();
|
|
8054
8068
|
init_agent_address();
|
|
8055
8069
|
init_agent_selectors();
|
|
8070
|
+
init_lifecycle();
|
|
8056
8071
|
init_agent_runs();
|
|
8057
8072
|
init_scout_composer();
|
|
8058
8073
|
init_inbox();
|
|
8059
8074
|
init_permission_policy();
|
|
8075
|
+
init_cursor_transport();
|
|
8060
8076
|
});
|
|
8061
8077
|
|
|
8062
8078
|
// ../runtime/src/user-project-hints.ts
|
|
@@ -10231,6 +10247,9 @@ var init_setup = __esm(() => {
|
|
|
10231
10247
|
};
|
|
10232
10248
|
});
|
|
10233
10249
|
|
|
10250
|
+
// ../runtime/src/dispatch-stalled.ts
|
|
10251
|
+
var init_dispatch_stalled = () => {};
|
|
10252
|
+
|
|
10234
10253
|
// ../runtime/src/managed-agent-environment.ts
|
|
10235
10254
|
function managedAgentEnvironmentEntries(options) {
|
|
10236
10255
|
const agentName = options.agentName.trim();
|
|
@@ -11023,6 +11042,15 @@ function readCodexAppServerReasoningEffortFromLaunchArgs(launchArgs) {
|
|
|
11023
11042
|
}
|
|
11024
11043
|
return null;
|
|
11025
11044
|
}
|
|
11045
|
+
function codexAppServerExitMessage(input) {
|
|
11046
|
+
if (input.exitKind === "proactive_shutdown") {
|
|
11047
|
+
return `Codex app-server session for ${input.agentName} was stopped by OpenScout` + (input.reason ? `: ${input.reason}` : "") + ".";
|
|
11048
|
+
}
|
|
11049
|
+
if (input.exitKind === "external_sigterm") {
|
|
11050
|
+
return `Codex app-server for ${input.agentName} was interrupted by SIGTERM.`;
|
|
11051
|
+
}
|
|
11052
|
+
return `Codex app-server exited for ${input.agentName}` + (input.exitCode !== null ? ` with code ${input.exitCode}` : "") + (input.signal ? ` (${input.signal})` : "");
|
|
11053
|
+
}
|
|
11026
11054
|
function resolveRequesterTimeoutMs2(timeoutMs) {
|
|
11027
11055
|
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
11028
11056
|
return timeoutMs;
|
|
@@ -11194,6 +11222,7 @@ class CodexAppServerSession {
|
|
|
11194
11222
|
activeTurn = null;
|
|
11195
11223
|
threadId = null;
|
|
11196
11224
|
threadPath = null;
|
|
11225
|
+
proactiveShutdown = null;
|
|
11197
11226
|
lastConfigSignature;
|
|
11198
11227
|
constructor(options) {
|
|
11199
11228
|
this.options = options;
|
|
@@ -11324,13 +11353,23 @@ class CodexAppServerSession {
|
|
|
11324
11353
|
});
|
|
11325
11354
|
}
|
|
11326
11355
|
async shutdown(options = {}) {
|
|
11356
|
+
this.proactiveShutdown = {
|
|
11357
|
+
reason: options.reason ?? (options.resetThread ? "OpenScout reset the app-server session" : "OpenScout stopped the app-server session")
|
|
11358
|
+
};
|
|
11359
|
+
const stoppedError = new CodexAppServerExitError({
|
|
11360
|
+
agentName: this.options.agentName,
|
|
11361
|
+
exitKind: "proactive_shutdown",
|
|
11362
|
+
exitCode: null,
|
|
11363
|
+
signal: null,
|
|
11364
|
+
reason: this.proactiveShutdown.reason
|
|
11365
|
+
});
|
|
11327
11366
|
const activeTurn = this.activeTurn;
|
|
11328
11367
|
this.activeTurn = null;
|
|
11329
11368
|
if (activeTurn) {
|
|
11330
|
-
activeTurn.reject(
|
|
11369
|
+
activeTurn.reject(stoppedError);
|
|
11331
11370
|
}
|
|
11332
11371
|
for (const pending of this.pendingRequests.values()) {
|
|
11333
|
-
pending.reject(
|
|
11372
|
+
pending.reject(stoppedError);
|
|
11334
11373
|
}
|
|
11335
11374
|
this.pendingRequests.clear();
|
|
11336
11375
|
const child = this.process;
|
|
@@ -11340,7 +11379,7 @@ class CodexAppServerSession {
|
|
|
11340
11379
|
if (child && child.exitCode === null && !child.killed) {
|
|
11341
11380
|
child.kill("SIGTERM");
|
|
11342
11381
|
await new Promise((resolve5) => setTimeout(resolve5, 250));
|
|
11343
|
-
if (child.exitCode === null &&
|
|
11382
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
11344
11383
|
child.kill("SIGKILL");
|
|
11345
11384
|
}
|
|
11346
11385
|
}
|
|
@@ -11391,6 +11430,7 @@ class CodexAppServerSession {
|
|
|
11391
11430
|
await mkdir5(this.options.runtimeDirectory, { recursive: true });
|
|
11392
11431
|
await mkdir5(this.options.logsDirectory, { recursive: true });
|
|
11393
11432
|
await writeFile5(join17(this.options.runtimeDirectory, "prompt.txt"), this.options.systemPrompt);
|
|
11433
|
+
this.proactiveShutdown = null;
|
|
11394
11434
|
const codexExecutable = resolveCodexExecutable();
|
|
11395
11435
|
const launchArgs = normalizeCodexAppServerLaunchArgs(this.options.launchArgs);
|
|
11396
11436
|
const env = buildManagedAgentEnvironment({
|
|
@@ -11430,7 +11470,16 @@ class CodexAppServerSession {
|
|
|
11430
11470
|
this.failSession(new Error(`Codex app-server failed for ${this.options.agentName}: ${errorMessage3(error)}`));
|
|
11431
11471
|
});
|
|
11432
11472
|
child.once("exit", (code, signal) => {
|
|
11433
|
-
|
|
11473
|
+
const proactiveShutdown = this.proactiveShutdown;
|
|
11474
|
+
if (proactiveShutdown) {
|
|
11475
|
+
this.handleProactiveProcessExit(child, proactiveShutdown, code, signal);
|
|
11476
|
+
return;
|
|
11477
|
+
}
|
|
11478
|
+
this.failSession(new CodexAppServerExitError({
|
|
11479
|
+
agentName: this.options.agentName,
|
|
11480
|
+
exitCode: code,
|
|
11481
|
+
signal
|
|
11482
|
+
}));
|
|
11434
11483
|
});
|
|
11435
11484
|
await this.request("initialize", {
|
|
11436
11485
|
clientInfo: {
|
|
@@ -11765,6 +11814,23 @@ class CodexAppServerSession {
|
|
|
11765
11814
|
}
|
|
11766
11815
|
this.pendingRequests.clear();
|
|
11767
11816
|
appendFile3(this.stderrLogPath, `[openscout] ${error.message}
|
|
11817
|
+
`).catch(() => {
|
|
11818
|
+
return;
|
|
11819
|
+
});
|
|
11820
|
+
this.persistState();
|
|
11821
|
+
}
|
|
11822
|
+
handleProactiveProcessExit(child, shutdown, code, signal) {
|
|
11823
|
+
if (this.process === child) {
|
|
11824
|
+
this.process = null;
|
|
11825
|
+
}
|
|
11826
|
+
this.proactiveShutdown = null;
|
|
11827
|
+
this.starting = null;
|
|
11828
|
+
const exitDetail = [
|
|
11829
|
+
code !== null ? `code ${code}` : null,
|
|
11830
|
+
signal
|
|
11831
|
+
].filter(Boolean).join(", ");
|
|
11832
|
+
const detail = exitDetail ? ` (${exitDetail})` : "";
|
|
11833
|
+
appendFile3(this.stderrLogPath, `[openscout] Codex app-server stopped for ${this.options.agentName}: ${shutdown.reason}${detail}
|
|
11768
11834
|
`).catch(() => {
|
|
11769
11835
|
return;
|
|
11770
11836
|
});
|
|
@@ -11835,7 +11901,10 @@ function getOrCreateSession2(options) {
|
|
|
11835
11901
|
if (existing.matches(options)) {
|
|
11836
11902
|
return existing;
|
|
11837
11903
|
}
|
|
11838
|
-
existing.shutdown({
|
|
11904
|
+
existing.shutdown({
|
|
11905
|
+
resetThread: true,
|
|
11906
|
+
reason: "OpenScout replaced the app-server session after its launch options changed"
|
|
11907
|
+
});
|
|
11839
11908
|
sessions2.delete(key);
|
|
11840
11909
|
}
|
|
11841
11910
|
const session = new CodexAppServerSession(options);
|
|
@@ -11866,13 +11935,36 @@ async function shutdownCodexAppServerAgent(options, shutdownOptions = {}) {
|
|
|
11866
11935
|
sessions2.delete(key);
|
|
11867
11936
|
await session.shutdown(shutdownOptions);
|
|
11868
11937
|
}
|
|
11869
|
-
var SESSION_CATALOG_FILENAME2 = "session-catalog.json", SESSION_CATALOG_MAX_ENTRIES2 = 64, CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS, sessions2;
|
|
11938
|
+
var CodexAppServerExitError, SESSION_CATALOG_FILENAME2 = "session-catalog.json", SESSION_CATALOG_MAX_ENTRIES2 = 64, CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS, sessions2;
|
|
11870
11939
|
var init_codex_app_server = __esm(() => {
|
|
11871
11940
|
init_src();
|
|
11872
11941
|
init_topology();
|
|
11873
11942
|
init_codex_executable();
|
|
11874
11943
|
init_requester_timeout();
|
|
11875
11944
|
init_codex_executable();
|
|
11945
|
+
CodexAppServerExitError = class CodexAppServerExitError extends Error {
|
|
11946
|
+
code = "CODEX_APP_SERVER_EXIT";
|
|
11947
|
+
exitKind;
|
|
11948
|
+
agentName;
|
|
11949
|
+
exitCode;
|
|
11950
|
+
signal;
|
|
11951
|
+
reason;
|
|
11952
|
+
noteworthy;
|
|
11953
|
+
constructor(input) {
|
|
11954
|
+
const exitKind = input.exitKind ?? (input.signal === "SIGTERM" ? "external_sigterm" : "unexpected_exit");
|
|
11955
|
+
super(codexAppServerExitMessage({
|
|
11956
|
+
...input,
|
|
11957
|
+
exitKind
|
|
11958
|
+
}));
|
|
11959
|
+
this.name = "CodexAppServerExitError";
|
|
11960
|
+
this.exitKind = exitKind;
|
|
11961
|
+
this.agentName = input.agentName;
|
|
11962
|
+
this.exitCode = input.exitCode;
|
|
11963
|
+
this.signal = input.signal;
|
|
11964
|
+
this.reason = input.reason ?? null;
|
|
11965
|
+
this.noteworthy = exitKind !== "unexpected_exit";
|
|
11966
|
+
}
|
|
11967
|
+
};
|
|
11876
11968
|
CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS = 10 * 60 * 1000;
|
|
11877
11969
|
sessions2 = new Map;
|
|
11878
11970
|
});
|
|
@@ -12499,6 +12591,17 @@ async function fetchHealthSnapshot(config) {
|
|
|
12499
12591
|
nodes: payload.counts.nodes ?? 0,
|
|
12500
12592
|
actors: payload.counts.actors ?? 0,
|
|
12501
12593
|
agents: payload.counts.agents ?? 0,
|
|
12594
|
+
agentRecords: payload.counts.agentRecords,
|
|
12595
|
+
rawAgentRecords: payload.counts.rawAgentRecords,
|
|
12596
|
+
configuredAgents: payload.counts.configuredAgents,
|
|
12597
|
+
scoutManagedAgents: payload.counts.scoutManagedAgents,
|
|
12598
|
+
currentAgentRegistrations: payload.counts.currentAgentRegistrations,
|
|
12599
|
+
localAgentRegistrations: payload.counts.localAgentRegistrations,
|
|
12600
|
+
remoteAgentRegistrations: payload.counts.remoteAgentRegistrations,
|
|
12601
|
+
staleAgentRegistrations: payload.counts.staleAgentRegistrations,
|
|
12602
|
+
retiredAgentRegistrations: payload.counts.retiredAgentRegistrations,
|
|
12603
|
+
oneTimeAgentCards: payload.counts.oneTimeAgentCards,
|
|
12604
|
+
persistentAgentCards: payload.counts.persistentAgentCards,
|
|
12502
12605
|
conversations: payload.counts.conversations ?? 0,
|
|
12503
12606
|
messages: payload.counts.messages ?? 0,
|
|
12504
12607
|
flights: payload.counts.flights ?? 0,
|
|
@@ -12736,11 +12839,37 @@ var init_permission_policy2 = __esm(() => {
|
|
|
12736
12839
|
init_src2();
|
|
12737
12840
|
});
|
|
12738
12841
|
|
|
12842
|
+
// ../runtime/src/user-config.ts
|
|
12843
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
|
|
12844
|
+
import { homedir as homedir18 } from "os";
|
|
12845
|
+
import { dirname as dirname9, join as join19 } from "path";
|
|
12846
|
+
function userConfigPath() {
|
|
12847
|
+
return join19(process.env.OPENSCOUT_HOME ?? join19(homedir18(), ".openscout"), "user.json");
|
|
12848
|
+
}
|
|
12849
|
+
function loadUserConfig() {
|
|
12850
|
+
const configPath = userConfigPath();
|
|
12851
|
+
if (!existsSync16(configPath))
|
|
12852
|
+
return {};
|
|
12853
|
+
try {
|
|
12854
|
+
return JSON.parse(readFileSync10(configPath, "utf8"));
|
|
12855
|
+
} catch {
|
|
12856
|
+
return {};
|
|
12857
|
+
}
|
|
12858
|
+
}
|
|
12859
|
+
function normalizeHandle(value) {
|
|
12860
|
+
return value?.trim().replace(/^@+/, "") ?? "";
|
|
12861
|
+
}
|
|
12862
|
+
function resolveOperatorHandle() {
|
|
12863
|
+
const config = loadUserConfig();
|
|
12864
|
+
return normalizeHandle(config.handle) || normalizeHandle(process.env.OPENSCOUT_OPERATOR_HANDLE) || normalizeHandle(config.name) || normalizeHandle(process.env.OPENSCOUT_OPERATOR_NAME) || normalizeHandle(process.env.USER) || "operator";
|
|
12865
|
+
}
|
|
12866
|
+
var init_user_config = () => {};
|
|
12867
|
+
|
|
12739
12868
|
// ../runtime/src/local-agents.ts
|
|
12740
12869
|
import { execFileSync as execFileSync4, execSync as execSync2 } from "child_process";
|
|
12741
|
-
import { existsSync as
|
|
12870
|
+
import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
|
|
12742
12871
|
import { mkdir as mkdir6, rm as rm5, stat as stat3, writeFile as writeFile6 } from "fs/promises";
|
|
12743
|
-
import { basename as basename8, dirname as
|
|
12872
|
+
import { basename as basename8, dirname as dirname10, join as join20, resolve as resolve6 } from "path";
|
|
12744
12873
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12745
12874
|
function resolveRelayHub() {
|
|
12746
12875
|
return resolveOpenScoutSupportPaths().relayHubDirectory;
|
|
@@ -12751,14 +12880,14 @@ function resolveProjectsRoot(projectPath) {
|
|
|
12751
12880
|
}
|
|
12752
12881
|
try {
|
|
12753
12882
|
const supportPaths = resolveOpenScoutSupportPaths();
|
|
12754
|
-
if (!
|
|
12755
|
-
return
|
|
12883
|
+
if (!existsSync17(supportPaths.settingsPath)) {
|
|
12884
|
+
return dirname10(projectPath);
|
|
12756
12885
|
}
|
|
12757
|
-
const raw = JSON.parse(
|
|
12886
|
+
const raw = JSON.parse(readFileSync11(supportPaths.settingsPath, "utf8"));
|
|
12758
12887
|
const workspaceRoot = raw.discovery?.workspaceRoots?.find((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
12759
|
-
return workspaceRoot ? resolve6(workspaceRoot) :
|
|
12888
|
+
return workspaceRoot ? resolve6(workspaceRoot) : dirname10(projectPath);
|
|
12760
12889
|
} catch {
|
|
12761
|
-
return
|
|
12890
|
+
return dirname10(projectPath);
|
|
12762
12891
|
}
|
|
12763
12892
|
}
|
|
12764
12893
|
function resolveBrokerUrl() {
|
|
@@ -12768,7 +12897,7 @@ function nowSeconds2() {
|
|
|
12768
12897
|
return Math.floor(Date.now() / 1000);
|
|
12769
12898
|
}
|
|
12770
12899
|
function scoutCliPath() {
|
|
12771
|
-
return
|
|
12900
|
+
return join20(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs");
|
|
12772
12901
|
}
|
|
12773
12902
|
function legacyNodeBrokerRelayCommand() {
|
|
12774
12903
|
return `node ${JSON.stringify(scoutCliPath())}`;
|
|
@@ -12781,13 +12910,13 @@ function titleCaseLocalAgentName(value) {
|
|
|
12781
12910
|
}
|
|
12782
12911
|
function resolveScoutSkillPath() {
|
|
12783
12912
|
const candidatePaths = [
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12913
|
+
join20(OPENSCOUT_REPO_ROOT, ".agents", "skills", "scout", "SKILL.md"),
|
|
12914
|
+
join20(process.env.HOME ?? "", ".agents", "skills", "scout", "SKILL.md"),
|
|
12915
|
+
join20(OPENSCOUT_REPO_ROOT, ".agents", "skills", "relay-agent-comms", "SKILL.md"),
|
|
12916
|
+
join20(process.env.HOME ?? "", ".agents", "skills", "relay-agent-comms", "SKILL.md")
|
|
12788
12917
|
];
|
|
12789
12918
|
for (const path2 of candidatePaths) {
|
|
12790
|
-
if (
|
|
12919
|
+
if (existsSync17(path2)) {
|
|
12791
12920
|
return path2;
|
|
12792
12921
|
}
|
|
12793
12922
|
}
|
|
@@ -12895,6 +13024,74 @@ function buildLocalAgentSystemPromptTemplate() {
|
|
|
12895
13024
|
].join(`
|
|
12896
13025
|
`);
|
|
12897
13026
|
}
|
|
13027
|
+
function normalizeOperatorHandleSegment(value) {
|
|
13028
|
+
return normalizeAgentSelectorSegment(value?.trim().replace(/^@+/, "") ?? "") || "operator";
|
|
13029
|
+
}
|
|
13030
|
+
function resolveOperatorAugmentAgentName() {
|
|
13031
|
+
return `${normalizeOperatorHandleSegment(resolveOperatorHandle())}-ai`;
|
|
13032
|
+
}
|
|
13033
|
+
function operatorAugmentAgentNameCandidates() {
|
|
13034
|
+
return new Set([
|
|
13035
|
+
resolveOperatorAugmentAgentName(),
|
|
13036
|
+
"operator-ai"
|
|
13037
|
+
]);
|
|
13038
|
+
}
|
|
13039
|
+
function buildOperatorAugmentSystemPromptTemplate(input = {}) {
|
|
13040
|
+
const operatorHandle = normalizeOperatorHandleSegment(input.operatorHandle ?? resolveOperatorHandle());
|
|
13041
|
+
const augmentHandle = normalizeOperatorHandleSegment(input.augmentHandle ?? `${operatorHandle}-ai`);
|
|
13042
|
+
const humanLabel = `@${operatorHandle}`;
|
|
13043
|
+
const augmentLabel = `@${augmentHandle}`;
|
|
13044
|
+
return [
|
|
13045
|
+
"{{base_prompt}}",
|
|
13046
|
+
"",
|
|
13047
|
+
`You are the augmented counterpart to the human Scout operator ${humanLabel}.`,
|
|
13048
|
+
`${humanLabel} is the human. ${augmentLabel} is the AI-augmented looper with a human in the loop.`,
|
|
13049
|
+
`Do not impersonate ${humanLabel}. Speak and act as ${augmentLabel}, and involve ${humanLabel} only when human judgment or approval is the real next dependency.`,
|
|
13050
|
+
"",
|
|
13051
|
+
"Operating loop:",
|
|
13052
|
+
" - Keep long-running conversations in the same DM or invocation thread whenever possible.",
|
|
13053
|
+
" - Maintain continuity across turns: track the goal, decisions made, open questions, blockers, and promised follow-ups.",
|
|
13054
|
+
" - For efforts that span many turns, many files, or more than one working session, create or update a durable note/checkpoint when you have write access, then point to it briefly.",
|
|
13055
|
+
" - Prefer continuing from a concise recap over resetting context. If context is aging, summarize the useful state and keep moving.",
|
|
13056
|
+
` - Be explicit about the next responsible owner: ${augmentLabel}, ${humanLabel}, or another named agent.`,
|
|
13057
|
+
"",
|
|
13058
|
+
`When to invoke ${humanLabel}:`,
|
|
13059
|
+
" - Approval is needed for destructive, irreversible, public, financial, security-sensitive, credential, privacy, or cross-project priority decisions.",
|
|
13060
|
+
` - The task depends on taste, intent, product direction, personal context, or a choice only ${humanLabel} can make.`,
|
|
13061
|
+
" - You are blocked after a reasonable local attempt and the unblock request can be stated as a short concrete question.",
|
|
13062
|
+
" - A result is materially uncertain and acting without human input could waste meaningful time or create cleanup work.",
|
|
13063
|
+
" - You need permission to interrupt, wake, or redirect other people or agents outside the current work venue.",
|
|
13064
|
+
"",
|
|
13065
|
+
`How to invoke ${humanLabel}:`,
|
|
13066
|
+
" - Keep the ask concise: context, the decision needed, the default you recommend, and the consequence of no answer.",
|
|
13067
|
+
" - Use the same DM/thread when it exists. Do not broadcast human-loop requests.",
|
|
13068
|
+
` - If a reply is required before work can proceed, say that ${humanLabel} owns the next move and stop claiming progress.`,
|
|
13069
|
+
" - If work can continue safely, state the assumption and continue without waiting.",
|
|
13070
|
+
"",
|
|
13071
|
+
`Do not invoke ${humanLabel} for:`,
|
|
13072
|
+
" - Routine status updates, obvious implementation details, command outputs, local orientation, or reversible low-risk cleanup.",
|
|
13073
|
+
" - Ambiguity that you can resolve by reading the repo, checking Scout state, or asking the correct specialist agent.",
|
|
13074
|
+
"",
|
|
13075
|
+
"{{project_context}}",
|
|
13076
|
+
"",
|
|
13077
|
+
"{{collaboration_prompt}}",
|
|
13078
|
+
"",
|
|
13079
|
+
"{{protocol_prompt}}"
|
|
13080
|
+
].join(`
|
|
13081
|
+
`);
|
|
13082
|
+
}
|
|
13083
|
+
function operatorAugmentDefaultsForDefinitionId(definitionId) {
|
|
13084
|
+
const normalizedDefinitionId = normalizeAgentSelectorSegment(definitionId);
|
|
13085
|
+
if (!normalizedDefinitionId || !operatorAugmentAgentNameCandidates().has(normalizedDefinitionId)) {
|
|
13086
|
+
return null;
|
|
13087
|
+
}
|
|
13088
|
+
return {
|
|
13089
|
+
displayName: titleCaseLocalAgentName(normalizedDefinitionId),
|
|
13090
|
+
systemPrompt: buildOperatorAugmentSystemPromptTemplate({
|
|
13091
|
+
augmentHandle: normalizedDefinitionId
|
|
13092
|
+
})
|
|
13093
|
+
};
|
|
13094
|
+
}
|
|
12898
13095
|
function renderLocalAgentSystemPromptTemplate(template, context, options = {}) {
|
|
12899
13096
|
const basePrompt = buildLocalAgentBasePrompt(context);
|
|
12900
13097
|
const projectContext = buildLocalAgentProjectContextPrompt(context);
|
|
@@ -13049,13 +13246,20 @@ function expandHomePath4(value) {
|
|
|
13049
13246
|
return process.env.HOME ?? process.cwd();
|
|
13050
13247
|
}
|
|
13051
13248
|
if (value.startsWith("~/")) {
|
|
13052
|
-
return
|
|
13249
|
+
return join20(process.env.HOME ?? process.cwd(), value.slice(2));
|
|
13053
13250
|
}
|
|
13054
13251
|
return value;
|
|
13055
13252
|
}
|
|
13056
13253
|
function normalizeProjectPath(value) {
|
|
13057
13254
|
return resolve6(expandHomePath4(value.trim() || "."));
|
|
13058
13255
|
}
|
|
13256
|
+
function compactHomePath(value) {
|
|
13257
|
+
const home = process.env.HOME;
|
|
13258
|
+
if (!home) {
|
|
13259
|
+
return value;
|
|
13260
|
+
}
|
|
13261
|
+
return value.startsWith(home) ? value.replace(home, "~") : value;
|
|
13262
|
+
}
|
|
13059
13263
|
function normalizeTmuxSessionName2(value, agentId) {
|
|
13060
13264
|
const fallback = `relay-${agentId}`;
|
|
13061
13265
|
const trimmed = value?.trim() ?? "";
|
|
@@ -13309,6 +13513,21 @@ function applyRequestedRuntimeOptionsToLaunchArgs(harness, launchArgs, options)
|
|
|
13309
13513
|
...buildLaunchArgsForRequestedReasoningEffort(harness, requestedReasoningEffort)
|
|
13310
13514
|
];
|
|
13311
13515
|
}
|
|
13516
|
+
function setLaunchModelForHarness(harness, launchArgs, model) {
|
|
13517
|
+
const normalized = normalizeLaunchArgsForHarness(harness, launchArgs);
|
|
13518
|
+
if (model === undefined) {
|
|
13519
|
+
return normalized;
|
|
13520
|
+
}
|
|
13521
|
+
const stripped = stripLaunchModelForHarness(harness, normalized);
|
|
13522
|
+
const requestedModel = normalizeRequestedModel(model ?? undefined);
|
|
13523
|
+
if (!requestedModel) {
|
|
13524
|
+
return stripped;
|
|
13525
|
+
}
|
|
13526
|
+
return [
|
|
13527
|
+
...stripped,
|
|
13528
|
+
...buildLaunchArgsForRequestedModel(harness, requestedModel)
|
|
13529
|
+
];
|
|
13530
|
+
}
|
|
13312
13531
|
function defaultHarnessForOverride(override, fallback = "claude") {
|
|
13313
13532
|
return normalizeManagedHarness2(override.defaultHarness ?? override.runtime?.harness, fallback);
|
|
13314
13533
|
}
|
|
@@ -13429,6 +13648,31 @@ function recordForHarness(record, harnessOverride) {
|
|
|
13429
13648
|
permissionProfile: profile.permissionProfile
|
|
13430
13649
|
};
|
|
13431
13650
|
}
|
|
13651
|
+
function normalizeCardTimestamp(value) {
|
|
13652
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined;
|
|
13653
|
+
}
|
|
13654
|
+
function normalizeLocalAgentCardLifecycle(value, now = Date.now()) {
|
|
13655
|
+
if (!value) {
|
|
13656
|
+
return;
|
|
13657
|
+
}
|
|
13658
|
+
const kind = value.kind === "one_time" ? "one_time" : value.kind === "persistent" ? "persistent" : undefined;
|
|
13659
|
+
if (!kind) {
|
|
13660
|
+
return;
|
|
13661
|
+
}
|
|
13662
|
+
const createdAt = normalizeCardTimestamp(value.createdAt) ?? now;
|
|
13663
|
+
const expiresAt = normalizeCardTimestamp(value.expiresAt);
|
|
13664
|
+
const maxUses = typeof value.maxUses === "number" && Number.isFinite(value.maxUses) && value.maxUses > 0 ? Math.floor(value.maxUses) : undefined;
|
|
13665
|
+
const createdById = value.createdById?.trim();
|
|
13666
|
+
const inboxConversationId = value.inboxConversationId?.trim();
|
|
13667
|
+
return {
|
|
13668
|
+
kind,
|
|
13669
|
+
createdAt,
|
|
13670
|
+
...createdById ? { createdById } : {},
|
|
13671
|
+
...expiresAt ? { expiresAt } : {},
|
|
13672
|
+
...maxUses ? { maxUses } : {},
|
|
13673
|
+
...inboxConversationId ? { inboxConversationId } : {}
|
|
13674
|
+
};
|
|
13675
|
+
}
|
|
13432
13676
|
function normalizeLocalAgentRecord(agentId, record) {
|
|
13433
13677
|
const cwd = normalizeProjectPath(record.cwd || process.cwd());
|
|
13434
13678
|
const projectRoot = normalizeProjectPath(record.projectRoot || cwd);
|
|
@@ -13458,7 +13702,8 @@ function normalizeLocalAgentRecord(agentId, record) {
|
|
|
13458
13702
|
capabilities: normalizeLocalAgentCapabilities(record.capabilities),
|
|
13459
13703
|
launchArgs: activeProfile?.launchArgs ?? normalizeLaunchArgsForHarness(harness, record.launchArgs),
|
|
13460
13704
|
permissionProfile: activeProfile?.permissionProfile ?? record.permissionProfile,
|
|
13461
|
-
registrationSource: record.registrationSource
|
|
13705
|
+
registrationSource: record.registrationSource,
|
|
13706
|
+
card: normalizeLocalAgentCardLifecycle(record.card)
|
|
13462
13707
|
};
|
|
13463
13708
|
}
|
|
13464
13709
|
function localAgentStatusFromRecord(agentId, record, source) {
|
|
@@ -13512,7 +13757,8 @@ function localAgentRecordFromRelayAgentOverride(agentId, override) {
|
|
|
13512
13757
|
harnessProfiles: override.harnessProfiles,
|
|
13513
13758
|
transport: normalizeLocalAgentTransport(override.runtime?.transport, normalizeLocalAgentHarness(override.runtime?.harness)),
|
|
13514
13759
|
capabilities: override.capabilities,
|
|
13515
|
-
launchArgs: override.launchArgs
|
|
13760
|
+
launchArgs: override.launchArgs,
|
|
13761
|
+
card: override.card
|
|
13516
13762
|
});
|
|
13517
13763
|
}
|
|
13518
13764
|
function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
|
|
@@ -13533,6 +13779,7 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
|
|
|
13533
13779
|
capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
|
|
13534
13780
|
defaultHarness: normalizeManagedHarness2(normalizedRecord.defaultHarness, "claude"),
|
|
13535
13781
|
harnessProfiles: normalizedRecord.harnessProfiles,
|
|
13782
|
+
...normalizedRecord.card ? { card: normalizedRecord.card } : {},
|
|
13536
13783
|
runtime: {
|
|
13537
13784
|
cwd: normalizeProjectPath(normalizedRecord.cwd),
|
|
13538
13785
|
harness: normalizeLocalAgentHarness(normalizedRecord.harness),
|
|
@@ -13542,6 +13789,245 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
|
|
|
13542
13789
|
}
|
|
13543
13790
|
};
|
|
13544
13791
|
}
|
|
13792
|
+
function buildLocalAgentConfigState(agentId, record) {
|
|
13793
|
+
const harness = normalizeLocalAgentHarness(record.harness);
|
|
13794
|
+
const launchArgs = normalizeLaunchArgsForHarness(harness, record.launchArgs);
|
|
13795
|
+
return {
|
|
13796
|
+
agentId,
|
|
13797
|
+
editable: true,
|
|
13798
|
+
model: readLaunchModelForHarness(harness, launchArgs) ?? null,
|
|
13799
|
+
permissionProfile: record.permissionProfile ?? null,
|
|
13800
|
+
systemPrompt: record.systemPrompt || buildLocalAgentSystemPromptTemplate(),
|
|
13801
|
+
runtime: {
|
|
13802
|
+
cwd: compactHomePath(record.cwd),
|
|
13803
|
+
harness,
|
|
13804
|
+
transport: normalizeLocalAgentTransport(record.transport, harness),
|
|
13805
|
+
sessionId: record.tmuxSession,
|
|
13806
|
+
wakePolicy: "on_demand"
|
|
13807
|
+
},
|
|
13808
|
+
launchArgs,
|
|
13809
|
+
capabilities: normalizeLocalAgentCapabilities(record.capabilities),
|
|
13810
|
+
applyMode: "restart",
|
|
13811
|
+
templateHint: LOCAL_AGENT_SYSTEM_PROMPT_TEMPLATE_HINT
|
|
13812
|
+
};
|
|
13813
|
+
}
|
|
13814
|
+
async function assertDirectoryExists(directory) {
|
|
13815
|
+
const stats = await stat3(directory);
|
|
13816
|
+
if (!stats.isDirectory()) {
|
|
13817
|
+
throw new Error(`${directory} is not a directory.`);
|
|
13818
|
+
}
|
|
13819
|
+
}
|
|
13820
|
+
async function resolveConfiguredLocalAgentRecord(agentId) {
|
|
13821
|
+
const overrides = await readRelayAgentOverrides();
|
|
13822
|
+
const override = overrides[agentId];
|
|
13823
|
+
if (override) {
|
|
13824
|
+
return localAgentRecordFromRelayAgentOverride(agentId, override);
|
|
13825
|
+
}
|
|
13826
|
+
const registry = await readLocalAgentRegistry();
|
|
13827
|
+
const record = registry[agentId];
|
|
13828
|
+
return record ? normalizeLocalAgentRecord(agentId, record) : null;
|
|
13829
|
+
}
|
|
13830
|
+
async function getLocalAgentConfig(agentId) {
|
|
13831
|
+
const record = await resolveConfiguredLocalAgentRecord(agentId);
|
|
13832
|
+
if (!record) {
|
|
13833
|
+
return null;
|
|
13834
|
+
}
|
|
13835
|
+
return buildLocalAgentConfigState(agentId, record);
|
|
13836
|
+
}
|
|
13837
|
+
function defaultLocalAgentSessionId(agentId, harness) {
|
|
13838
|
+
return normalizeTmuxSessionName2(undefined, `${agentId}-${harness}`);
|
|
13839
|
+
}
|
|
13840
|
+
async function updateLocalAgentCard(agentId, input) {
|
|
13841
|
+
const existing = await getLocalAgentConfig(agentId);
|
|
13842
|
+
if (!existing) {
|
|
13843
|
+
return null;
|
|
13844
|
+
}
|
|
13845
|
+
const nextHarness = input.harness ? normalizeLocalAgentHarness(input.harness) : existing.runtime.harness;
|
|
13846
|
+
const harnessChanged = nextHarness !== existing.runtime.harness;
|
|
13847
|
+
const model = input.model === undefined ? harnessChanged ? null : undefined : input.model;
|
|
13848
|
+
return updateLocalAgentConfig(agentId, {
|
|
13849
|
+
runtime: {
|
|
13850
|
+
cwd: existing.runtime.cwd,
|
|
13851
|
+
harness: nextHarness,
|
|
13852
|
+
transport: harnessChanged ? normalizeLocalAgentTransport(undefined, nextHarness) : existing.runtime.transport,
|
|
13853
|
+
sessionId: harnessChanged ? defaultLocalAgentSessionId(agentId, nextHarness) : existing.runtime.sessionId
|
|
13854
|
+
},
|
|
13855
|
+
systemPrompt: existing.systemPrompt,
|
|
13856
|
+
launchArgs: harnessChanged ? [] : existing.launchArgs,
|
|
13857
|
+
model,
|
|
13858
|
+
reasoningEffort: input.reasoningEffort,
|
|
13859
|
+
permissionProfile: input.permissionProfile,
|
|
13860
|
+
capabilities: existing.capabilities
|
|
13861
|
+
});
|
|
13862
|
+
}
|
|
13863
|
+
async function updateLocalAgentConfig(agentId, input) {
|
|
13864
|
+
const [registry, overrides] = await Promise.all([
|
|
13865
|
+
readLocalAgentRegistry(),
|
|
13866
|
+
readRelayAgentOverrides()
|
|
13867
|
+
]);
|
|
13868
|
+
const record = registry[agentId] ?? (overrides[agentId] ? localAgentRecordFromRelayAgentOverride(agentId, overrides[agentId]) : undefined);
|
|
13869
|
+
if (!record) {
|
|
13870
|
+
return null;
|
|
13871
|
+
}
|
|
13872
|
+
const cwd = normalizeProjectPath(input.runtime.cwd || record.cwd);
|
|
13873
|
+
await assertDirectoryExists(cwd);
|
|
13874
|
+
const nextHarness = normalizeLocalAgentHarness(input.runtime.harness);
|
|
13875
|
+
const nextTransport = normalizeLocalAgentTransport(input.runtime.transport ?? record.transport, nextHarness);
|
|
13876
|
+
let nextLaunchArgs = setLaunchModelForHarness(nextHarness, input.launchArgs, input.model);
|
|
13877
|
+
if (input.reasoningEffort !== undefined) {
|
|
13878
|
+
const stripped = stripLaunchReasoningEffortForHarness(nextHarness, nextLaunchArgs);
|
|
13879
|
+
const requestedReasoningEffort = normalizeRequestedReasoningEffort(input.reasoningEffort ?? undefined);
|
|
13880
|
+
nextLaunchArgs = requestedReasoningEffort ? [
|
|
13881
|
+
...stripped,
|
|
13882
|
+
...buildLaunchArgsForRequestedReasoningEffort(nextHarness, requestedReasoningEffort)
|
|
13883
|
+
] : stripped;
|
|
13884
|
+
}
|
|
13885
|
+
const nextPermissionProfile = input.permissionProfile === undefined ? record.permissionProfile : input.permissionProfile === null ? undefined : parseScoutPermissionProfile(input.permissionProfile);
|
|
13886
|
+
const nextRecord = normalizeLocalAgentRecord(agentId, {
|
|
13887
|
+
...record,
|
|
13888
|
+
cwd,
|
|
13889
|
+
tmuxSession: input.runtime.sessionId,
|
|
13890
|
+
harness: nextHarness,
|
|
13891
|
+
defaultHarness: nextHarness,
|
|
13892
|
+
harnessProfiles: {
|
|
13893
|
+
...record.harnessProfiles ?? {},
|
|
13894
|
+
[normalizeManagedHarness2(input.runtime.harness, "claude")]: {
|
|
13895
|
+
cwd,
|
|
13896
|
+
transport: nextTransport,
|
|
13897
|
+
sessionId: normalizeTmuxSessionName2(input.runtime.sessionId, `${agentId}-${normalizeManagedHarness2(input.runtime.harness, "claude")}`),
|
|
13898
|
+
launchArgs: nextLaunchArgs,
|
|
13899
|
+
...nextPermissionProfile ? { permissionProfile: nextPermissionProfile } : {}
|
|
13900
|
+
}
|
|
13901
|
+
},
|
|
13902
|
+
transport: nextTransport,
|
|
13903
|
+
systemPrompt: input.systemPrompt.trim() || undefined,
|
|
13904
|
+
launchArgs: nextLaunchArgs,
|
|
13905
|
+
permissionProfile: nextPermissionProfile,
|
|
13906
|
+
capabilities: input.capabilities
|
|
13907
|
+
});
|
|
13908
|
+
registry[agentId] = nextRecord;
|
|
13909
|
+
await writeLocalAgentRegistry(registry);
|
|
13910
|
+
return buildLocalAgentConfigState(agentId, nextRecord);
|
|
13911
|
+
}
|
|
13912
|
+
async function updateLocalAgentCardLifecycle(agentId, input) {
|
|
13913
|
+
const overrides = await readRelayAgentOverrides();
|
|
13914
|
+
const override = overrides[agentId];
|
|
13915
|
+
if (!override) {
|
|
13916
|
+
return null;
|
|
13917
|
+
}
|
|
13918
|
+
const lifecycle2 = normalizeLocalAgentCardLifecycle({
|
|
13919
|
+
...override.card ?? {},
|
|
13920
|
+
...input
|
|
13921
|
+
});
|
|
13922
|
+
if (!lifecycle2) {
|
|
13923
|
+
return null;
|
|
13924
|
+
}
|
|
13925
|
+
overrides[agentId] = {
|
|
13926
|
+
...override,
|
|
13927
|
+
card: lifecycle2
|
|
13928
|
+
};
|
|
13929
|
+
await writeRelayAgentOverrides(overrides);
|
|
13930
|
+
return lifecycle2;
|
|
13931
|
+
}
|
|
13932
|
+
function oneTimeCardCreatedAt(lifecycle2) {
|
|
13933
|
+
return normalizeCardTimestamp(lifecycle2.createdAt) ?? 0;
|
|
13934
|
+
}
|
|
13935
|
+
function shouldPruneOneTimeCard(lifecycle2, now, maxAgeMs) {
|
|
13936
|
+
if (lifecycle2.kind !== "one_time") {
|
|
13937
|
+
return false;
|
|
13938
|
+
}
|
|
13939
|
+
if (lifecycle2.expiresAt !== undefined && lifecycle2.expiresAt <= now) {
|
|
13940
|
+
return true;
|
|
13941
|
+
}
|
|
13942
|
+
const createdAt = oneTimeCardCreatedAt(lifecycle2);
|
|
13943
|
+
return createdAt > 0 && createdAt + maxAgeMs <= now;
|
|
13944
|
+
}
|
|
13945
|
+
function oneTimeCardMatchesScope(agentId, override, input) {
|
|
13946
|
+
if (BUILT_IN_AGENT_DEFINITION_IDS.has(agentId)) {
|
|
13947
|
+
return false;
|
|
13948
|
+
}
|
|
13949
|
+
const lifecycle2 = normalizeLocalAgentCardLifecycle(override.card);
|
|
13950
|
+
if (lifecycle2?.kind !== "one_time") {
|
|
13951
|
+
return false;
|
|
13952
|
+
}
|
|
13953
|
+
if (input.createdById?.trim() && lifecycle2.createdById !== input.createdById.trim()) {
|
|
13954
|
+
return false;
|
|
13955
|
+
}
|
|
13956
|
+
if (input.projectRoot?.trim()) {
|
|
13957
|
+
const scopedRoot = normalizeProjectPath(input.projectRoot);
|
|
13958
|
+
const cardRoot = normalizeProjectPath(override.projectRoot || override.runtime?.cwd || ".");
|
|
13959
|
+
if (cardRoot !== scopedRoot) {
|
|
13960
|
+
return false;
|
|
13961
|
+
}
|
|
13962
|
+
}
|
|
13963
|
+
return true;
|
|
13964
|
+
}
|
|
13965
|
+
async function retireLocalAgentOverrides(overrides, agentIds) {
|
|
13966
|
+
const retired = [];
|
|
13967
|
+
for (const agentId of agentIds) {
|
|
13968
|
+
const override = overrides[agentId];
|
|
13969
|
+
if (!override) {
|
|
13970
|
+
continue;
|
|
13971
|
+
}
|
|
13972
|
+
const record = localAgentRecordFromRelayAgentOverride(agentId, override);
|
|
13973
|
+
const status = localAgentStatusFromRecord(agentId, record, localAgentStatusSource(agentId, overrides));
|
|
13974
|
+
await stopLocalAgent(agentId).catch(() => null);
|
|
13975
|
+
retired.push({
|
|
13976
|
+
...status,
|
|
13977
|
+
isOnline: false
|
|
13978
|
+
});
|
|
13979
|
+
delete overrides[agentId];
|
|
13980
|
+
}
|
|
13981
|
+
if (retired.length > 0) {
|
|
13982
|
+
await writeRelayAgentOverrides(overrides);
|
|
13983
|
+
}
|
|
13984
|
+
return retired;
|
|
13985
|
+
}
|
|
13986
|
+
async function pruneOneTimeLocalAgentCards(input = {}) {
|
|
13987
|
+
const now = input.now ?? Date.now();
|
|
13988
|
+
const maxAgeMs = Math.max(0, input.maxAgeMs ?? DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS);
|
|
13989
|
+
const maxCount = Math.max(0, input.maxCount ?? DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_RETAIN);
|
|
13990
|
+
const excluded = new Set(input.excludeAgentIds ?? []);
|
|
13991
|
+
const overrides = await readRelayAgentOverrides();
|
|
13992
|
+
const candidates = Object.entries(overrides).filter(([agentId, override]) => !excluded.has(agentId) && oneTimeCardMatchesScope(agentId, override, input)).map(([agentId, override]) => ({
|
|
13993
|
+
agentId,
|
|
13994
|
+
override,
|
|
13995
|
+
lifecycle: normalizeLocalAgentCardLifecycle(override.card)
|
|
13996
|
+
})).sort((left, right) => oneTimeCardCreatedAt(right.lifecycle) - oneTimeCardCreatedAt(left.lifecycle) || left.agentId.localeCompare(right.agentId));
|
|
13997
|
+
const retireIds = new Set;
|
|
13998
|
+
for (const candidate of candidates) {
|
|
13999
|
+
if (shouldPruneOneTimeCard(candidate.lifecycle, now, maxAgeMs)) {
|
|
14000
|
+
retireIds.add(candidate.agentId);
|
|
14001
|
+
}
|
|
14002
|
+
}
|
|
14003
|
+
const retained = candidates.filter((candidate) => !retireIds.has(candidate.agentId));
|
|
14004
|
+
for (const candidate of retained.slice(maxCount)) {
|
|
14005
|
+
retireIds.add(candidate.agentId);
|
|
14006
|
+
}
|
|
14007
|
+
const retired = await retireLocalAgentOverrides({ ...overrides }, retireIds);
|
|
14008
|
+
return {
|
|
14009
|
+
inspected: candidates.length,
|
|
14010
|
+
remaining: Math.max(0, candidates.length - retired.length),
|
|
14011
|
+
retired
|
|
14012
|
+
};
|
|
14013
|
+
}
|
|
14014
|
+
async function retireLocalAgent(agentId) {
|
|
14015
|
+
const overrides = await readRelayAgentOverrides();
|
|
14016
|
+
const override = overrides[agentId];
|
|
14017
|
+
if (!override) {
|
|
14018
|
+
return null;
|
|
14019
|
+
}
|
|
14020
|
+
const record = localAgentRecordFromRelayAgentOverride(agentId, override);
|
|
14021
|
+
const status = localAgentStatusFromRecord(agentId, record, localAgentStatusSource(agentId, overrides));
|
|
14022
|
+
await stopLocalAgent(agentId).catch(() => null);
|
|
14023
|
+
const nextOverrides = { ...overrides };
|
|
14024
|
+
delete nextOverrides[agentId];
|
|
14025
|
+
await writeRelayAgentOverrides(nextOverrides);
|
|
14026
|
+
return {
|
|
14027
|
+
...status,
|
|
14028
|
+
isOnline: false
|
|
14029
|
+
};
|
|
14030
|
+
}
|
|
13545
14031
|
function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
|
|
13546
14032
|
const permissionPosture = compileCodexPermissionProfile(record.permissionProfile);
|
|
13547
14033
|
return {
|
|
@@ -13592,6 +14078,90 @@ function buildLocalAgentSystemPrompt(agentName, projectName, projectPath, option
|
|
|
13592
14078
|
function buildLocalAgentInitialMessage(projectName, agentName) {
|
|
13593
14079
|
return `You are now online as the ${agentName} relay agent for ${projectName}. Announce yourself on the relay with: ${brokerRelayCommand()} send --as ${agentName} "relay agent online \u2014 ready to assist with ${projectName}"`;
|
|
13594
14080
|
}
|
|
14081
|
+
function tmuxDispatchSleep(ms) {
|
|
14082
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
14083
|
+
}
|
|
14084
|
+
function captureTmuxPaneTail(sessionName, lines) {
|
|
14085
|
+
try {
|
|
14086
|
+
return execFileSync4("tmux", ["capture-pane", "-p", "-t", sessionName, "-S", `-${lines}`, "-E", "-"], { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
|
|
14087
|
+
} catch {
|
|
14088
|
+
return "";
|
|
14089
|
+
}
|
|
14090
|
+
}
|
|
14091
|
+
async function waitForTmuxHarnessReady(sessionName, harness) {
|
|
14092
|
+
if (harness !== "claude") {
|
|
14093
|
+
return;
|
|
14094
|
+
}
|
|
14095
|
+
const deadline = Date.now() + TMUX_READY_TIMEOUT_MS;
|
|
14096
|
+
let paneTail = "";
|
|
14097
|
+
while (Date.now() < deadline) {
|
|
14098
|
+
if (!isLocalAgentSessionAlive(sessionName)) {
|
|
14099
|
+
throw new Error(`tmux session ${sessionName} exited before Claude Code was ready.`);
|
|
14100
|
+
}
|
|
14101
|
+
paneTail = captureTmuxPaneTail(sessionName, TMUX_READY_TAIL_LINES);
|
|
14102
|
+
if (tmuxPaneTailShowsReadyComposer(paneTail)) {
|
|
14103
|
+
return;
|
|
14104
|
+
}
|
|
14105
|
+
await tmuxDispatchSleep(TMUX_READY_POLL_MS);
|
|
14106
|
+
}
|
|
14107
|
+
const tail = stripTerminalControlSequences(paneTail).trim().split(/\r?\n/).slice(-20).join(`
|
|
14108
|
+
`).trim();
|
|
14109
|
+
throw new Error(`tmux session ${sessionName} did not show a ready Claude Code composer within ${TMUX_READY_TIMEOUT_MS}ms.` + (tail ? `
|
|
14110
|
+
Recent pane tail:
|
|
14111
|
+
${tail}` : ""));
|
|
14112
|
+
}
|
|
14113
|
+
function tmuxPaneTailShowsReadyComposer(paneTail) {
|
|
14114
|
+
const cleanedTail = stripTerminalControlSequences(paneTail);
|
|
14115
|
+
const lines = cleanedTail.split(/\r?\n/);
|
|
14116
|
+
const anchor = findActiveTmuxComposerAnchor(lines);
|
|
14117
|
+
if (!anchor) {
|
|
14118
|
+
return false;
|
|
14119
|
+
}
|
|
14120
|
+
const afterComposerLines = [];
|
|
14121
|
+
for (const line of lines.slice(anchor.index + 1)) {
|
|
14122
|
+
if (isTmuxComposerBoundary(line)) {
|
|
14123
|
+
break;
|
|
14124
|
+
}
|
|
14125
|
+
afterComposerLines.push(line);
|
|
14126
|
+
}
|
|
14127
|
+
return !tmuxPaneTailShowsHarnessActivity(afterComposerLines.join(`
|
|
14128
|
+
`));
|
|
14129
|
+
}
|
|
14130
|
+
function stripTerminalControlSequences(value) {
|
|
14131
|
+
return value.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
14132
|
+
}
|
|
14133
|
+
function findActiveTmuxComposerAnchor(lines) {
|
|
14134
|
+
const inlineComposerIndex = findLastIndex(lines, (line) => /^\s*[\u276F\u203A]\s*/.test(line));
|
|
14135
|
+
const boxedComposerIndex = findLastIndex(lines, (line) => /^\s*[\u2502\u2503]\s*[>\u276F]\s*/.test(line));
|
|
14136
|
+
if (inlineComposerIndex < 0 && boxedComposerIndex < 0) {
|
|
14137
|
+
return null;
|
|
14138
|
+
}
|
|
14139
|
+
if (inlineComposerIndex > boxedComposerIndex) {
|
|
14140
|
+
return { index: inlineComposerIndex, kind: "inline" };
|
|
14141
|
+
}
|
|
14142
|
+
return { index: boxedComposerIndex, kind: "boxed" };
|
|
14143
|
+
}
|
|
14144
|
+
function findLastIndex(items, predicate) {
|
|
14145
|
+
for (let index = items.length - 1;index >= 0; index -= 1) {
|
|
14146
|
+
if (predicate(items[index])) {
|
|
14147
|
+
return index;
|
|
14148
|
+
}
|
|
14149
|
+
}
|
|
14150
|
+
return -1;
|
|
14151
|
+
}
|
|
14152
|
+
function isTmuxComposerBoundary(line) {
|
|
14153
|
+
const trimmed = line.trim();
|
|
14154
|
+
if (!trimmed) {
|
|
14155
|
+
return false;
|
|
14156
|
+
}
|
|
14157
|
+
if (/^[\u2500\u2501\u2550\u256D\u256E\u2570\u256F\u250C\u2510\u2514\u2518\u2554\u2557\u255A\u255D\u255F\u2562\u2560\u2563\u256A\u256B\u256C\u2569\u2566\u2564\u2567\u254C\u254D\u254E\u254F\s]+$/.test(trimmed)) {
|
|
14158
|
+
return true;
|
|
14159
|
+
}
|
|
14160
|
+
return /^--\s*(?:INSERT|NORMAL)\s*--/.test(trimmed) || /^(?:Opus|Sonnet|Haiku|Claude|Codex|GPT)\b/.test(trimmed);
|
|
14161
|
+
}
|
|
14162
|
+
function tmuxPaneTailShowsHarnessActivity(paneTail) {
|
|
14163
|
+
return /(?:^|\n)\s*(?:[\u23FA\u25CF\u273D\u2722\u273B\u23BF]|Bash\(|Read\(|Edit\(|Write\(|Grep\(|Glob\(|TodoWrite\()/.test(paneTail);
|
|
14164
|
+
}
|
|
13595
14165
|
function shellQuoteArguments(args) {
|
|
13596
14166
|
return args.map((arg) => JSON.stringify(arg)).join(" ");
|
|
13597
14167
|
}
|
|
@@ -13602,10 +14172,10 @@ function buildLocalAgentLaunchCommand(agentName, record, projectPath, promptFile
|
|
|
13602
14172
|
const harness = normalizeLocalAgentHarness(record.harness);
|
|
13603
14173
|
const extraArgs = shellQuoteArguments(harness === "claude" ? normalizeClaudeRuntimeLaunchArgs(record.launchArgs) : normalizeLaunchArgsForHarness(harness, record.launchArgs));
|
|
13604
14174
|
if (harness === "codex") {
|
|
13605
|
-
return `exec bash ${JSON.stringify(workerScript ??
|
|
14175
|
+
return `exec bash ${JSON.stringify(workerScript ?? join20(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
|
|
13606
14176
|
}
|
|
13607
14177
|
if (harness === "pi") {
|
|
13608
|
-
const sessionDir =
|
|
14178
|
+
const sessionDir = join20(relayAgentRuntimeDirectory(agentName), "pi-sessions");
|
|
13609
14179
|
return [
|
|
13610
14180
|
"pi",
|
|
13611
14181
|
`--append-system-prompt "$(cat ${JSON.stringify(promptFile)})"`,
|
|
@@ -13675,7 +14245,7 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
13675
14245
|
await mkdir6(agentRuntimeDir, { recursive: true });
|
|
13676
14246
|
await mkdir6(logsDir, { recursive: true });
|
|
13677
14247
|
if (normalizedRecord.transport === "codex_app_server") {
|
|
13678
|
-
await writeFile6(
|
|
14248
|
+
await writeFile6(join20(agentRuntimeDir, "prompt.txt"), systemPrompt);
|
|
13679
14249
|
await ensureCodexAppServerAgentOnline(buildCodexAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
|
|
13680
14250
|
const registry2 = await readLocalAgentRegistry();
|
|
13681
14251
|
registry2[agentName] = {
|
|
@@ -13687,7 +14257,7 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
13687
14257
|
return registry2[agentName];
|
|
13688
14258
|
}
|
|
13689
14259
|
if (normalizedRecord.transport === "claude_stream_json") {
|
|
13690
|
-
await writeFile6(
|
|
14260
|
+
await writeFile6(join20(agentRuntimeDir, "prompt.txt"), systemPrompt);
|
|
13691
14261
|
await ensureClaudeStreamJsonAgentOnline(buildClaudeAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
|
|
13692
14262
|
const registry2 = await readLocalAgentRegistry();
|
|
13693
14263
|
registry2[agentName] = {
|
|
@@ -13700,17 +14270,17 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
13700
14270
|
}
|
|
13701
14271
|
const initialMessage = buildLocalAgentInitialMessage(projectName, agentName);
|
|
13702
14272
|
const bootstrapPrompt = buildLocalAgentBootstrapPrompt(normalizeLocalAgentHarness(normalizedRecord.harness), systemPrompt, initialMessage);
|
|
13703
|
-
const queueDirectory =
|
|
13704
|
-
const processingDirectory =
|
|
13705
|
-
const processedDirectory =
|
|
13706
|
-
const promptFile =
|
|
13707
|
-
const initialFile =
|
|
13708
|
-
const launchScript =
|
|
13709
|
-
const workerScript =
|
|
13710
|
-
const codexSessionIdFile =
|
|
13711
|
-
const stateFile =
|
|
13712
|
-
const stdoutLogFile =
|
|
13713
|
-
const stderrLogFile =
|
|
14273
|
+
const queueDirectory = join20(agentRuntimeDir, "queue");
|
|
14274
|
+
const processingDirectory = join20(queueDirectory, "processing");
|
|
14275
|
+
const processedDirectory = join20(queueDirectory, "processed");
|
|
14276
|
+
const promptFile = join20(agentRuntimeDir, "prompt.txt");
|
|
14277
|
+
const initialFile = join20(agentRuntimeDir, "initial.txt");
|
|
14278
|
+
const launchScript = join20(agentRuntimeDir, "launch.sh");
|
|
14279
|
+
const workerScript = join20(agentRuntimeDir, "codex-worker.sh");
|
|
14280
|
+
const codexSessionIdFile = join20(agentRuntimeDir, "codex-session-id.txt");
|
|
14281
|
+
const stateFile = join20(agentRuntimeDir, "state.json");
|
|
14282
|
+
const stdoutLogFile = join20(logsDir, "stdout.log");
|
|
14283
|
+
const stderrLogFile = join20(logsDir, "stderr.log");
|
|
13714
14284
|
await writeFile6(promptFile, systemPrompt);
|
|
13715
14285
|
await writeFile6(initialFile, bootstrapPrompt);
|
|
13716
14286
|
await writeFile6(stderrLogFile, "");
|
|
@@ -13775,11 +14345,17 @@ async function ensureLocalAgentOnline(agentName, record) {
|
|
|
13775
14345
|
await new Promise((resolve7) => setTimeout(resolve7, 100));
|
|
13776
14346
|
}
|
|
13777
14347
|
if (!isLocalAgentSessionAlive(normalizedRecord.tmuxSession)) {
|
|
13778
|
-
const stderrTail =
|
|
14348
|
+
const stderrTail = existsSync17(stderrLogFile) ? readFileSync11(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
|
|
13779
14349
|
`).trim() : "";
|
|
13780
14350
|
throw new Error(stderrTail ? `Relay agent ${agentName} failed to stay online:
|
|
13781
14351
|
${stderrTail}` : `Relay agent ${agentName} failed to stay online.`);
|
|
13782
14352
|
}
|
|
14353
|
+
try {
|
|
14354
|
+
await waitForTmuxHarnessReady(normalizedRecord.tmuxSession, normalizeLocalAgentHarness(normalizedRecord.harness));
|
|
14355
|
+
} catch (error) {
|
|
14356
|
+
killAgentSession(normalizedRecord.tmuxSession);
|
|
14357
|
+
throw error;
|
|
14358
|
+
}
|
|
13783
14359
|
const registry = await readLocalAgentRegistry();
|
|
13784
14360
|
registry[agentName] = {
|
|
13785
14361
|
...normalizedRecord,
|
|
@@ -13836,6 +14412,7 @@ async function startLocalAgent(input) {
|
|
|
13836
14412
|
const effectiveCwd = input.cwdOverride ? normalizeProjectPath(input.cwdOverride) : undefined;
|
|
13837
14413
|
const shouldEnsureOnline = input.ensureOnline !== false;
|
|
13838
14414
|
const requestedPermissionProfile = parseScoutPermissionProfile(input.permissionProfile);
|
|
14415
|
+
const cardLifecycle = normalizeLocalAgentCardLifecycle(input.card);
|
|
13839
14416
|
const requestedDefinitionId = input.agentName?.trim() ? normalizeAgentSelectorSegment(input.agentName.trim()) : "";
|
|
13840
14417
|
if (input.agentName?.trim() && !requestedDefinitionId) {
|
|
13841
14418
|
throw new Error(`Invalid agent name "${input.agentName}".`);
|
|
@@ -13885,7 +14462,8 @@ async function startLocalAgent(input) {
|
|
|
13885
14462
|
const configDefinitionId = coldProjectConfig?.agent?.id?.trim() ? normalizeAgentSelectorSegment(coldProjectConfig.agent.id.trim()) : "";
|
|
13886
14463
|
const definitionId = requestedDefinitionId || configDefinitionId || normalizeAgentSelectorSegment(basename8(projectRoot)) || "agent";
|
|
13887
14464
|
const configDisplayName = coldProjectConfig?.agent?.displayName?.trim() || "";
|
|
13888
|
-
const
|
|
14465
|
+
const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(definitionId);
|
|
14466
|
+
const effectiveDisplayName = input.displayName || operatorAugmentDefaults?.displayName || configDisplayName || titleCaseLocalAgentName(definitionId);
|
|
13889
14467
|
const instance = buildRelayAgentInstance(definitionId, projectRoot);
|
|
13890
14468
|
const configDefaultHarness = coldProjectConfig?.agent?.runtime?.defaultHarness;
|
|
13891
14469
|
const effectiveHarness = normalizeManagedHarness2(preferredHarness ?? configDefaultHarness, "claude");
|
|
@@ -13908,8 +14486,10 @@ async function startLocalAgent(input) {
|
|
|
13908
14486
|
projectConfigPath: coldProjectConfigPath,
|
|
13909
14487
|
source: "manual",
|
|
13910
14488
|
startedAt: nowSeconds2(),
|
|
14489
|
+
...operatorAugmentDefaults ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
|
|
13911
14490
|
launchArgs,
|
|
13912
14491
|
defaultHarness: effectiveHarness,
|
|
14492
|
+
...cardLifecycle ? { card: cardLifecycle } : {},
|
|
13913
14493
|
harnessProfiles: {
|
|
13914
14494
|
[effectiveHarness]: {
|
|
13915
14495
|
cwd: effectiveCwd ?? projectRoot,
|
|
@@ -13945,18 +14525,21 @@ async function startLocalAgent(input) {
|
|
|
13945
14525
|
reasoningEffort: input.reasoningEffort
|
|
13946
14526
|
});
|
|
13947
14527
|
const nextTransport = normalizeLocalAgentTransport(preferredHarness ? undefined : matchingOverride.runtime?.transport, nextHarness);
|
|
14528
|
+
const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(requestedDefinitionId);
|
|
13948
14529
|
overrides[instance.id] = {
|
|
13949
14530
|
agentId: instance.id,
|
|
13950
14531
|
definitionId: requestedDefinitionId,
|
|
13951
|
-
displayName: input.displayName || titleCaseLocalAgentName(requestedDefinitionId),
|
|
14532
|
+
displayName: input.displayName || operatorAugmentDefaults?.displayName || titleCaseLocalAgentName(requestedDefinitionId),
|
|
13952
14533
|
projectName: matchingOverride.projectName ?? basename8(matchingProjectRoot),
|
|
13953
14534
|
projectRoot: matchingProjectRoot,
|
|
13954
14535
|
projectConfigPath: null,
|
|
13955
14536
|
source: "manual",
|
|
13956
14537
|
startedAt: matchingOverride.startedAt ?? nowSeconds2(),
|
|
14538
|
+
...operatorAugmentDefaults ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
|
|
13957
14539
|
launchArgs: nextHarness === nextDefaultHarness ? nextLaunchArgs : matchingOverride.launchArgs,
|
|
13958
14540
|
capabilities: matchingOverride.capabilities,
|
|
13959
14541
|
defaultHarness: nextDefaultHarness,
|
|
14542
|
+
...cardLifecycle ? { card: cardLifecycle } : {},
|
|
13960
14543
|
harnessProfiles: {
|
|
13961
14544
|
...matchingOverride.harnessProfiles ?? {},
|
|
13962
14545
|
[nextHarness]: {
|
|
@@ -13978,7 +14561,9 @@ async function startLocalAgent(input) {
|
|
|
13978
14561
|
await writeRelayAgentOverrides(overrides);
|
|
13979
14562
|
targetAgentId = instance.id;
|
|
13980
14563
|
} else {
|
|
13981
|
-
|
|
14564
|
+
const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(matchingOverride.definitionId ?? requestedDefinitionId);
|
|
14565
|
+
const shouldApplyOperatorAugmentPrompt = Boolean(operatorAugmentDefaults && !matchingOverride.systemPrompt?.trim());
|
|
14566
|
+
if (input.model || input.reasoningEffort || requestedPermissionProfile || cardLifecycle || shouldApplyOperatorAugmentPrompt) {
|
|
13982
14567
|
const existingHarness = normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness ?? matchingOverride.runtime?.harness, "claude");
|
|
13983
14568
|
const nextLaunchArgs = applyRequestedRuntimeOptionsToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), {
|
|
13984
14569
|
model: input.model,
|
|
@@ -13986,6 +14571,8 @@ async function startLocalAgent(input) {
|
|
|
13986
14571
|
});
|
|
13987
14572
|
overrides[matchingAgentId] = {
|
|
13988
14573
|
...matchingOverride,
|
|
14574
|
+
...cardLifecycle ? { card: cardLifecycle } : {},
|
|
14575
|
+
...shouldApplyOperatorAugmentPrompt ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
|
|
13989
14576
|
launchArgs: existingHarness === defaultHarnessForOverride(matchingOverride, existingHarness) ? nextLaunchArgs : matchingOverride.launchArgs,
|
|
13990
14577
|
harnessProfiles: {
|
|
13991
14578
|
...matchingOverride.harnessProfiles ?? {},
|
|
@@ -14090,9 +14677,9 @@ async function restartAllLocalAgents(options = {}) {
|
|
|
14090
14677
|
function readPersistedClaudeSessionId(agentName) {
|
|
14091
14678
|
try {
|
|
14092
14679
|
const runtimeDir = relayAgentRuntimeDirectory(agentName);
|
|
14093
|
-
const catalogPath =
|
|
14094
|
-
if (
|
|
14095
|
-
const raw =
|
|
14680
|
+
const catalogPath = join20(runtimeDir, "session-catalog.json");
|
|
14681
|
+
if (existsSync17(catalogPath)) {
|
|
14682
|
+
const raw = readFileSync11(catalogPath, "utf8").trim();
|
|
14096
14683
|
if (raw) {
|
|
14097
14684
|
const catalog = JSON.parse(raw);
|
|
14098
14685
|
if (typeof catalog.activeSessionId === "string" && catalog.activeSessionId.trim()) {
|
|
@@ -14100,9 +14687,9 @@ function readPersistedClaudeSessionId(agentName) {
|
|
|
14100
14687
|
}
|
|
14101
14688
|
}
|
|
14102
14689
|
}
|
|
14103
|
-
const legacyPath =
|
|
14104
|
-
if (
|
|
14105
|
-
const value =
|
|
14690
|
+
const legacyPath = join20(runtimeDir, "claude-session-id.txt");
|
|
14691
|
+
if (existsSync17(legacyPath)) {
|
|
14692
|
+
const value = readFileSync11(legacyPath, "utf8").trim();
|
|
14106
14693
|
return value || null;
|
|
14107
14694
|
}
|
|
14108
14695
|
return null;
|
|
@@ -14114,6 +14701,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
|
|
|
14114
14701
|
const normalizedRecord = normalizeLocalAgentRecord(agentId, record);
|
|
14115
14702
|
const configuredModel = readLaunchModelForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs);
|
|
14116
14703
|
const codexPermissionPosture = normalizeLocalAgentHarness(normalizedRecord.harness) === "codex" ? compileCodexPermissionProfile(normalizedRecord.permissionProfile) : null;
|
|
14704
|
+
const cardLifecycle = normalizeLocalAgentCardLifecycle(normalizedRecord.card);
|
|
14117
14705
|
const definitionId = normalizedRecord.definitionId ?? agentId;
|
|
14118
14706
|
const displayName = titleCaseLocalAgentName(definitionId);
|
|
14119
14707
|
const projectRoot = normalizedRecord.projectRoot ?? normalizedRecord.cwd;
|
|
@@ -14141,6 +14729,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
|
|
|
14141
14729
|
workspaceQualifier: instance.workspaceQualifier,
|
|
14142
14730
|
branch: instance.branch,
|
|
14143
14731
|
...configuredModel ? { model: configuredModel } : {},
|
|
14732
|
+
...cardLifecycle ? { cardLifecycle } : {},
|
|
14144
14733
|
...codexPermissionPosture ? {
|
|
14145
14734
|
permissionProfile: codexPermissionPosture.profile,
|
|
14146
14735
|
approvalPolicy: codexPermissionPosture.approvalPolicy,
|
|
@@ -14176,6 +14765,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
|
|
|
14176
14765
|
workspaceQualifier: instance.workspaceQualifier,
|
|
14177
14766
|
branch: instance.branch,
|
|
14178
14767
|
...configuredModel ? { model: configuredModel } : {},
|
|
14768
|
+
...cardLifecycle ? { cardLifecycle } : {},
|
|
14179
14769
|
...codexPermissionPosture ? {
|
|
14180
14770
|
permissionProfile: codexPermissionPosture.profile,
|
|
14181
14771
|
approvalPolicy: codexPermissionPosture.approvalPolicy,
|
|
@@ -14216,6 +14806,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
|
|
|
14216
14806
|
workspaceQualifier: instance.workspaceQualifier,
|
|
14217
14807
|
branch: instance.branch,
|
|
14218
14808
|
...configuredModel ? { model: configuredModel } : {},
|
|
14809
|
+
...cardLifecycle ? { cardLifecycle } : {},
|
|
14219
14810
|
...codexPermissionPosture ? {
|
|
14220
14811
|
permissionProfile: codexPermissionPosture.profile,
|
|
14221
14812
|
approvalPolicy: codexPermissionPosture.approvalPolicy,
|
|
@@ -14302,9 +14893,10 @@ async function ensureLocalAgentBindingOnline(agentId, nodeId, options = {}) {
|
|
|
14302
14893
|
const onlineRecord = await ensureLocalAgentOnline(agentId, recordForHarness(localAgentRecordFromRelayAgentOverride(agentId, override), options.harness));
|
|
14303
14894
|
return buildLocalAgentBinding(agentId, onlineRecord, isLocalAgentRecordOnline(agentId, onlineRecord), nodeId, "relay-agent-registry");
|
|
14304
14895
|
}
|
|
14305
|
-
var MODULE_DIRECTORY, OPENSCOUT_REPO_ROOT, DEFAULT_LOCAL_AGENT_CAPABILITIES, DEFAULT_LOCAL_AGENT_HARNESS = "claude", SUPPORTED_LOCAL_AGENT_HARNESSES, SUPPORTED_SCOUT_HARNESSES, DEFAULT_CLAUDE_SCOUT_ALLOWED_TOOLS;
|
|
14896
|
+
var MODULE_DIRECTORY, OPENSCOUT_REPO_ROOT, DEFAULT_LOCAL_AGENT_CAPABILITIES, DEFAULT_LOCAL_AGENT_HARNESS = "claude", DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS, DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_RETAIN = 24, SUPPORTED_LOCAL_AGENT_HARNESSES, SUPPORTED_SCOUT_HARNESSES, DEFAULT_CLAUDE_SCOUT_ALLOWED_TOOLS, TMUX_READY_TIMEOUT_MS = 20000, TMUX_READY_POLL_MS = 250, TMUX_READY_TAIL_LINES = 80;
|
|
14306
14897
|
var init_local_agents = __esm(() => {
|
|
14307
14898
|
init_src2();
|
|
14899
|
+
init_dispatch_stalled();
|
|
14308
14900
|
init_claude_stream_json();
|
|
14309
14901
|
init_codex_app_server();
|
|
14310
14902
|
init_setup();
|
|
@@ -14314,9 +14906,11 @@ var init_local_agents = __esm(() => {
|
|
|
14314
14906
|
init_local_agent_template();
|
|
14315
14907
|
init_permission_policy2();
|
|
14316
14908
|
init_requester_timeout();
|
|
14317
|
-
|
|
14909
|
+
init_user_config();
|
|
14910
|
+
MODULE_DIRECTORY = dirname10(fileURLToPath5(import.meta.url));
|
|
14318
14911
|
OPENSCOUT_REPO_ROOT = resolve6(MODULE_DIRECTORY, "..", "..", "..");
|
|
14319
14912
|
DEFAULT_LOCAL_AGENT_CAPABILITIES = ["chat", "invoke", "deliver"];
|
|
14913
|
+
DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
14320
14914
|
SUPPORTED_LOCAL_AGENT_HARNESSES = ["claude", "codex", "pi"];
|
|
14321
14915
|
SUPPORTED_SCOUT_HARNESSES = [
|
|
14322
14916
|
...SUPPORTED_LOCAL_AGENT_HARNESSES,
|
|
@@ -14331,8 +14925,8 @@ var init_local_agents = __esm(() => {
|
|
|
14331
14925
|
"mcp__scout__agents_search",
|
|
14332
14926
|
"mcp__scout__agents_resolve",
|
|
14333
14927
|
"mcp__scout__messages_reply",
|
|
14928
|
+
"mcp__scout__ask",
|
|
14334
14929
|
"mcp__scout__messages_send",
|
|
14335
|
-
"mcp__scout__invocations_ask",
|
|
14336
14930
|
"mcp__scout__invocations_get",
|
|
14337
14931
|
"mcp__scout__invocations_wait",
|
|
14338
14932
|
"mcp__scout__work_update",
|
|
@@ -17829,7 +18423,7 @@ function writeJsonAtomically(filePath, value) {
|
|
|
17829
18423
|
|
|
17830
18424
|
// ../web/server/core/pairing/runtime/runtime.ts
|
|
17831
18425
|
init_src();
|
|
17832
|
-
import { homedir as
|
|
18426
|
+
import { homedir as homedir25 } from "os";
|
|
17833
18427
|
|
|
17834
18428
|
// ../web/server/core/pairing/runtime/bridge/bridge.ts
|
|
17835
18429
|
init_src();
|
|
@@ -18643,18 +19237,21 @@ function formatTimestamp(value) {
|
|
|
18643
19237
|
|
|
18644
19238
|
// ../web/server/core/pairing/runtime/bridge/server.ts
|
|
18645
19239
|
init_src();
|
|
18646
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
19240
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync13, realpathSync, statSync as statSync5 } from "fs";
|
|
18647
19241
|
import { execSync as execSync3 } from "child_process";
|
|
18648
|
-
import { basename as
|
|
18649
|
-
import { homedir as
|
|
19242
|
+
import { basename as basename11, isAbsolute as isAbsolute3, join as join25, relative as relative3 } from "path";
|
|
19243
|
+
import { homedir as homedir22 } from "os";
|
|
18650
19244
|
|
|
18651
19245
|
// ../web/server/core/mobile/service.ts
|
|
18652
19246
|
init_harness_catalog();
|
|
18653
19247
|
init_setup();
|
|
18654
|
-
import { basename as
|
|
19248
|
+
import { basename as basename10, resolve as resolve7 } from "path";
|
|
18655
19249
|
|
|
18656
19250
|
// ../runtime/src/control-plane-agents.ts
|
|
19251
|
+
init_src2();
|
|
18657
19252
|
init_local_agents();
|
|
19253
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19254
|
+
import { basename as basename9 } from "path";
|
|
18658
19255
|
|
|
18659
19256
|
// ../runtime/src/scout-agent-cards.ts
|
|
18660
19257
|
init_src2();
|
|
@@ -18707,6 +19304,7 @@ function buildScoutAgentCard(binding, options = {}) {
|
|
|
18707
19304
|
const supportedInterfaces = metadataRecordArray(binding.agent.metadata, "supportedInterfaces");
|
|
18708
19305
|
const securitySchemes = metadataRecord2(binding.agent.metadata, "securitySchemes");
|
|
18709
19306
|
const securityRequirements = metadataStringMatrix(binding.agent.metadata, "securityRequirements");
|
|
19307
|
+
const lifecycle2 = options.lifecycle ?? metadataRecord2(binding.agent.metadata, "cardLifecycle");
|
|
18710
19308
|
return {
|
|
18711
19309
|
id: binding.agent.id,
|
|
18712
19310
|
agentId: binding.agent.id,
|
|
@@ -18736,6 +19334,7 @@ function buildScoutAgentCard(binding, options = {}) {
|
|
|
18736
19334
|
...options.createdById?.trim() ? { createdById: options.createdById.trim() } : {},
|
|
18737
19335
|
brokerRegistered: options.brokerRegistered ?? false,
|
|
18738
19336
|
...options.inboxConversationId?.trim() ? { inboxConversationId: options.inboxConversationId.trim() } : {},
|
|
19337
|
+
...lifecycle2 ? { lifecycle: lifecycle2 } : {},
|
|
18739
19338
|
returnAddress: buildScoutReturnAddress({
|
|
18740
19339
|
actorId: binding.agent.id,
|
|
18741
19340
|
handle,
|
|
@@ -18753,19 +19352,30 @@ function buildScoutAgentCard(binding, options = {}) {
|
|
|
18753
19352
|
actorId: binding.actor.id,
|
|
18754
19353
|
endpointId: binding.endpoint.id,
|
|
18755
19354
|
wakePolicy: binding.agent.wakePolicy,
|
|
18756
|
-
...model ? { model } : {}
|
|
19355
|
+
...model ? { model } : {},
|
|
19356
|
+
...lifecycle2 ? { cardLifecycle: lifecycle2 } : {}
|
|
18757
19357
|
}
|
|
18758
19358
|
};
|
|
18759
19359
|
}
|
|
18760
19360
|
|
|
18761
19361
|
// ../runtime/src/control-plane-agents.ts
|
|
19362
|
+
var DEFAULT_ONE_TIME_SCOUT_AGENT_CARD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
19363
|
+
function oneTimeAgentName(input) {
|
|
19364
|
+
const base = normalizeAgentSelectorSegment(input.agentName || basename9(input.projectPath)) || "agent";
|
|
19365
|
+
return `${base}-card-${randomUUID2().slice(0, 8)}`;
|
|
19366
|
+
}
|
|
18762
19367
|
function createScoutAgentService(deps) {
|
|
18763
19368
|
const localAgents = {
|
|
18764
19369
|
listLocalAgents: deps.localAgents?.listLocalAgents ?? listLocalAgents,
|
|
19370
|
+
retireLocalAgent: deps.localAgents?.retireLocalAgent ?? retireLocalAgent,
|
|
18765
19371
|
restartAllLocalAgents: deps.localAgents?.restartAllLocalAgents ?? restartAllLocalAgents,
|
|
19372
|
+
restartLocalAgent: deps.localAgents?.restartLocalAgent ?? restartLocalAgent,
|
|
18766
19373
|
startLocalAgent: deps.localAgents?.startLocalAgent ?? startLocalAgent,
|
|
18767
19374
|
stopAllLocalAgents: deps.localAgents?.stopAllLocalAgents ?? stopAllLocalAgents,
|
|
18768
19375
|
stopLocalAgent: deps.localAgents?.stopLocalAgent ?? stopLocalAgent,
|
|
19376
|
+
updateLocalAgentCard: deps.localAgents?.updateLocalAgentCard ?? updateLocalAgentCard,
|
|
19377
|
+
updateLocalAgentCardLifecycle: deps.localAgents?.updateLocalAgentCardLifecycle ?? updateLocalAgentCardLifecycle,
|
|
19378
|
+
pruneOneTimeLocalAgentCards: deps.localAgents?.pruneOneTimeLocalAgentCards ?? pruneOneTimeLocalAgentCards,
|
|
18769
19379
|
inferLocalAgentBinding: deps.localAgents?.inferLocalAgentBinding ?? inferLocalAgentBinding
|
|
18770
19380
|
};
|
|
18771
19381
|
return {
|
|
@@ -18782,22 +19392,62 @@ function createScoutAgentService(deps) {
|
|
|
18782
19392
|
async downScoutAgent(agentId) {
|
|
18783
19393
|
return localAgents.stopLocalAgent(agentId);
|
|
18784
19394
|
},
|
|
19395
|
+
async retireScoutAgentCard(agentId) {
|
|
19396
|
+
const broker = await deps.loadScoutBrokerContext().catch(() => null);
|
|
19397
|
+
const retired = await localAgents.retireLocalAgent(agentId);
|
|
19398
|
+
if (retired) {
|
|
19399
|
+
await deps.retireScoutLocalAgentBinding?.({ agentId, broker }).catch(() => false);
|
|
19400
|
+
}
|
|
19401
|
+
return retired;
|
|
19402
|
+
},
|
|
19403
|
+
async updateScoutAgentCard(agentId, input) {
|
|
19404
|
+
const config = await localAgents.updateLocalAgentCard(agentId, input);
|
|
19405
|
+
if (!config) {
|
|
19406
|
+
return null;
|
|
19407
|
+
}
|
|
19408
|
+
if (input.restart === true) {
|
|
19409
|
+
await localAgents.restartLocalAgent(agentId);
|
|
19410
|
+
}
|
|
19411
|
+
await deps.registerScoutLocalAgentBinding({ agentId }).catch(() => {});
|
|
19412
|
+
return config;
|
|
19413
|
+
},
|
|
18785
19414
|
async downAllScoutAgents(input = {}) {
|
|
18786
19415
|
return localAgents.stopAllLocalAgents(input);
|
|
18787
19416
|
},
|
|
18788
19417
|
async restartScoutAgents(input = {}) {
|
|
18789
19418
|
return localAgents.restartAllLocalAgents(input);
|
|
18790
19419
|
},
|
|
19420
|
+
async cleanupScoutAgentCards(input = {}) {
|
|
19421
|
+
const broker = await deps.loadScoutBrokerContext().catch(() => null);
|
|
19422
|
+
const result = await localAgents.pruneOneTimeLocalAgentCards(input);
|
|
19423
|
+
await Promise.all(result.retired.map((status) => deps.retireScoutLocalAgentBinding?.({ agentId: status.agentId, broker }).catch(() => false)));
|
|
19424
|
+
return result;
|
|
19425
|
+
},
|
|
18791
19426
|
async createScoutAgentCard(input) {
|
|
19427
|
+
const createdAt = Date.now();
|
|
19428
|
+
const oneTimeUse = input.oneTimeUse === true;
|
|
19429
|
+
const createdById = input.createdById?.trim() || undefined;
|
|
19430
|
+
const agentName = oneTimeUse ? oneTimeAgentName({
|
|
19431
|
+
agentName: input.agentName,
|
|
19432
|
+
projectPath: input.projectPath
|
|
19433
|
+
}) : input.agentName;
|
|
19434
|
+
const lifecycle2 = oneTimeUse ? {
|
|
19435
|
+
kind: "one_time",
|
|
19436
|
+
createdAt,
|
|
19437
|
+
...createdById ? { createdById } : {},
|
|
19438
|
+
expiresAt: createdAt + Math.max(1, input.ttlMs ?? DEFAULT_ONE_TIME_SCOUT_AGENT_CARD_TTL_MS),
|
|
19439
|
+
maxUses: 1
|
|
19440
|
+
} : undefined;
|
|
18792
19441
|
const status = await localAgents.startLocalAgent({
|
|
18793
19442
|
projectPath: input.projectPath,
|
|
18794
|
-
agentName
|
|
19443
|
+
agentName,
|
|
18795
19444
|
displayName: input.displayName,
|
|
18796
19445
|
harness: input.harness,
|
|
18797
19446
|
model: input.model,
|
|
18798
19447
|
reasoningEffort: input.reasoningEffort,
|
|
18799
19448
|
permissionProfile: input.permissionProfile,
|
|
18800
19449
|
currentDirectory: input.currentDirectory,
|
|
19450
|
+
...lifecycle2 ? { card: lifecycle2 } : {},
|
|
18801
19451
|
ensureOnline: false
|
|
18802
19452
|
});
|
|
18803
19453
|
const currentDirectory = input.currentDirectory ?? input.projectPath;
|
|
@@ -18811,21 +19461,38 @@ function createScoutAgentService(deps) {
|
|
|
18811
19461
|
throw new Error(`Agent ${status.agentId} did not expose an addressable binding.`);
|
|
18812
19462
|
}
|
|
18813
19463
|
let inboxConversationId;
|
|
18814
|
-
let
|
|
18815
|
-
if (broker &&
|
|
19464
|
+
let resolvedCreatedById = createdById;
|
|
19465
|
+
if (broker && resolvedCreatedById && resolvedCreatedById !== binding.agent.id) {
|
|
18816
19466
|
const session = await deps.openScoutPeerSession({
|
|
18817
|
-
sourceId:
|
|
19467
|
+
sourceId: resolvedCreatedById,
|
|
18818
19468
|
targetId: binding.agent.id,
|
|
18819
19469
|
currentDirectory
|
|
18820
19470
|
});
|
|
18821
19471
|
inboxConversationId = session.conversation.id;
|
|
18822
|
-
|
|
19472
|
+
resolvedCreatedById = session.sourceId;
|
|
19473
|
+
}
|
|
19474
|
+
const finalLifecycle = lifecycle2 ? {
|
|
19475
|
+
...lifecycle2,
|
|
19476
|
+
...resolvedCreatedById ? { createdById: resolvedCreatedById } : {},
|
|
19477
|
+
...inboxConversationId ? { inboxConversationId } : {}
|
|
19478
|
+
} : undefined;
|
|
19479
|
+
if (finalLifecycle) {
|
|
19480
|
+
await localAgents.updateLocalAgentCardLifecycle(status.agentId, finalLifecycle).catch(() => null);
|
|
19481
|
+
const cleaned = await localAgents.pruneOneTimeLocalAgentCards({
|
|
19482
|
+
createdById: finalLifecycle.createdById,
|
|
19483
|
+
projectRoot: binding.endpoint.projectRoot ?? input.projectPath,
|
|
19484
|
+
excludeAgentIds: [status.agentId]
|
|
19485
|
+
}).catch(() => null);
|
|
19486
|
+
if (cleaned?.retired.length) {
|
|
19487
|
+
await Promise.all(cleaned.retired.map((retired) => deps.retireScoutLocalAgentBinding?.({ agentId: retired.agentId, broker }).catch(() => false)));
|
|
19488
|
+
}
|
|
18823
19489
|
}
|
|
18824
19490
|
return buildScoutAgentCard(binding, {
|
|
18825
19491
|
currentDirectory,
|
|
18826
|
-
createdById,
|
|
19492
|
+
createdById: resolvedCreatedById,
|
|
18827
19493
|
brokerRegistered: syncResult?.brokerRegistered ?? false,
|
|
18828
|
-
inboxConversationId
|
|
19494
|
+
inboxConversationId,
|
|
19495
|
+
lifecycle: finalLifecycle
|
|
18829
19496
|
});
|
|
18830
19497
|
}
|
|
18831
19498
|
};
|
|
@@ -18838,30 +19505,31 @@ init_broker_api();
|
|
|
18838
19505
|
init_broker_process_manager();
|
|
18839
19506
|
init_local_agents();
|
|
18840
19507
|
init_support_paths();
|
|
19508
|
+
init_user_config();
|
|
18841
19509
|
|
|
18842
19510
|
// ../runtime/src/local-config.ts
|
|
18843
19511
|
import {
|
|
18844
|
-
existsSync as
|
|
18845
|
-
mkdirSync as
|
|
18846
|
-
readFileSync as
|
|
19512
|
+
existsSync as existsSync18,
|
|
19513
|
+
mkdirSync as mkdirSync10,
|
|
19514
|
+
readFileSync as readFileSync12,
|
|
18847
19515
|
renameSync as renameSync2,
|
|
18848
|
-
writeFileSync as
|
|
19516
|
+
writeFileSync as writeFileSync7
|
|
18849
19517
|
} from "fs";
|
|
18850
|
-
import { homedir as
|
|
18851
|
-
import { dirname as
|
|
19518
|
+
import { homedir as homedir19, hostname as osHostname } from "os";
|
|
19519
|
+
import { dirname as dirname11, join as join21 } from "path";
|
|
18852
19520
|
var LOCAL_CONFIG_VERSION = 1;
|
|
18853
19521
|
function localConfigHome() {
|
|
18854
|
-
return process.env.OPENSCOUT_HOME ??
|
|
19522
|
+
return process.env.OPENSCOUT_HOME ?? join21(homedir19(), ".openscout");
|
|
18855
19523
|
}
|
|
18856
19524
|
function localConfigPath() {
|
|
18857
|
-
return
|
|
19525
|
+
return join21(localConfigHome(), "config.json");
|
|
18858
19526
|
}
|
|
18859
19527
|
function loadLocalConfig() {
|
|
18860
19528
|
const configPath = localConfigPath();
|
|
18861
|
-
if (!
|
|
19529
|
+
if (!existsSync18(configPath))
|
|
18862
19530
|
return { version: LOCAL_CONFIG_VERSION };
|
|
18863
19531
|
try {
|
|
18864
|
-
return validateLocalConfig(JSON.parse(
|
|
19532
|
+
return validateLocalConfig(JSON.parse(readFileSync12(configPath, "utf8")));
|
|
18865
19533
|
} catch {
|
|
18866
19534
|
return { version: LOCAL_CONFIG_VERSION };
|
|
18867
19535
|
}
|
|
@@ -18952,6 +19620,13 @@ function metadataString3(metadata, key) {
|
|
|
18952
19620
|
function metadataBoolean(metadata, key) {
|
|
18953
19621
|
return metadata?.[key] === true;
|
|
18954
19622
|
}
|
|
19623
|
+
function isSupersededBrokerAgent(snapshot, agentId) {
|
|
19624
|
+
const agent = snapshot.agents[agentId];
|
|
19625
|
+
if (!agent) {
|
|
19626
|
+
return false;
|
|
19627
|
+
}
|
|
19628
|
+
return metadataBoolean(agent.metadata, "retiredFromFleet") || metadataBoolean(agent.metadata, "staleLocalRegistration");
|
|
19629
|
+
}
|
|
18955
19630
|
async function brokerReadJson(baseUrl, path2, options = {}) {
|
|
18956
19631
|
return requestScoutBrokerJson(baseUrl, path2, {
|
|
18957
19632
|
socketPath: resolveBrokerSocketPathForBaseUrl(baseUrl),
|
|
@@ -18989,6 +19664,17 @@ async function readScoutBrokerHealth(baseUrl = resolveScoutBrokerUrl(), options
|
|
|
18989
19664
|
nodes: health.counts.nodes ?? 0,
|
|
18990
19665
|
actors: health.counts.actors ?? 0,
|
|
18991
19666
|
agents: health.counts.agents ?? 0,
|
|
19667
|
+
agentRecords: health.counts.agentRecords,
|
|
19668
|
+
rawAgentRecords: health.counts.rawAgentRecords,
|
|
19669
|
+
configuredAgents: health.counts.configuredAgents,
|
|
19670
|
+
scoutManagedAgents: health.counts.scoutManagedAgents,
|
|
19671
|
+
currentAgentRegistrations: health.counts.currentAgentRegistrations,
|
|
19672
|
+
localAgentRegistrations: health.counts.localAgentRegistrations,
|
|
19673
|
+
remoteAgentRegistrations: health.counts.remoteAgentRegistrations,
|
|
19674
|
+
staleAgentRegistrations: health.counts.staleAgentRegistrations,
|
|
19675
|
+
retiredAgentRegistrations: health.counts.retiredAgentRegistrations,
|
|
19676
|
+
oneTimeAgentCards: health.counts.oneTimeAgentCards,
|
|
19677
|
+
persistentAgentCards: health.counts.persistentAgentCards,
|
|
18992
19678
|
conversations: health.counts.conversations ?? 0,
|
|
18993
19679
|
messages: health.counts.messages ?? 0,
|
|
18994
19680
|
flights: health.counts.flights ?? 0
|
|
@@ -19138,6 +19824,48 @@ async function registerScoutLocalAgentBinding(input) {
|
|
|
19138
19824
|
brokerRegistered: Boolean(broker)
|
|
19139
19825
|
};
|
|
19140
19826
|
}
|
|
19827
|
+
async function retireScoutLocalAgentBinding(input) {
|
|
19828
|
+
const broker = input.broker ?? await loadScoutBrokerContext();
|
|
19829
|
+
if (!broker) {
|
|
19830
|
+
return false;
|
|
19831
|
+
}
|
|
19832
|
+
const retiredAt = Date.now();
|
|
19833
|
+
let retired = false;
|
|
19834
|
+
const agent = broker.snapshot.agents[input.agentId];
|
|
19835
|
+
if (agent) {
|
|
19836
|
+
const nextAgent = {
|
|
19837
|
+
...agent,
|
|
19838
|
+
metadata: {
|
|
19839
|
+
...agent.metadata ?? {},
|
|
19840
|
+
retiredFromFleet: true,
|
|
19841
|
+
retiredAt
|
|
19842
|
+
}
|
|
19843
|
+
};
|
|
19844
|
+
await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.agents, nextAgent);
|
|
19845
|
+
broker.snapshot.agents[input.agentId] = nextAgent;
|
|
19846
|
+
retired = true;
|
|
19847
|
+
}
|
|
19848
|
+
for (const endpoint of Object.values(broker.snapshot.endpoints)) {
|
|
19849
|
+
if (endpoint.agentId !== input.agentId) {
|
|
19850
|
+
continue;
|
|
19851
|
+
}
|
|
19852
|
+
const nextEndpoint = {
|
|
19853
|
+
...endpoint,
|
|
19854
|
+
state: "offline",
|
|
19855
|
+
metadata: {
|
|
19856
|
+
...endpoint.metadata ?? {},
|
|
19857
|
+
retiredFromFleet: true,
|
|
19858
|
+
retiredAt,
|
|
19859
|
+
lastError: "local agent card retired",
|
|
19860
|
+
lastFailedAt: retiredAt
|
|
19861
|
+
}
|
|
19862
|
+
};
|
|
19863
|
+
await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.endpoints, nextEndpoint);
|
|
19864
|
+
broker.snapshot.endpoints[endpoint.id] = nextEndpoint;
|
|
19865
|
+
retired = true;
|
|
19866
|
+
}
|
|
19867
|
+
return retired;
|
|
19868
|
+
}
|
|
19141
19869
|
async function resolveConversationActorId(baseUrl, snapshot, nodeId, actorId, currentDirectory, displayName) {
|
|
19142
19870
|
const normalized = actorId.trim() || OPERATOR_ID;
|
|
19143
19871
|
if (snapshot.agents[normalized] || snapshot.actors[normalized]) {
|
|
@@ -19165,6 +19893,9 @@ async function resolveConversationActorId(baseUrl, snapshot, nodeId, actorId, cu
|
|
|
19165
19893
|
}
|
|
19166
19894
|
async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agentId, currentDirectory) {
|
|
19167
19895
|
const existingAgent = snapshot.agents[agentId];
|
|
19896
|
+
if (existingAgent && metadataBoolean(existingAgent.metadata, "retiredFromFleet")) {
|
|
19897
|
+
return false;
|
|
19898
|
+
}
|
|
19168
19899
|
if (existingAgent && !metadataBoolean(existingAgent.metadata, "staleLocalRegistration")) {
|
|
19169
19900
|
return true;
|
|
19170
19901
|
}
|
|
@@ -19173,7 +19904,7 @@ async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agent
|
|
|
19173
19904
|
syncLegacyMirror: true
|
|
19174
19905
|
});
|
|
19175
19906
|
if (!configured) {
|
|
19176
|
-
return
|
|
19907
|
+
return Boolean(existingAgent);
|
|
19177
19908
|
}
|
|
19178
19909
|
const binding = await inferLocalAgentBinding(configured.agentId, nodeId);
|
|
19179
19910
|
if (!binding) {
|
|
@@ -19254,6 +19985,11 @@ async function sendScoutDirectMessage(input) {
|
|
|
19254
19985
|
const broker = await requireScoutBrokerContext();
|
|
19255
19986
|
const createdAt = Date.now();
|
|
19256
19987
|
const source = input.source?.trim() || "scout-mobile";
|
|
19988
|
+
const targetEndpoints = Object.values(broker.snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === input.agentId);
|
|
19989
|
+
const targetIsStale = isSupersededBrokerAgent(broker.snapshot, input.agentId) || targetEndpoints.length > 0 && targetEndpoints.every((endpoint) => metadataBoolean(endpoint.metadata, "retiredFromFleet") || metadataBoolean(endpoint.metadata, "staleLocalRegistration"));
|
|
19990
|
+
if (targetIsStale) {
|
|
19991
|
+
throw new Error(`${displayNameForBrokerActor(broker.snapshot, input.agentId)} is a stale local registration. Start the current project session from Workspaces before sending.`);
|
|
19992
|
+
}
|
|
19257
19993
|
const delivery = await brokerPostDeliver(broker.baseUrl, {
|
|
19258
19994
|
id: `deliver-${createdAt.toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
19259
19995
|
requesterId: OPERATOR_ID,
|
|
@@ -19311,24 +20047,28 @@ async function loadScoutActivityItems(options = {}) {
|
|
|
19311
20047
|
var scoutAgentService = createScoutAgentService({
|
|
19312
20048
|
loadScoutBrokerContext,
|
|
19313
20049
|
openScoutPeerSession,
|
|
19314
|
-
registerScoutLocalAgentBinding
|
|
20050
|
+
registerScoutLocalAgentBinding,
|
|
20051
|
+
retireScoutLocalAgentBinding
|
|
19315
20052
|
});
|
|
19316
20053
|
var {
|
|
20054
|
+
cleanupScoutAgentCards,
|
|
19317
20055
|
createScoutAgentCard,
|
|
19318
20056
|
downAllScoutAgents,
|
|
19319
20057
|
downScoutAgent,
|
|
19320
20058
|
loadScoutAgentStatuses,
|
|
20059
|
+
retireScoutAgentCard,
|
|
19321
20060
|
restartScoutAgents,
|
|
20061
|
+
updateScoutAgentCard,
|
|
19322
20062
|
upScoutAgent
|
|
19323
20063
|
} = scoutAgentService;
|
|
19324
20064
|
|
|
19325
20065
|
// ../web/server/db/internal/db.ts
|
|
19326
20066
|
import { Database } from "bun:sqlite";
|
|
19327
|
-
import { homedir as
|
|
19328
|
-
import { join as
|
|
20067
|
+
import { homedir as homedir20 } from "os";
|
|
20068
|
+
import { join as join22 } from "path";
|
|
19329
20069
|
function resolveDbPath() {
|
|
19330
|
-
const controlHome = process.env.OPENSCOUT_CONTROL_HOME ??
|
|
19331
|
-
return
|
|
20070
|
+
const controlHome = process.env.OPENSCOUT_CONTROL_HOME ?? join22(homedir20(), ".openscout", "control-plane");
|
|
20071
|
+
return join22(controlHome, "control-plane.sqlite");
|
|
19332
20072
|
}
|
|
19333
20073
|
var _db = null;
|
|
19334
20074
|
var DB_BUSY_TIMEOUT_MS = 250;
|
|
@@ -19344,6 +20084,7 @@ function db() {
|
|
|
19344
20084
|
return _db;
|
|
19345
20085
|
}
|
|
19346
20086
|
// ../runtime/src/conversations/legacy-ids.ts
|
|
20087
|
+
init_user_config();
|
|
19347
20088
|
function conversationIdForAgent(agentId) {
|
|
19348
20089
|
return buildDirectConversationId("operator", agentId);
|
|
19349
20090
|
}
|
|
@@ -19360,9 +20101,9 @@ var EPOCH_MILLISECONDS_FLOOR = 1000000000000;
|
|
|
19360
20101
|
|
|
19361
20102
|
// ../web/server/db/internal/paths.ts
|
|
19362
20103
|
init_support_paths();
|
|
19363
|
-
import { homedir as
|
|
19364
|
-
import { join as
|
|
19365
|
-
var HOME =
|
|
20104
|
+
import { homedir as homedir21 } from "os";
|
|
20105
|
+
import { join as join23 } from "path";
|
|
20106
|
+
var HOME = homedir21();
|
|
19366
20107
|
function compact(p) {
|
|
19367
20108
|
if (!p)
|
|
19368
20109
|
return null;
|
|
@@ -19374,12 +20115,15 @@ function pairingHarnessLogPath(adapterType, sessionId) {
|
|
|
19374
20115
|
if (!normalizedAdapter || !normalizedSessionId) {
|
|
19375
20116
|
return null;
|
|
19376
20117
|
}
|
|
19377
|
-
return
|
|
20118
|
+
return join23(HOME, ".scout", "pairing", normalizedAdapter, normalizedSessionId, "logs", "stdout.log");
|
|
19378
20119
|
}
|
|
19379
20120
|
function relayHarnessLogPath(agentId) {
|
|
19380
|
-
return
|
|
20121
|
+
return join23(relayAgentLogsDirectory(agentId), "stdout.log");
|
|
19381
20122
|
}
|
|
19382
20123
|
function resolveHarnessSessionId(transport, endpointSessionId, metadata) {
|
|
20124
|
+
if (transport === "tmux") {
|
|
20125
|
+
return metadataString4(metadata, "tmuxSession") ?? endpointSessionId;
|
|
20126
|
+
}
|
|
19383
20127
|
if (transport === "pairing_bridge") {
|
|
19384
20128
|
const attachedTransport = metadataString4(metadata, "attachedTransport");
|
|
19385
20129
|
if (attachedTransport === "codex_app_server") {
|
|
@@ -19444,7 +20188,8 @@ function summarizeAgentState(rawState, isWorking, wakePolicy) {
|
|
|
19444
20188
|
if (isWorking) {
|
|
19445
20189
|
return "working";
|
|
19446
20190
|
}
|
|
19447
|
-
|
|
20191
|
+
const wakeableWithoutEndpoint = wakePolicy === "on_demand" || wakePolicy === "always_on";
|
|
20192
|
+
if ((!rawState || rawState === "offline") && wakeableWithoutEndpoint) {
|
|
19448
20193
|
return "available";
|
|
19449
20194
|
}
|
|
19450
20195
|
return rawState && rawState !== "offline" ? "available" : "offline";
|
|
@@ -19466,7 +20211,11 @@ function transientBrokerWorkingStatusPredicate(alias) {
|
|
|
19466
20211
|
return `NOT (
|
|
19467
20212
|
${alias}.class = 'status'
|
|
19468
20213
|
AND COALESCE(json_extract(${alias}.metadata_json, '$.source'), '') = 'broker'
|
|
19469
|
-
AND
|
|
20214
|
+
AND (
|
|
20215
|
+
${alias}.body LIKE '% is working.'
|
|
20216
|
+
OR ${alias}.body LIKE '%Scout stopped waiting for a synchronous result%'
|
|
20217
|
+
OR ${alias}.body LIKE '%the requester stopped waiting after%'
|
|
20218
|
+
)
|
|
19470
20219
|
)`;
|
|
19471
20220
|
}
|
|
19472
20221
|
// ../web/server/db/activity.ts
|
|
@@ -19485,13 +20234,29 @@ var ACTIVE_RUN_STATES_SQL = sqlStringList([...ACTIVE_RUN_STATES]);
|
|
|
19485
20234
|
var AGENT_RUN_STATES_SQL = sqlStringList(AGENT_RUN_STATES);
|
|
19486
20235
|
var AGENT_RUN_SOURCES_SQL = sqlStringList(AGENT_RUN_SOURCES);
|
|
19487
20236
|
// ../web/server/db/fleet.ts
|
|
20237
|
+
init_user_config();
|
|
19488
20238
|
var TERMINAL_FLIGHT_STATES = new Set(["completed", "failed", "cancelled"]);
|
|
19489
20239
|
var FLEET_RECENT_COMPLETED_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000;
|
|
19490
20240
|
// ../web/server/db/mobile/agents.ts
|
|
19491
|
-
function queryMobileAgents(limit = 50) {
|
|
20241
|
+
function queryMobileAgents(limit = 50, filters = {}) {
|
|
19492
20242
|
const executingAgentIds = queryExecutingAgentIds();
|
|
19493
20243
|
const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
|
|
19494
20244
|
const endpointUpdatedAtExpression = sqlTimestampMsExpression("ep.updated_at");
|
|
20245
|
+
const query = filters.query?.trim().toLowerCase();
|
|
20246
|
+
const whereClauses = [activeAgentMetadataPredicate("a")];
|
|
20247
|
+
const params = [];
|
|
20248
|
+
if (query) {
|
|
20249
|
+
whereClauses.push(`(
|
|
20250
|
+
lower(ac.display_name) LIKE ?
|
|
20251
|
+
OR lower(a.id) LIKE ?
|
|
20252
|
+
OR lower(COALESCE(a.default_selector, '')) LIKE ?
|
|
20253
|
+
OR lower(COALESCE(a.selector, '')) LIKE ?
|
|
20254
|
+
OR lower(COALESCE(ep.project_root, '')) LIKE ?
|
|
20255
|
+
)`);
|
|
20256
|
+
const pattern = `%${query}%`;
|
|
20257
|
+
params.push(pattern, pattern, pattern, pattern, pattern);
|
|
20258
|
+
}
|
|
20259
|
+
params.push(limit);
|
|
19495
20260
|
const lastMessageAt = new Map(db().prepare(`SELECT actor_id, MAX(${messageCreatedAtExpression}) AS last_at FROM messages GROUP BY actor_id`).all().map((r) => [r.actor_id, r.last_at]));
|
|
19496
20261
|
const rows = db().prepare(`SELECT
|
|
19497
20262
|
a.id,
|
|
@@ -19508,9 +20273,9 @@ function queryMobileAgents(limit = 50) {
|
|
|
19508
20273
|
FROM agents a
|
|
19509
20274
|
JOIN actors ac ON ac.id = a.id
|
|
19510
20275
|
${LATEST_AGENT_ENDPOINT_JOIN}
|
|
19511
|
-
WHERE ${
|
|
20276
|
+
WHERE ${whereClauses.join(" AND ")}
|
|
19512
20277
|
ORDER BY COALESCE(${endpointUpdatedAtExpression}, 0) DESC, ac.display_name ASC
|
|
19513
|
-
LIMIT ?`).all(
|
|
20278
|
+
LIMIT ?`).all(...params);
|
|
19514
20279
|
return rows.map((r) => {
|
|
19515
20280
|
let meta = {};
|
|
19516
20281
|
try {
|
|
@@ -19619,6 +20384,9 @@ function queryMobileAgentDetail(agentId) {
|
|
|
19619
20384
|
}
|
|
19620
20385
|
// ../web/server/db/mobile/sessions.ts
|
|
19621
20386
|
function queryMobileSessions(limit = 50) {
|
|
20387
|
+
const conversationCreatedAtExpression = sqlTimestampMsExpression("c.created_at");
|
|
20388
|
+
const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
|
|
20389
|
+
const previewMessageCreatedAtExpression = sqlTimestampMsExpression("m.created_at");
|
|
19622
20390
|
const rows = db().prepare(`SELECT
|
|
19623
20391
|
c.id,
|
|
19624
20392
|
c.kind,
|
|
@@ -19626,16 +20394,16 @@ function queryMobileSessions(limit = 50) {
|
|
|
19626
20394
|
c.metadata_json
|
|
19627
20395
|
FROM conversations c
|
|
19628
20396
|
WHERE c.kind = 'direct'
|
|
19629
|
-
ORDER BY
|
|
20397
|
+
ORDER BY ${conversationCreatedAtExpression} DESC
|
|
19630
20398
|
LIMIT ?`).all(limit);
|
|
19631
20399
|
const memberStmt = db().prepare(`SELECT actor_id FROM conversation_members WHERE conversation_id = ?`);
|
|
19632
|
-
const statsStmt = db().prepare(`SELECT COUNT(*) AS cnt, MAX(
|
|
20400
|
+
const statsStmt = db().prepare(`SELECT COUNT(*) AS cnt, MAX(${messageCreatedAtExpression}) AS last_at FROM messages WHERE conversation_id = ?`);
|
|
19633
20401
|
const previewStmt = db().prepare(`SELECT body
|
|
19634
20402
|
FROM messages m
|
|
19635
20403
|
WHERE conversation_id = ?
|
|
19636
20404
|
AND actor_id != 'operator'
|
|
19637
20405
|
AND ${transientBrokerWorkingStatusPredicate("m")}
|
|
19638
|
-
ORDER BY
|
|
20406
|
+
ORDER BY ${previewMessageCreatedAtExpression} DESC LIMIT 1`);
|
|
19639
20407
|
return rows.map((r) => {
|
|
19640
20408
|
const participants = memberStmt.all(r.id).map((m) => m.actor_id);
|
|
19641
20409
|
const agentId = participants.find((p) => p !== "operator") ?? null;
|
|
@@ -19741,6 +20509,713 @@ function queryMobileWorkspaces(limit = 50) {
|
|
|
19741
20509
|
}
|
|
19742
20510
|
return results;
|
|
19743
20511
|
}
|
|
20512
|
+
// ../web/server/scoutbot/directives.ts
|
|
20513
|
+
var ACTIONS = new Set([
|
|
20514
|
+
"help",
|
|
20515
|
+
"agents",
|
|
20516
|
+
"status",
|
|
20517
|
+
"recent",
|
|
20518
|
+
"doing",
|
|
20519
|
+
"flight",
|
|
20520
|
+
"steer"
|
|
20521
|
+
]);
|
|
20522
|
+
var EFFORT_ALIASES = {
|
|
20523
|
+
none: "none",
|
|
20524
|
+
off: "none",
|
|
20525
|
+
minimal: "minimal",
|
|
20526
|
+
min: "minimal",
|
|
20527
|
+
low: "low",
|
|
20528
|
+
quick: "low",
|
|
20529
|
+
medium: "medium",
|
|
20530
|
+
med: "medium",
|
|
20531
|
+
mid: "medium",
|
|
20532
|
+
high: "high",
|
|
20533
|
+
deep: "high",
|
|
20534
|
+
xhigh: "xhigh",
|
|
20535
|
+
max: "xhigh"
|
|
20536
|
+
};
|
|
20537
|
+
var EFFORT_KEYS = new Set(["eff", "effort", "reasoning", "reasoning-effort", "reasoning_effort"]);
|
|
20538
|
+
var SESSION_KEYS = new Set(["sid", "session", "session-id", "session_id", "target-session", "target_session"]);
|
|
20539
|
+
function parseScoutbotDirectives(input) {
|
|
20540
|
+
const original = input.trim();
|
|
20541
|
+
const rawTokens = original.split(/\s+/).filter(Boolean);
|
|
20542
|
+
const directives = {};
|
|
20543
|
+
const bodyTokens = [];
|
|
20544
|
+
const messageTokens = [];
|
|
20545
|
+
let command = null;
|
|
20546
|
+
let commandIndex = -1;
|
|
20547
|
+
for (let index = 0;index < rawTokens.length; index += 1) {
|
|
20548
|
+
const token = rawTokens[index] ?? "";
|
|
20549
|
+
const directive = parseDirectiveToken(token);
|
|
20550
|
+
if (directive) {
|
|
20551
|
+
if (directive.kind === "reasoningEffort") {
|
|
20552
|
+
directives.reasoningEffort = directive.value;
|
|
20553
|
+
} else {
|
|
20554
|
+
directives.targetSessionId = directive.value;
|
|
20555
|
+
}
|
|
20556
|
+
continue;
|
|
20557
|
+
}
|
|
20558
|
+
messageTokens.push(token);
|
|
20559
|
+
const action = command ? null : parseActionToken(token);
|
|
20560
|
+
if (action) {
|
|
20561
|
+
command = { name: action, raw: token };
|
|
20562
|
+
commandIndex = index;
|
|
20563
|
+
continue;
|
|
20564
|
+
}
|
|
20565
|
+
bodyTokens.push(token);
|
|
20566
|
+
}
|
|
20567
|
+
const args = command ? rawTokens.slice(commandIndex + 1).filter((token) => !parseDirectiveToken(token) && !parseActionToken(token)).join(" ").trim() : "";
|
|
20568
|
+
return {
|
|
20569
|
+
original,
|
|
20570
|
+
body: bodyTokens.join(" ").trim(),
|
|
20571
|
+
messageBody: messageTokens.join(" ").trim(),
|
|
20572
|
+
command: command ? { ...command, args } : null,
|
|
20573
|
+
directives
|
|
20574
|
+
};
|
|
20575
|
+
}
|
|
20576
|
+
function scoutbotDirectiveMetadata(parsed) {
|
|
20577
|
+
const metadata = {};
|
|
20578
|
+
if (parsed.command)
|
|
20579
|
+
metadata.scoutbotAction = parsed.command.name;
|
|
20580
|
+
if (parsed.directives.reasoningEffort)
|
|
20581
|
+
metadata.reasoningEffort = parsed.directives.reasoningEffort;
|
|
20582
|
+
if (parsed.directives.targetSessionId)
|
|
20583
|
+
metadata.targetSessionId = parsed.directives.targetSessionId;
|
|
20584
|
+
if ((parsed.directives.reasoningEffort || parsed.directives.targetSessionId) && parsed.body && parsed.body !== parsed.original) {
|
|
20585
|
+
metadata.directiveBody = parsed.body;
|
|
20586
|
+
}
|
|
20587
|
+
return metadata;
|
|
20588
|
+
}
|
|
20589
|
+
function parseActionToken(rawToken) {
|
|
20590
|
+
const token = trimTokenBoundary(rawToken);
|
|
20591
|
+
const match = token.match(/^\/([A-Za-z][A-Za-z0-9_-]*)$/);
|
|
20592
|
+
if (!match?.[1])
|
|
20593
|
+
return null;
|
|
20594
|
+
const action = match[1].toLowerCase().replace(/-/g, "_");
|
|
20595
|
+
return ACTIONS.has(action) ? action : null;
|
|
20596
|
+
}
|
|
20597
|
+
function parseDirectiveToken(rawToken) {
|
|
20598
|
+
const token = trimTokenBoundary(rawToken);
|
|
20599
|
+
const separator = token.indexOf(":");
|
|
20600
|
+
if (separator <= 0)
|
|
20601
|
+
return null;
|
|
20602
|
+
const key = token.slice(0, separator).trim().toLowerCase();
|
|
20603
|
+
const value = normalizeDirectiveValue(token.slice(separator + 1));
|
|
20604
|
+
if (!value)
|
|
20605
|
+
return null;
|
|
20606
|
+
if (EFFORT_KEYS.has(key)) {
|
|
20607
|
+
const effort = EFFORT_ALIASES[value.toLowerCase()];
|
|
20608
|
+
return effort ? { kind: "reasoningEffort", value: effort } : null;
|
|
20609
|
+
}
|
|
20610
|
+
if (SESSION_KEYS.has(key) && isValidSessionId(value)) {
|
|
20611
|
+
return { kind: "targetSessionId", value };
|
|
20612
|
+
}
|
|
20613
|
+
return null;
|
|
20614
|
+
}
|
|
20615
|
+
function normalizeDirectiveValue(value) {
|
|
20616
|
+
return trimTokenBoundary(value).replace(/^["']+|["']+$/g, "").trim();
|
|
20617
|
+
}
|
|
20618
|
+
function trimTokenBoundary(value) {
|
|
20619
|
+
return value.trim().replace(/^[([{]+/g, "").replace(/[)\]},.!?;]+$/g, "");
|
|
20620
|
+
}
|
|
20621
|
+
function isValidSessionId(value) {
|
|
20622
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value);
|
|
20623
|
+
}
|
|
20624
|
+
|
|
20625
|
+
// ../web/server/scoutbot/role.ts
|
|
20626
|
+
var SCOUTBOT_AGENT_ID = "scoutbot";
|
|
20627
|
+
var SCOUTBOT_DISPLAY_NAME = "Scout";
|
|
20628
|
+
var SCOUTBOT_HANDLE = "scoutbot";
|
|
20629
|
+
var SCOUTBOT_DEFAULT_THREAD_ID = "thr-default";
|
|
20630
|
+
var SCOUTBOT_DEFAULT_THREAD_NAME = "default";
|
|
20631
|
+
var SCOUTBOT_DEFAULT_CONVERSATION_ID = "dm.operator.scoutbot.default";
|
|
20632
|
+
var SCOUTBOT_LEGACY_CONVERSATION_ID = "dm.operator.scoutbot";
|
|
20633
|
+
var SCOUTBOT_ENDPOINT_ID = "endpoint.scoutbot.codex_app_server";
|
|
20634
|
+
var SCOUTBOT_RUNTIME_INSTANCE_ID = "scoutbot-default";
|
|
20635
|
+
var SCOUTBOT_REASONING_EFFORT = "low";
|
|
20636
|
+
var SCOUTBOT_SYSTEM_PROMPT = `# Scoutbot role
|
|
20637
|
+
|
|
20638
|
+
You are Scoutbot, the operator-facing concierge for the local OpenScout fleet.
|
|
20639
|
+
|
|
20640
|
+
Your job is to read broker state, explain what is happening, and perform structured broker operations on the operator's behalf. You do not write code, edit files, or run shell commands. If a task requires project work, ask or dispatch the appropriate project agent instead of doing that work yourself.
|
|
20641
|
+
|
|
20642
|
+
## Operating loop
|
|
20643
|
+
|
|
20644
|
+
Default to a low-effort triage pass. Your first move is to read the current broker facts and recent thread context, then answer directly if the operator is asking for status, latest activity, who is blocked, what changed, routing, or a next-action recommendation.
|
|
20645
|
+
|
|
20646
|
+
For status questions such as "what's latest on Hudson", answer from broker facts in a few bullets:
|
|
20647
|
+
- current state
|
|
20648
|
+
- most recent activity
|
|
20649
|
+
- blocker or risk
|
|
20650
|
+
- suggested next action
|
|
20651
|
+
|
|
20652
|
+
Do not start a broad investigation, inspect code, or dispatch project work unless the operator asks for that next step.
|
|
20653
|
+
|
|
20654
|
+
## Inline answers
|
|
20655
|
+
|
|
20656
|
+
Answer inline when the request can be handled from broker state, recent messages, active flights, agent registrations, endpoint state, or known routing metadata. Keep these answers short and explicit. Separate observed facts from inference.
|
|
20657
|
+
|
|
20658
|
+
## Offloading
|
|
20659
|
+
|
|
20660
|
+
Offload with structured broker operations when the request requires a project agent to inspect files, run commands, reproduce a bug, write code, review a diff, research a repo, operate a UI, or own multi-step work. Pick the agent by explicit routing metadata or by the closest matching project/workspace identity. If the target is ambiguous, ask one clarifying question instead of guessing.
|
|
20661
|
+
|
|
20662
|
+
Use send_message for tells/status nudges. Use ask_agent or dispatch_subagent when the meaning is "own this and report back". Use cancel_flight only for a specific active flight the operator wants stopped.
|
|
20663
|
+
|
|
20664
|
+
## Parallelism
|
|
20665
|
+
|
|
20666
|
+
Use parallel asks only when the work naturally splits into independent lanes, for example frontend and backend investigation, reproduce and log inspection, docs and implementation review, or several agents owning separate repos. Keep fanout bounded, usually two to four agents. Do not parallelize when agents would edit the same files, depend on a single sequence, or need one owner to make a coherent decision.
|
|
20667
|
+
|
|
20668
|
+
When you fan out, tell the operator who owns each lane and what result you expect back. Prefer an explicit channel for group coordination; use DMs for one-agent work.
|
|
20669
|
+
|
|
20670
|
+
## Tools and routing
|
|
20671
|
+
|
|
20672
|
+
Use read-only broker tools first: list_agents, list_endpoints, list_flights, latest_messages, and current_turn. For writes, use only structured broker tools: send_message, ask_agent, dispatch_subagent, and cancel_flight. No shell access. No codebase writes.
|
|
20673
|
+
|
|
20674
|
+
Routing must be explicit. Resolve targets before broker writes; do not rely on body mentions as instructions. Every broker write you emit must carry Scoutbot provenance so the operator can audit why it happened.
|
|
20675
|
+
|
|
20676
|
+
Prefer concise operational answers. Use the deterministic broker facts available to you, say when you are inferring, and keep follow-up in the same Scout thread unless the operator explicitly asks otherwise.`;
|
|
20677
|
+
var SCOUTBOT_ROLE_CONFIG = {
|
|
20678
|
+
roleId: "scoutbot",
|
|
20679
|
+
systemPrompt: SCOUTBOT_SYSTEM_PROMPT,
|
|
20680
|
+
grants: {
|
|
20681
|
+
read: [
|
|
20682
|
+
"list_agents",
|
|
20683
|
+
"list_endpoints",
|
|
20684
|
+
"list_flights",
|
|
20685
|
+
"latest_messages",
|
|
20686
|
+
"current_turn"
|
|
20687
|
+
],
|
|
20688
|
+
write: [
|
|
20689
|
+
"send_message",
|
|
20690
|
+
"ask_agent",
|
|
20691
|
+
"dispatch_subagent",
|
|
20692
|
+
"cancel_flight"
|
|
20693
|
+
],
|
|
20694
|
+
shell: false,
|
|
20695
|
+
codebaseWrites: false
|
|
20696
|
+
},
|
|
20697
|
+
defaults: {
|
|
20698
|
+
requestedBy: "operator",
|
|
20699
|
+
provenanceSource: "scoutbot",
|
|
20700
|
+
generatedBy: "scoutbot",
|
|
20701
|
+
cwdPolicy: "openscout_control_plane",
|
|
20702
|
+
reasoningEffort: SCOUTBOT_REASONING_EFFORT
|
|
20703
|
+
}
|
|
20704
|
+
};
|
|
20705
|
+
|
|
20706
|
+
// ../web/server/scoutbot/thread-map.ts
|
|
20707
|
+
init_support_paths();
|
|
20708
|
+
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
20709
|
+
import { dirname as dirname12, join as join24 } from "path";
|
|
20710
|
+
class ScoutbotThreadMapStore {
|
|
20711
|
+
filePath;
|
|
20712
|
+
constructor(filePath = defaultScoutbotThreadMapPath()) {
|
|
20713
|
+
this.filePath = filePath;
|
|
20714
|
+
}
|
|
20715
|
+
async getThreads() {
|
|
20716
|
+
const map = await this.read();
|
|
20717
|
+
return map.threads.filter((thread) => !thread.archivedAt);
|
|
20718
|
+
}
|
|
20719
|
+
async list() {
|
|
20720
|
+
const map = await this.read();
|
|
20721
|
+
return {
|
|
20722
|
+
threads: map.threads.filter((thread) => !thread.archivedAt),
|
|
20723
|
+
defaultThreadId: map.defaultThreadId
|
|
20724
|
+
};
|
|
20725
|
+
}
|
|
20726
|
+
async getThread(threadId) {
|
|
20727
|
+
const normalized = threadId.trim();
|
|
20728
|
+
if (!normalized)
|
|
20729
|
+
return null;
|
|
20730
|
+
const map = await this.read();
|
|
20731
|
+
return map.threads.find((thread) => thread.threadId === normalized && !thread.archivedAt) ?? null;
|
|
20732
|
+
}
|
|
20733
|
+
async getThreadByConversationId(conversationId) {
|
|
20734
|
+
const normalized = conversationId.trim();
|
|
20735
|
+
if (!normalized)
|
|
20736
|
+
return null;
|
|
20737
|
+
const map = await this.read();
|
|
20738
|
+
return map.threads.find((thread) => thread.conversationId === normalized && !thread.archivedAt) ?? null;
|
|
20739
|
+
}
|
|
20740
|
+
async ensureDefaultThread(options) {
|
|
20741
|
+
const now = options.now ?? Date.now();
|
|
20742
|
+
const map = await this.read();
|
|
20743
|
+
const existing = map.threads.find((thread2) => thread2.threadId === map.defaultThreadId);
|
|
20744
|
+
if (existing) {
|
|
20745
|
+
const next = {
|
|
20746
|
+
...existing,
|
|
20747
|
+
transportSessionId: options.transportSessionId !== undefined ? normalizeSessionId(options.transportSessionId) : existing.transportSessionId,
|
|
20748
|
+
transport: options.transport ?? existing.transport
|
|
20749
|
+
};
|
|
20750
|
+
if (sameThread(existing, next))
|
|
20751
|
+
return existing;
|
|
20752
|
+
const updated = replaceThread(map, next);
|
|
20753
|
+
await this.write(updated);
|
|
20754
|
+
return next;
|
|
20755
|
+
}
|
|
20756
|
+
const conversationId = chooseDefaultConversationId(options.snapshot);
|
|
20757
|
+
const thread = {
|
|
20758
|
+
threadId: SCOUTBOT_DEFAULT_THREAD_ID,
|
|
20759
|
+
name: SCOUTBOT_DEFAULT_THREAD_NAME,
|
|
20760
|
+
conversationId,
|
|
20761
|
+
transportSessionId: normalizeSessionId(options.transportSessionId),
|
|
20762
|
+
transport: options.transport ?? "codex_app_server",
|
|
20763
|
+
pins: null,
|
|
20764
|
+
lastActiveAt: now
|
|
20765
|
+
};
|
|
20766
|
+
await this.write({
|
|
20767
|
+
version: 1,
|
|
20768
|
+
defaultThreadId: SCOUTBOT_DEFAULT_THREAD_ID,
|
|
20769
|
+
threads: [thread]
|
|
20770
|
+
});
|
|
20771
|
+
return thread;
|
|
20772
|
+
}
|
|
20773
|
+
async createThread(name, opts) {
|
|
20774
|
+
const map = await this.read();
|
|
20775
|
+
const now = opts.now ?? Date.now();
|
|
20776
|
+
const threadId = opts.threadId?.trim() || `thr-${now.toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
20777
|
+
const conversationId = opts.conversationId?.trim() || `dm.operator.scoutbot.${threadId.replace(/^thr-/, "")}`;
|
|
20778
|
+
const thread = {
|
|
20779
|
+
threadId,
|
|
20780
|
+
name: name.trim() || threadId,
|
|
20781
|
+
conversationId,
|
|
20782
|
+
transportSessionId: normalizeSessionId(opts.transportSessionId),
|
|
20783
|
+
transport: opts.transport ?? "codex_app_server",
|
|
20784
|
+
pins: opts.pins ?? null,
|
|
20785
|
+
lastActiveAt: now
|
|
20786
|
+
};
|
|
20787
|
+
await this.write({
|
|
20788
|
+
...map,
|
|
20789
|
+
threads: [...map.threads.filter((candidate) => candidate.threadId !== threadId), thread]
|
|
20790
|
+
});
|
|
20791
|
+
return thread;
|
|
20792
|
+
}
|
|
20793
|
+
async archiveThread(threadId) {
|
|
20794
|
+
const map = await this.read();
|
|
20795
|
+
const thread = map.threads.find((candidate) => candidate.threadId === threadId);
|
|
20796
|
+
if (!thread || thread.threadId === map.defaultThreadId)
|
|
20797
|
+
return null;
|
|
20798
|
+
const archived = { ...thread, archivedAt: Date.now() };
|
|
20799
|
+
await this.write(replaceThread(map, archived));
|
|
20800
|
+
return archived;
|
|
20801
|
+
}
|
|
20802
|
+
async touchThread(threadId, now = Date.now()) {
|
|
20803
|
+
const map = await this.read();
|
|
20804
|
+
const thread = map.threads.find((candidate) => candidate.threadId === threadId);
|
|
20805
|
+
if (!thread)
|
|
20806
|
+
return;
|
|
20807
|
+
await this.write(replaceThread(map, { ...thread, lastActiveAt: now }));
|
|
20808
|
+
}
|
|
20809
|
+
async setThreadTransportSessionId(threadId, transportSessionId, now = Date.now()) {
|
|
20810
|
+
const normalized = normalizeSessionId(transportSessionId);
|
|
20811
|
+
const map = await this.read();
|
|
20812
|
+
const thread = map.threads.find((candidate) => candidate.threadId === threadId);
|
|
20813
|
+
if (!thread)
|
|
20814
|
+
return null;
|
|
20815
|
+
const next = { ...thread, transportSessionId: normalized, lastActiveAt: now };
|
|
20816
|
+
if (sameThread(thread, next))
|
|
20817
|
+
return thread;
|
|
20818
|
+
await this.write(replaceThread(map, next));
|
|
20819
|
+
return next;
|
|
20820
|
+
}
|
|
20821
|
+
async read() {
|
|
20822
|
+
try {
|
|
20823
|
+
const raw = await readFile7(this.filePath, "utf8");
|
|
20824
|
+
const parsed = JSON.parse(raw);
|
|
20825
|
+
const threads = Array.isArray(parsed.threads) ? parsed.threads.filter(isThreadRecord).map((thread) => ({
|
|
20826
|
+
...thread,
|
|
20827
|
+
transportSessionId: normalizeSessionId(thread.transportSessionId)
|
|
20828
|
+
})) : [];
|
|
20829
|
+
return {
|
|
20830
|
+
version: 1,
|
|
20831
|
+
defaultThreadId: typeof parsed.defaultThreadId === "string" && parsed.defaultThreadId.trim() ? parsed.defaultThreadId : SCOUTBOT_DEFAULT_THREAD_ID,
|
|
20832
|
+
threads
|
|
20833
|
+
};
|
|
20834
|
+
} catch (error) {
|
|
20835
|
+
if (error.code === "ENOENT") {
|
|
20836
|
+
return { version: 1, defaultThreadId: SCOUTBOT_DEFAULT_THREAD_ID, threads: [] };
|
|
20837
|
+
}
|
|
20838
|
+
throw error;
|
|
20839
|
+
}
|
|
20840
|
+
}
|
|
20841
|
+
async write(map) {
|
|
20842
|
+
await mkdir7(dirname12(this.filePath), { recursive: true });
|
|
20843
|
+
await writeFile7(this.filePath, `${JSON.stringify(map, null, 2)}
|
|
20844
|
+
`, "utf8");
|
|
20845
|
+
}
|
|
20846
|
+
}
|
|
20847
|
+
function defaultScoutbotThreadMapPath() {
|
|
20848
|
+
return join24(resolveOpenScoutSupportPaths().supportDirectory, "scoutbot-threads.json");
|
|
20849
|
+
}
|
|
20850
|
+
function chooseDefaultConversationId(snapshot) {
|
|
20851
|
+
return snapshot?.conversations?.[SCOUTBOT_LEGACY_CONVERSATION_ID] ? SCOUTBOT_LEGACY_CONVERSATION_ID : SCOUTBOT_DEFAULT_CONVERSATION_ID;
|
|
20852
|
+
}
|
|
20853
|
+
function buildScoutbotThreadConversation(thread, snapshot, nodeId) {
|
|
20854
|
+
const existing = snapshot.conversations[thread.conversationId];
|
|
20855
|
+
if (existing)
|
|
20856
|
+
return null;
|
|
20857
|
+
const participantIds = ["operator", "scoutbot"].sort();
|
|
20858
|
+
return {
|
|
20859
|
+
id: thread.conversationId,
|
|
20860
|
+
kind: "direct",
|
|
20861
|
+
title: thread.name === SCOUTBOT_DEFAULT_THREAD_NAME ? "Scout \xB7 default" : `Scout \xB7 ${thread.name}`,
|
|
20862
|
+
visibility: "private",
|
|
20863
|
+
shareMode: "local",
|
|
20864
|
+
authorityNodeId: nodeId,
|
|
20865
|
+
participantIds,
|
|
20866
|
+
metadata: {
|
|
20867
|
+
surface: "scoutbot",
|
|
20868
|
+
scoutbotThreadId: thread.threadId,
|
|
20869
|
+
transportSessionId: thread.transportSessionId,
|
|
20870
|
+
transport: thread.transport
|
|
20871
|
+
}
|
|
20872
|
+
};
|
|
20873
|
+
}
|
|
20874
|
+
function isThreadRecord(value) {
|
|
20875
|
+
if (!value || typeof value !== "object")
|
|
20876
|
+
return false;
|
|
20877
|
+
const record = value;
|
|
20878
|
+
return typeof record.threadId === "string" && typeof record.name === "string" && typeof record.conversationId === "string" && (typeof record.transportSessionId === "string" || record.transportSessionId === null || record.transportSessionId === undefined) && typeof record.transport === "string" && typeof record.lastActiveAt === "number";
|
|
20879
|
+
}
|
|
20880
|
+
function normalizeSessionId(value) {
|
|
20881
|
+
const normalized = value?.trim();
|
|
20882
|
+
return normalized ? normalized : null;
|
|
20883
|
+
}
|
|
20884
|
+
function replaceThread(map, thread) {
|
|
20885
|
+
return {
|
|
20886
|
+
...map,
|
|
20887
|
+
threads: map.threads.map((candidate) => candidate.threadId === thread.threadId ? thread : candidate)
|
|
20888
|
+
};
|
|
20889
|
+
}
|
|
20890
|
+
function sameThread(left, right) {
|
|
20891
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
20892
|
+
}
|
|
20893
|
+
|
|
20894
|
+
// ../web/server/scoutbot/runner.ts
|
|
20895
|
+
var defaultLog = {
|
|
20896
|
+
info: (msg) => {
|
|
20897
|
+
console.log(`[scoutbot] ${msg}`);
|
|
20898
|
+
},
|
|
20899
|
+
warn: (msg) => {
|
|
20900
|
+
console.warn(`[scoutbot] ${msg}`);
|
|
20901
|
+
}
|
|
20902
|
+
};
|
|
20903
|
+
async function postScoutbotOperatorMessage(input) {
|
|
20904
|
+
const log2 = input.log ?? defaultLog;
|
|
20905
|
+
const baseUrl = input.brokerBaseUrl ?? resolveScoutBrokerUrl();
|
|
20906
|
+
const threadMap = input.threadMap ?? new ScoutbotThreadMapStore;
|
|
20907
|
+
if (input.bootstrap) {
|
|
20908
|
+
const prepared = await ensureScoutbotBootstrapped({
|
|
20909
|
+
baseUrl,
|
|
20910
|
+
currentDirectory: input.currentDirectory,
|
|
20911
|
+
threadMap,
|
|
20912
|
+
log: log2
|
|
20913
|
+
});
|
|
20914
|
+
if (!prepared) {
|
|
20915
|
+
return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
|
|
20916
|
+
}
|
|
20917
|
+
}
|
|
20918
|
+
const thread = await resolvePostTargetThread({
|
|
20919
|
+
baseUrl,
|
|
20920
|
+
threadMap,
|
|
20921
|
+
threadId: input.threadId
|
|
20922
|
+
});
|
|
20923
|
+
if (!thread) {
|
|
20924
|
+
if (input.threadId?.trim()) {
|
|
20925
|
+
throw new Error(`Unknown scoutbot thread ${input.threadId}`);
|
|
20926
|
+
}
|
|
20927
|
+
return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
|
|
20928
|
+
}
|
|
20929
|
+
if (input.threadId?.trim() && thread.threadId !== input.threadId.trim()) {
|
|
20930
|
+
throw new Error(`Unknown scoutbot thread ${input.threadId}`);
|
|
20931
|
+
}
|
|
20932
|
+
return postOperatorThreadMessage({
|
|
20933
|
+
baseUrl,
|
|
20934
|
+
thread,
|
|
20935
|
+
body: input.body,
|
|
20936
|
+
source: input.source ?? "scout-web",
|
|
20937
|
+
clientMessageId: input.clientMessageId,
|
|
20938
|
+
replyToMessageId: input.replyToMessageId,
|
|
20939
|
+
referenceMessageIds: input.referenceMessageIds,
|
|
20940
|
+
deviceId: input.deviceId,
|
|
20941
|
+
log: log2
|
|
20942
|
+
});
|
|
20943
|
+
}
|
|
20944
|
+
async function resolvePostTargetThread(input) {
|
|
20945
|
+
const explicitThreadId = input.threadId?.trim();
|
|
20946
|
+
if (explicitThreadId) {
|
|
20947
|
+
return input.threadMap.getThread(explicitThreadId);
|
|
20948
|
+
}
|
|
20949
|
+
const threadList = await input.threadMap.list();
|
|
20950
|
+
const existing = threadList.threads.find((candidate) => candidate.threadId === threadList.defaultThreadId) ?? null;
|
|
20951
|
+
if (existing)
|
|
20952
|
+
return existing;
|
|
20953
|
+
const ctx = await loadScoutBrokerContext(input.baseUrl);
|
|
20954
|
+
if (!ctx)
|
|
20955
|
+
return null;
|
|
20956
|
+
return input.threadMap.ensureDefaultThread({
|
|
20957
|
+
snapshot: ctx.snapshot,
|
|
20958
|
+
transportSessionId: null,
|
|
20959
|
+
transport: "codex_app_server"
|
|
20960
|
+
});
|
|
20961
|
+
}
|
|
20962
|
+
async function ensureScoutbotBootstrapped(input) {
|
|
20963
|
+
const ctx = await loadScoutBrokerContext(input.baseUrl);
|
|
20964
|
+
if (!ctx) {
|
|
20965
|
+
input.log.warn(`broker unreachable at ${input.baseUrl}; runner is inert`);
|
|
20966
|
+
return false;
|
|
20967
|
+
}
|
|
20968
|
+
await ensureScoutbotRegistered(input.baseUrl, ctx.snapshot, ctx.node.id, input.currentDirectory, input.log);
|
|
20969
|
+
const refreshed = await loadScoutBrokerContext(input.baseUrl);
|
|
20970
|
+
const snapshot = refreshed?.snapshot ?? ctx.snapshot;
|
|
20971
|
+
const nodeId = refreshed?.node.id ?? ctx.node.id;
|
|
20972
|
+
const endpoint = findScoutbotEndpoint(snapshot, nodeId) ?? buildScoutbotEndpoint(nodeId, input.currentDirectory);
|
|
20973
|
+
const warmedEndpoint = await ensureScoutbotEndpointSession(input.baseUrl, endpoint, input.log);
|
|
20974
|
+
const transportSessionId = scoutbotEndpointTransportSessionId(warmedEndpoint);
|
|
20975
|
+
const thread = await input.threadMap.ensureDefaultThread({
|
|
20976
|
+
snapshot,
|
|
20977
|
+
transportSessionId,
|
|
20978
|
+
transport: endpoint.transport ?? "codex_app_server"
|
|
20979
|
+
});
|
|
20980
|
+
const latest = await loadScoutBrokerContext(input.baseUrl);
|
|
20981
|
+
const conversation = buildScoutbotThreadConversation(thread, latest?.snapshot ?? snapshot, latest?.node.id ?? nodeId);
|
|
20982
|
+
if (conversation) {
|
|
20983
|
+
await postJson(input.baseUrl, "/v1/conversations", conversation);
|
|
20984
|
+
}
|
|
20985
|
+
return true;
|
|
20986
|
+
}
|
|
20987
|
+
async function ensureScoutbotRegistered(baseUrl, snapshot, nodeId, currentDirectory, log2) {
|
|
20988
|
+
const actor = buildScoutbotActor(nodeId);
|
|
20989
|
+
const agent = buildScoutbotAgent(nodeId);
|
|
20990
|
+
const endpoint = normalizeScoutbotEndpoint(findScoutbotEndpoint(snapshot, nodeId), buildScoutbotEndpoint(nodeId, currentDirectory));
|
|
20991
|
+
if (!snapshot.actors?.[SCOUTBOT_AGENT_ID]) {
|
|
20992
|
+
await postJson(baseUrl, "/v1/actors", actor);
|
|
20993
|
+
}
|
|
20994
|
+
const existingAgent = snapshot.agents?.[SCOUTBOT_AGENT_ID];
|
|
20995
|
+
if (!existingAgent || !hasScoutbotLabels(existingAgent) || !hasCurrentScoutbotAgentConfig(existingAgent)) {
|
|
20996
|
+
await postJson(baseUrl, "/v1/agents", agent);
|
|
20997
|
+
}
|
|
20998
|
+
const existingEndpoint = findScoutbotEndpoint(snapshot, nodeId);
|
|
20999
|
+
if (!existingEndpoint || existingEndpoint.state === "offline" || existingEndpoint.metadata?.source !== "scoutbot" || !hasCurrentScoutbotRuntimeConfig(existingEndpoint) || isInvalidScoutbotSessionEndpoint(existingEndpoint)) {
|
|
21000
|
+
await postJson(baseUrl, "/v1/endpoints", endpoint);
|
|
21001
|
+
log2.info(`registered endpoint ${endpoint.id}`);
|
|
21002
|
+
}
|
|
21003
|
+
}
|
|
21004
|
+
function buildScoutbotActor(_nodeId) {
|
|
21005
|
+
return {
|
|
21006
|
+
id: SCOUTBOT_AGENT_ID,
|
|
21007
|
+
kind: "agent",
|
|
21008
|
+
displayName: SCOUTBOT_DISPLAY_NAME,
|
|
21009
|
+
handle: SCOUTBOT_HANDLE,
|
|
21010
|
+
labels: ["assistant", "scout", "scoutbot"],
|
|
21011
|
+
metadata: {
|
|
21012
|
+
source: "scoutbot",
|
|
21013
|
+
role: "operator-assistant"
|
|
21014
|
+
}
|
|
21015
|
+
};
|
|
21016
|
+
}
|
|
21017
|
+
function buildScoutbotAgent(nodeId) {
|
|
21018
|
+
return {
|
|
21019
|
+
...buildScoutbotActor(nodeId),
|
|
21020
|
+
kind: "agent",
|
|
21021
|
+
definitionId: SCOUTBOT_HANDLE,
|
|
21022
|
+
selector: `@${SCOUTBOT_HANDLE}`,
|
|
21023
|
+
defaultSelector: `@${SCOUTBOT_HANDLE}`,
|
|
21024
|
+
agentClass: "operator",
|
|
21025
|
+
capabilities: ["chat", "invoke", "deliver"],
|
|
21026
|
+
wakePolicy: "keep_warm",
|
|
21027
|
+
homeNodeId: nodeId,
|
|
21028
|
+
authorityNodeId: nodeId,
|
|
21029
|
+
advertiseScope: "local",
|
|
21030
|
+
metadata: {
|
|
21031
|
+
source: "scoutbot",
|
|
21032
|
+
role: "operator-assistant",
|
|
21033
|
+
summary: "Operator-facing fleet concierge backed by the broker's local session adapter.",
|
|
21034
|
+
roleConfig: SCOUTBOT_ROLE_CONFIG
|
|
21035
|
+
}
|
|
21036
|
+
};
|
|
21037
|
+
}
|
|
21038
|
+
function buildScoutbotEndpoint(nodeId, currentDirectory) {
|
|
21039
|
+
const reasoningEffort = SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
|
|
21040
|
+
return {
|
|
21041
|
+
id: `${SCOUTBOT_ENDPOINT_ID}.${nodeId}`,
|
|
21042
|
+
agentId: SCOUTBOT_AGENT_ID,
|
|
21043
|
+
nodeId,
|
|
21044
|
+
harness: "codex",
|
|
21045
|
+
transport: "codex_app_server",
|
|
21046
|
+
state: "waiting",
|
|
21047
|
+
cwd: currentDirectory,
|
|
21048
|
+
projectRoot: currentDirectory,
|
|
21049
|
+
metadata: {
|
|
21050
|
+
source: "scoutbot",
|
|
21051
|
+
managedByScout: true,
|
|
21052
|
+
sessionBacked: true,
|
|
21053
|
+
externalSource: "local-session",
|
|
21054
|
+
attachedTransport: "codex_app_server",
|
|
21055
|
+
agentName: SCOUTBOT_AGENT_ID,
|
|
21056
|
+
definitionId: SCOUTBOT_HANDLE,
|
|
21057
|
+
runtimeInstanceId: SCOUTBOT_RUNTIME_INSTANCE_ID,
|
|
21058
|
+
roleConfig: SCOUTBOT_ROLE_CONFIG,
|
|
21059
|
+
systemPrompt: SCOUTBOT_ROLE_CONFIG.systemPrompt,
|
|
21060
|
+
toolGrants: SCOUTBOT_ROLE_CONFIG.grants,
|
|
21061
|
+
reasoningEffort,
|
|
21062
|
+
launchArgs: ["--reasoning-effort", reasoningEffort],
|
|
21063
|
+
projectRoot: currentDirectory,
|
|
21064
|
+
transport: "codex_app_server",
|
|
21065
|
+
startedAt: String(Date.now())
|
|
21066
|
+
}
|
|
21067
|
+
};
|
|
21068
|
+
}
|
|
21069
|
+
function findScoutbotEndpoint(snapshot, nodeId) {
|
|
21070
|
+
const endpoints = Object.values(snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === SCOUTBOT_AGENT_ID && endpoint.transport === "codex_app_server");
|
|
21071
|
+
return endpoints.find((endpoint) => endpoint.nodeId === nodeId && endpoint.state !== "offline") ?? endpoints.find((endpoint) => endpoint.state !== "offline") ?? endpoints[0] ?? null;
|
|
21072
|
+
}
|
|
21073
|
+
function normalizeScoutbotEndpoint(existing, fallback) {
|
|
21074
|
+
if (!existing || isInvalidScoutbotSessionEndpoint(existing))
|
|
21075
|
+
return fallback;
|
|
21076
|
+
const existingMetadata = { ...existing.metadata ?? {} };
|
|
21077
|
+
delete existingMetadata.threadId;
|
|
21078
|
+
delete existingMetadata.externalSessionId;
|
|
21079
|
+
delete existingMetadata.targetSessionId;
|
|
21080
|
+
return {
|
|
21081
|
+
...fallback,
|
|
21082
|
+
id: existing.id || fallback.id,
|
|
21083
|
+
state: existing.state === "offline" ? "waiting" : existing.state,
|
|
21084
|
+
metadata: {
|
|
21085
|
+
...existingMetadata,
|
|
21086
|
+
...fallback.metadata ?? {}
|
|
21087
|
+
}
|
|
21088
|
+
};
|
|
21089
|
+
}
|
|
21090
|
+
function isInvalidScoutbotSessionEndpoint(endpoint) {
|
|
21091
|
+
const lastError = String(endpoint.metadata?.lastError ?? "").toLowerCase();
|
|
21092
|
+
return lastError.includes("no rollout found for thread id") || lastError.includes("codex_app_server session unavailable") || lastError.includes("session unavailable");
|
|
21093
|
+
}
|
|
21094
|
+
function hasCurrentScoutbotRuntimeConfig(endpoint) {
|
|
21095
|
+
return metadataString5(endpoint.metadata, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt && metadataString5(endpoint.metadata, "reasoningEffort") === SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
|
|
21096
|
+
}
|
|
21097
|
+
function scoutbotEndpointTransportSessionId(endpoint) {
|
|
21098
|
+
if (isInvalidScoutbotSessionEndpoint(endpoint))
|
|
21099
|
+
return null;
|
|
21100
|
+
return metadataString5(endpoint.metadata, "threadId") ?? metadataString5(endpoint.metadata, "externalSessionId") ?? endpoint.sessionId?.trim() ?? null;
|
|
21101
|
+
}
|
|
21102
|
+
async function ensureScoutbotEndpointSession(baseUrl, endpoint, log2) {
|
|
21103
|
+
try {
|
|
21104
|
+
const result = await postJson(baseUrl, "/v1/local-sessions/ensure", { agentId: endpoint.agentId, endpointId: endpoint.id }, { timeoutMs: 2000 });
|
|
21105
|
+
return result.endpoint ?? endpoint;
|
|
21106
|
+
} catch (error) {
|
|
21107
|
+
log2.warn(`session warmup skipped: ${describeError(error)}`);
|
|
21108
|
+
return endpoint;
|
|
21109
|
+
}
|
|
21110
|
+
}
|
|
21111
|
+
async function postOperatorThreadMessage(input) {
|
|
21112
|
+
const ctx = await loadScoutBrokerContext(input.baseUrl);
|
|
21113
|
+
if (!ctx) {
|
|
21114
|
+
return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
|
|
21115
|
+
}
|
|
21116
|
+
const conversation = ctx.snapshot.conversations[input.thread.conversationId] ?? buildScoutbotThreadConversation(input.thread, ctx.snapshot, ctx.node.id);
|
|
21117
|
+
if (conversation && !ctx.snapshot.conversations[input.thread.conversationId]) {
|
|
21118
|
+
await postJson(input.baseUrl, "/v1/conversations", conversation);
|
|
21119
|
+
}
|
|
21120
|
+
const parsed = parseScoutbotDirectives(input.body);
|
|
21121
|
+
const now = Date.now();
|
|
21122
|
+
const messageId = createId("msg");
|
|
21123
|
+
const transportSessionId = parsed.directives.targetSessionId ?? input.thread.transportSessionId?.trim() ?? null;
|
|
21124
|
+
await postJson(input.baseUrl, "/v1/messages", {
|
|
21125
|
+
id: messageId,
|
|
21126
|
+
conversationId: input.thread.conversationId,
|
|
21127
|
+
actorId: "operator",
|
|
21128
|
+
originNodeId: ctx.node.id,
|
|
21129
|
+
class: "agent",
|
|
21130
|
+
body: input.body.trim(),
|
|
21131
|
+
replyToMessageId: input.replyToMessageId ?? undefined,
|
|
21132
|
+
mentions: [{ actorId: SCOUTBOT_AGENT_ID, label: "@scoutbot" }],
|
|
21133
|
+
audience: { notify: [SCOUTBOT_AGENT_ID], reason: "direct_message" },
|
|
21134
|
+
visibility: "private",
|
|
21135
|
+
policy: "durable",
|
|
21136
|
+
createdAt: now,
|
|
21137
|
+
metadata: {
|
|
21138
|
+
source: input.source,
|
|
21139
|
+
...scoutbotDirectiveMetadata(parsed),
|
|
21140
|
+
destinationKind: "scoutbot_thread",
|
|
21141
|
+
destinationId: input.thread.threadId,
|
|
21142
|
+
scoutbotThreadId: input.thread.threadId,
|
|
21143
|
+
...transportSessionId ? { targetSessionId: transportSessionId } : {},
|
|
21144
|
+
referenceMessageIds: input.referenceMessageIds ?? [],
|
|
21145
|
+
clientMessageId: input.clientMessageId ?? null,
|
|
21146
|
+
...input.deviceId ? { deviceId: input.deviceId } : {},
|
|
21147
|
+
relayMessageId: messageId,
|
|
21148
|
+
returnAddress: {
|
|
21149
|
+
actorId: "operator",
|
|
21150
|
+
conversationId: input.thread.conversationId,
|
|
21151
|
+
replyToMessageId: messageId,
|
|
21152
|
+
...transportSessionId ? { sessionId: transportSessionId } : {}
|
|
21153
|
+
}
|
|
21154
|
+
}
|
|
21155
|
+
});
|
|
21156
|
+
return {
|
|
21157
|
+
usedBroker: true,
|
|
21158
|
+
threadId: input.thread.threadId,
|
|
21159
|
+
conversationId: input.thread.conversationId,
|
|
21160
|
+
messageId,
|
|
21161
|
+
invokedTargets: [SCOUTBOT_AGENT_ID],
|
|
21162
|
+
unresolvedTargets: []
|
|
21163
|
+
};
|
|
21164
|
+
}
|
|
21165
|
+
var TERMINAL_FLIGHT_STATES2 = new Set(["completed", "failed", "cancelled"]);
|
|
21166
|
+
async function postJson(baseUrl, path2, body, options = {}) {
|
|
21167
|
+
const controller = options.timeoutMs ? new AbortController : null;
|
|
21168
|
+
const timer = controller && options.timeoutMs ? setTimeout(() => controller.abort(), options.timeoutMs) : null;
|
|
21169
|
+
try {
|
|
21170
|
+
const res = await fetch(new URL(path2, baseUrl), {
|
|
21171
|
+
method: "POST",
|
|
21172
|
+
headers: { "content-type": "application/json" },
|
|
21173
|
+
body: JSON.stringify(body),
|
|
21174
|
+
...controller ? { signal: controller.signal } : {}
|
|
21175
|
+
});
|
|
21176
|
+
if (!res.ok) {
|
|
21177
|
+
const text = await res.text().catch(() => "");
|
|
21178
|
+
throw new Error(`POST ${path2} ${res.status}${text ? ` - ${text}` : ""}`);
|
|
21179
|
+
}
|
|
21180
|
+
return await res.json().catch(() => ({ ok: true }));
|
|
21181
|
+
} finally {
|
|
21182
|
+
if (timer)
|
|
21183
|
+
clearTimeout(timer);
|
|
21184
|
+
}
|
|
21185
|
+
}
|
|
21186
|
+
function hasScoutbotLabels(agent) {
|
|
21187
|
+
const labels = new Set(agent.labels ?? []);
|
|
21188
|
+
return labels.has("assistant") && labels.has("scout") && labels.has("scoutbot");
|
|
21189
|
+
}
|
|
21190
|
+
function hasCurrentScoutbotAgentConfig(agent) {
|
|
21191
|
+
const roleConfig = metadataObject(agent.metadata, "roleConfig");
|
|
21192
|
+
return metadataString5(roleConfig, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt;
|
|
21193
|
+
}
|
|
21194
|
+
function metadataObject(metadata, key) {
|
|
21195
|
+
const value = metadata?.[key];
|
|
21196
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
21197
|
+
}
|
|
21198
|
+
function metadataString5(metadata, key) {
|
|
21199
|
+
const value = metadata?.[key];
|
|
21200
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
21201
|
+
}
|
|
21202
|
+
var SCOUTBOT_REASONING_EFFORTS = new Set([
|
|
21203
|
+
"none",
|
|
21204
|
+
"minimal",
|
|
21205
|
+
"low",
|
|
21206
|
+
"medium",
|
|
21207
|
+
"high",
|
|
21208
|
+
"xhigh"
|
|
21209
|
+
]);
|
|
21210
|
+
function createId(prefix) {
|
|
21211
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
21212
|
+
}
|
|
21213
|
+
function describeError(cause) {
|
|
21214
|
+
if (cause instanceof Error)
|
|
21215
|
+
return cause.message;
|
|
21216
|
+
return String(cause);
|
|
21217
|
+
}
|
|
21218
|
+
|
|
19744
21219
|
// ../web/server/core/mobile/service.ts
|
|
19745
21220
|
var DEFAULT_MOBILE_RECENT_TURN_LIMIT = 24;
|
|
19746
21221
|
var DEFAULT_MOBILE_HISTORY_PAGE_LIMIT = 40;
|
|
@@ -19754,10 +21229,31 @@ function normalizeTimestampMs2(value) {
|
|
|
19754
21229
|
return null;
|
|
19755
21230
|
return value > 10000000000 ? Math.floor(value) : Math.floor(value * 1000);
|
|
19756
21231
|
}
|
|
19757
|
-
function
|
|
21232
|
+
function metadataString6(metadata, key) {
|
|
19758
21233
|
const value = metadata?.[key];
|
|
19759
21234
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
19760
21235
|
}
|
|
21236
|
+
function metadataBoolean2(metadata, key) {
|
|
21237
|
+
return metadata?.[key] === true;
|
|
21238
|
+
}
|
|
21239
|
+
function isBrokerRequesterWaitTimeoutStatusMessage(message) {
|
|
21240
|
+
if (message.class !== "status" || metadataString6(message.metadata, "source") !== "broker") {
|
|
21241
|
+
return false;
|
|
21242
|
+
}
|
|
21243
|
+
return message.body.includes("Scout stopped waiting for a synchronous result") || message.body.includes("the requester stopped waiting after");
|
|
21244
|
+
}
|
|
21245
|
+
function isRequesterWaitTimeoutFlight(flight) {
|
|
21246
|
+
return metadataBoolean2(flight.metadata, "requesterTimedOut") || metadataString6(flight.metadata, "timeoutScope") === "requester_wait" || Boolean(flight.summary?.includes("Scout stopped waiting for a synchronous result"));
|
|
21247
|
+
}
|
|
21248
|
+
function isInactiveAgent(agent) {
|
|
21249
|
+
return metadataBoolean2(agent?.metadata, "retiredFromFleet") || metadataBoolean2(agent?.metadata, "staleLocalRegistration");
|
|
21250
|
+
}
|
|
21251
|
+
function isInactiveEndpoint(snapshot, endpoint) {
|
|
21252
|
+
if (!endpoint) {
|
|
21253
|
+
return true;
|
|
21254
|
+
}
|
|
21255
|
+
return metadataBoolean2(endpoint.metadata, "retiredFromFleet") || metadataBoolean2(endpoint.metadata, "staleLocalRegistration") || isInactiveAgent(snapshot.agents[endpoint.agentId]);
|
|
21256
|
+
}
|
|
19761
21257
|
function titleCaseToken(value) {
|
|
19762
21258
|
return value.length === 0 ? value : `${value[0].toUpperCase()}${value.slice(1)}`;
|
|
19763
21259
|
}
|
|
@@ -19812,6 +21308,9 @@ async function loadMobileWorkspaceInventory(currentDirectory) {
|
|
|
19812
21308
|
function latestMessageByConversation(snapshot) {
|
|
19813
21309
|
const buckets = new Map;
|
|
19814
21310
|
for (const message of Object.values(snapshot.messages)) {
|
|
21311
|
+
if (isBrokerRequesterWaitTimeoutStatusMessage(message)) {
|
|
21312
|
+
continue;
|
|
21313
|
+
}
|
|
19815
21314
|
const next = buckets.get(message.conversationId) ?? [];
|
|
19816
21315
|
next.push(message);
|
|
19817
21316
|
buckets.set(message.conversationId, next);
|
|
@@ -19830,14 +21329,14 @@ function agentDisplayName(snapshot, agentId) {
|
|
|
19830
21329
|
return snapshot.agents[agentId]?.displayName ?? snapshot.actors[agentId]?.displayName ?? agentId;
|
|
19831
21330
|
}
|
|
19832
21331
|
function endpointForAgent(snapshot, agentId) {
|
|
19833
|
-
return Object.values(snapshot.endpoints).find((endpoint) => endpoint.agentId === agentId) ?? null;
|
|
21332
|
+
return Object.values(snapshot.endpoints).find((endpoint) => endpoint.agentId === agentId && !isInactiveEndpoint(snapshot, endpoint)) ?? null;
|
|
19834
21333
|
}
|
|
19835
21334
|
function buildMobileAgentSummary(snapshot, agent) {
|
|
19836
21335
|
const endpoint = endpointForAgent(snapshot, agent.id);
|
|
19837
21336
|
const flights = Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agent.id);
|
|
19838
21337
|
const hasWorkingFlight = flights.some((flight) => flight.state === "running");
|
|
19839
21338
|
const lastAuthoredMessageAt = Object.values(snapshot.messages).filter((message) => message.actorId === agent.id).reduce((latest, message) => {
|
|
19840
|
-
const createdAt =
|
|
21339
|
+
const createdAt = normalizeTimestampMs2(message.createdAt);
|
|
19841
21340
|
return typeof createdAt === "number" && (!latest || createdAt > latest) ? createdAt : latest;
|
|
19842
21341
|
}, null);
|
|
19843
21342
|
const state = hasWorkingFlight ? "working" : endpoint && endpoint.state !== "offline" ? "available" : "offline";
|
|
@@ -19862,7 +21361,7 @@ function buildMobileSessionSummaries(snapshot) {
|
|
|
19862
21361
|
const latestMessage = messages2.at(-1) ?? null;
|
|
19863
21362
|
const directAgentId = conversation.kind === "direct" ? conversation.participantIds.find((participantId) => participantId !== "operator") ?? null : null;
|
|
19864
21363
|
const agent = directAgentId ? snapshot.agents[directAgentId] ?? null : null;
|
|
19865
|
-
if (!directAgentId || !agent || messages2.length === 0) {
|
|
21364
|
+
if (!directAgentId || !agent || isInactiveAgent(agent) || messages2.length === 0) {
|
|
19866
21365
|
return [];
|
|
19867
21366
|
}
|
|
19868
21367
|
const endpoint = endpointForAgent(snapshot, directAgentId);
|
|
@@ -19877,10 +21376,10 @@ function buildMobileSessionSummaries(snapshot) {
|
|
|
19877
21376
|
agentId: directAgentId,
|
|
19878
21377
|
agentName: directAgentId ? agentDisplayName(snapshot, directAgentId) : null,
|
|
19879
21378
|
harness: endpoint?.harness ?? null,
|
|
19880
|
-
currentBranch:
|
|
21379
|
+
currentBranch: metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier"),
|
|
19881
21380
|
preview: latestMessage?.body ?? null,
|
|
19882
21381
|
messageCount: messages2.length,
|
|
19883
|
-
lastMessageAt:
|
|
21382
|
+
lastMessageAt: normalizeTimestampMs2(latestMessage?.createdAt),
|
|
19884
21383
|
workspaceRoot: endpoint?.projectRoot ?? endpoint?.cwd ?? null
|
|
19885
21384
|
}];
|
|
19886
21385
|
});
|
|
@@ -19888,7 +21387,7 @@ function buildMobileSessionSummaries(snapshot) {
|
|
|
19888
21387
|
for (const summary of summaries) {
|
|
19889
21388
|
const agent = summary.agentId ? snapshot.agents[summary.agentId] : null;
|
|
19890
21389
|
const endpoint = summary.agentId ? endpointForAgent(snapshot, summary.agentId) : null;
|
|
19891
|
-
const branchQualifier =
|
|
21390
|
+
const branchQualifier = metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier");
|
|
19892
21391
|
const identityKey = [
|
|
19893
21392
|
endpoint?.projectRoot?.trim().toLowerCase(),
|
|
19894
21393
|
endpoint?.cwd?.trim().toLowerCase(),
|
|
@@ -19904,7 +21403,7 @@ function buildMobileSessionSummaries(snapshot) {
|
|
|
19904
21403
|
return [...deduped.values()].sort((left, right) => (right.lastMessageAt ?? 0) - (left.lastMessageAt ?? 0));
|
|
19905
21404
|
}
|
|
19906
21405
|
function messagesForConversation(snapshot, conversationId) {
|
|
19907
|
-
return Object.values(snapshot.messages).filter((message) => message.conversationId === conversationId).sort((left, right) => (normalizeTimestamp2(left.createdAt) ?? 0) - (normalizeTimestamp2(right.createdAt) ?? 0));
|
|
21406
|
+
return Object.values(snapshot.messages).filter((message) => !isBrokerRequesterWaitTimeoutStatusMessage(message)).filter((message) => message.conversationId === conversationId).sort((left, right) => (normalizeTimestamp2(left.createdAt) ?? 0) - (normalizeTimestamp2(right.createdAt) ?? 0));
|
|
19908
21407
|
}
|
|
19909
21408
|
function pageMessagesForConversation(snapshot, conversationId, options = {}) {
|
|
19910
21409
|
const allMessages = messagesForConversation(snapshot, conversationId);
|
|
@@ -19941,7 +21440,7 @@ function pageMessagesForConversation(snapshot, conversationId, options = {}) {
|
|
|
19941
21440
|
function latestActiveFlightForAgent(snapshot, agentId) {
|
|
19942
21441
|
if (!agentId)
|
|
19943
21442
|
return null;
|
|
19944
|
-
return Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agentId &&
|
|
21443
|
+
return Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agentId && (flight.state === "running" || flight.state === "waiting") && !isRequesterWaitTimeoutFlight(flight)).sort((left, right) => (normalizeTimestamp2(right.startedAt) ?? 0) - (normalizeTimestamp2(left.startedAt) ?? 0))[0] ?? null;
|
|
19945
21444
|
}
|
|
19946
21445
|
async function loadMobileRelayState() {
|
|
19947
21446
|
const broker = await loadScoutBrokerContext();
|
|
@@ -19949,7 +21448,10 @@ async function loadMobileRelayState() {
|
|
|
19949
21448
|
return { agents: [], sessions: [] };
|
|
19950
21449
|
}
|
|
19951
21450
|
const snapshot = broker.snapshot;
|
|
19952
|
-
const agents = Object.values(snapshot.agents).
|
|
21451
|
+
const agents = Object.values(snapshot.agents).filter((agent) => !isInactiveAgent(agent)).filter((agent) => {
|
|
21452
|
+
const endpoints = Object.values(snapshot.endpoints).filter((endpoint) => endpoint.agentId === agent.id);
|
|
21453
|
+
return endpoints.length === 0 || endpoints.some((endpoint) => !isInactiveEndpoint(snapshot, endpoint));
|
|
21454
|
+
}).map((agent) => buildMobileAgentSummary(snapshot, agent)).sort((left, right) => (right.lastActiveAt ?? 0) - (left.lastActiveAt ?? 0) || left.title.localeCompare(right.title));
|
|
19953
21455
|
return {
|
|
19954
21456
|
agents,
|
|
19955
21457
|
sessions: buildMobileSessionSummaries(snapshot)
|
|
@@ -20028,10 +21530,10 @@ async function getScoutMobileSessionSnapshot(conversationId, options = {}, curre
|
|
|
20028
21530
|
const messages2 = messagePage.messages;
|
|
20029
21531
|
const activeFlight = latestActiveFlightForAgent(snapshot, directAgentId);
|
|
20030
21532
|
const lastAgentMessageAt = messages2.filter((message) => message.actorId === directAgentId).reduce((latest, message) => {
|
|
20031
|
-
const createdAt =
|
|
21533
|
+
const createdAt = normalizeTimestampMs2(message.createdAt);
|
|
20032
21534
|
return typeof createdAt === "number" && (!latest || createdAt > latest) ? createdAt : latest;
|
|
20033
21535
|
}, null);
|
|
20034
|
-
const shouldShowWorkingTurn = Boolean(activeFlight && (
|
|
21536
|
+
const shouldShowWorkingTurn = Boolean(activeFlight && (normalizeTimestampMs2(activeFlight.startedAt) ?? 0) > (lastAgentMessageAt ?? 0));
|
|
20035
21537
|
const turns = messages2.map((message) => ({
|
|
20036
21538
|
id: message.id,
|
|
20037
21539
|
status: "completed",
|
|
@@ -20086,8 +21588,8 @@ async function getScoutMobileSessionSnapshot(conversationId, options = {}, curre
|
|
|
20086
21588
|
selector: agent?.selector ?? null,
|
|
20087
21589
|
defaultSelector: agent?.defaultSelector ?? null,
|
|
20088
21590
|
project: directAgentId ? agentDisplayName(snapshot, directAgentId) : conversation.title,
|
|
20089
|
-
currentBranch:
|
|
20090
|
-
workspaceQualifier:
|
|
21591
|
+
currentBranch: metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier"),
|
|
21592
|
+
workspaceQualifier: metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "workspaceQualifier")
|
|
20091
21593
|
}
|
|
20092
21594
|
},
|
|
20093
21595
|
history: {
|
|
@@ -20105,7 +21607,7 @@ async function createScoutSession(input, currentDirectory, deviceId) {
|
|
|
20105
21607
|
throw new Error(`Invalid workspaceId.`);
|
|
20106
21608
|
}
|
|
20107
21609
|
const workspaceRoot = resolve7(rawWorkspaceId);
|
|
20108
|
-
const projectName =
|
|
21610
|
+
const projectName = basename10(workspaceRoot) || workspaceRoot;
|
|
20109
21611
|
const workspace = {
|
|
20110
21612
|
id: workspaceRoot,
|
|
20111
21613
|
title: projectName,
|
|
@@ -20180,6 +21682,24 @@ async function createScoutSession(input, currentDirectory, deviceId) {
|
|
|
20180
21682
|
};
|
|
20181
21683
|
}
|
|
20182
21684
|
async function sendScoutMobileMessage(input, currentDirectory, deviceId) {
|
|
21685
|
+
if (input.agentId === SCOUTBOT_AGENT_ID) {
|
|
21686
|
+
const result = await postScoutbotOperatorMessage({
|
|
21687
|
+
currentDirectory: currentDirectory ?? process.cwd(),
|
|
21688
|
+
body: input.body,
|
|
21689
|
+
source: "scout-mobile",
|
|
21690
|
+
clientMessageId: input.clientMessageId,
|
|
21691
|
+
replyToMessageId: input.replyToMessageId,
|
|
21692
|
+
referenceMessageIds: input.referenceMessageIds,
|
|
21693
|
+
deviceId
|
|
21694
|
+
});
|
|
21695
|
+
if (!result.usedBroker || !result.conversationId || !result.messageId) {
|
|
21696
|
+
throw new Error("Scoutbot is not available.");
|
|
21697
|
+
}
|
|
21698
|
+
return {
|
|
21699
|
+
conversationId: result.conversationId,
|
|
21700
|
+
messageId: result.messageId
|
|
21701
|
+
};
|
|
21702
|
+
}
|
|
20183
21703
|
return sendScoutDirectMessage({
|
|
20184
21704
|
agentId: input.agentId,
|
|
20185
21705
|
body: input.body,
|
|
@@ -20216,8 +21736,8 @@ async function deriveNewAgentName(projectName, branch, harness) {
|
|
|
20216
21736
|
}
|
|
20217
21737
|
async function createGitWorktree(projectRoot, agentName, requestedBranch) {
|
|
20218
21738
|
const { execFileSync: execFileSync5 } = await import("child_process");
|
|
20219
|
-
const { join:
|
|
20220
|
-
const { mkdirSync:
|
|
21739
|
+
const { join: join25 } = await import("path");
|
|
21740
|
+
const { mkdirSync: mkdirSync11, existsSync: existsSync19 } = await import("fs");
|
|
20221
21741
|
try {
|
|
20222
21742
|
execFileSync5("git", ["rev-parse", "--git-dir"], { cwd: projectRoot, stdio: "pipe" });
|
|
20223
21743
|
} catch {
|
|
@@ -20225,12 +21745,12 @@ async function createGitWorktree(projectRoot, agentName, requestedBranch) {
|
|
|
20225
21745
|
}
|
|
20226
21746
|
const normalizedRequestedBranch = requestedBranch?.trim();
|
|
20227
21747
|
const branchName = normalizedRequestedBranch || `scout/${agentName}`;
|
|
20228
|
-
const worktreeDir =
|
|
20229
|
-
const worktreePath =
|
|
20230
|
-
if (
|
|
21748
|
+
const worktreeDir = join25(projectRoot, ".scout-worktrees");
|
|
21749
|
+
const worktreePath = join25(worktreeDir, agentName);
|
|
21750
|
+
if (existsSync19(worktreePath)) {
|
|
20231
21751
|
return { path: worktreePath, branch: branchName };
|
|
20232
21752
|
}
|
|
20233
|
-
|
|
21753
|
+
mkdirSync11(worktreeDir, { recursive: true });
|
|
20234
21754
|
try {
|
|
20235
21755
|
execFileSync5("git", ["worktree", "add", "-b", branchName, worktreePath], { cwd: projectRoot, stdio: "pipe" });
|
|
20236
21756
|
return { path: worktreePath, branch: branchName };
|
|
@@ -20431,9 +21951,9 @@ async function handleRPCInner(bridge, req, deviceId) {
|
|
|
20431
21951
|
}
|
|
20432
21952
|
case "session/resume": {
|
|
20433
21953
|
const p = req.params;
|
|
20434
|
-
const sessionFilename =
|
|
21954
|
+
const sessionFilename = basename11(p.sessionPath, ".jsonl");
|
|
20435
21955
|
const parentDir = p.sessionPath.substring(0, p.sessionPath.lastIndexOf("/"));
|
|
20436
|
-
const dirName =
|
|
21956
|
+
const dirName = basename11(parentDir);
|
|
20437
21957
|
let cwd;
|
|
20438
21958
|
if (dirName.startsWith("-")) {
|
|
20439
21959
|
const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
|
|
@@ -20500,7 +22020,7 @@ async function handleRPCInner(bridge, req, deviceId) {
|
|
|
20500
22020
|
return { id: req.id, error: { code: -32000, message: "Workspace target is not a directory" } };
|
|
20501
22021
|
}
|
|
20502
22022
|
const adapterType = p.adapter ?? "claude-code";
|
|
20503
|
-
const name = p.name ??
|
|
22023
|
+
const name = p.name ?? basename11(projectPath);
|
|
20504
22024
|
const session = await bridge.createSession(adapterType, {
|
|
20505
22025
|
name,
|
|
20506
22026
|
cwd: projectPath
|
|
@@ -20622,7 +22142,7 @@ async function handleRPCInner(bridge, req, deviceId) {
|
|
|
20622
22142
|
return { id: req.id, error: { code: -32000, message: "Only .jsonl files can be read" } };
|
|
20623
22143
|
}
|
|
20624
22144
|
try {
|
|
20625
|
-
const content =
|
|
22145
|
+
const content = readFileSync13(p.path, "utf-8");
|
|
20626
22146
|
const lines = content.split(`
|
|
20627
22147
|
`).filter((l) => l.trim().length > 0);
|
|
20628
22148
|
const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
|
|
@@ -20740,13 +22260,13 @@ function resolveMobileCurrentDirectory() {
|
|
|
20740
22260
|
}
|
|
20741
22261
|
}
|
|
20742
22262
|
function resolveWorkspaceRoot(root) {
|
|
20743
|
-
const expandedRoot = root.replace(/^~/,
|
|
22263
|
+
const expandedRoot = root.replace(/^~/, homedir22());
|
|
20744
22264
|
return realpathSync(expandedRoot);
|
|
20745
22265
|
}
|
|
20746
22266
|
function resolveWorkspacePath(root, requestedPath) {
|
|
20747
22267
|
const normalizedRoot = resolveWorkspaceRoot(root);
|
|
20748
|
-
const expandedPath = requestedPath?.replace(/^~/,
|
|
20749
|
-
const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath :
|
|
22268
|
+
const expandedPath = requestedPath?.replace(/^~/, homedir22());
|
|
22269
|
+
const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath : join25(normalizedRoot, expandedPath) : normalizedRoot;
|
|
20750
22270
|
const resolvedCandidate = realpathSync(candidate);
|
|
20751
22271
|
const rel = relative3(normalizedRoot, resolvedCandidate);
|
|
20752
22272
|
if (rel === "" || !rel.startsWith("..") && !isAbsolute3(rel)) {
|
|
@@ -20776,7 +22296,7 @@ function listDirectories(dirPath) {
|
|
|
20776
22296
|
continue;
|
|
20777
22297
|
if (name === "node_modules" || name === ".build" || name === "target")
|
|
20778
22298
|
continue;
|
|
20779
|
-
const fullPath =
|
|
22299
|
+
const fullPath = join25(dirPath, name);
|
|
20780
22300
|
try {
|
|
20781
22301
|
const stat4 = statSync5(fullPath);
|
|
20782
22302
|
if (!stat4.isDirectory())
|
|
@@ -20799,7 +22319,7 @@ function listDirectories(dirPath) {
|
|
|
20799
22319
|
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
20800
22320
|
}
|
|
20801
22321
|
async function discoverSessionFiles(maxAgeDays, limit) {
|
|
20802
|
-
const home =
|
|
22322
|
+
const home = homedir22();
|
|
20803
22323
|
const results = [];
|
|
20804
22324
|
const searchPaths = [
|
|
20805
22325
|
{ pattern: `${home}/.claude/projects`, agent: "claude-code" },
|
|
@@ -35444,7 +36964,7 @@ function planMessageDeliveries(input) {
|
|
|
35444
36964
|
}
|
|
35445
36965
|
return [...deliveries2.values()];
|
|
35446
36966
|
}
|
|
35447
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
36967
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/entity.js
|
|
35448
36968
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
35449
36969
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
35450
36970
|
function is(value, type) {
|
|
@@ -35469,7 +36989,7 @@ function is(value, type) {
|
|
|
35469
36989
|
return false;
|
|
35470
36990
|
}
|
|
35471
36991
|
|
|
35472
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
36992
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/column.js
|
|
35473
36993
|
class Column {
|
|
35474
36994
|
constructor(table, config2) {
|
|
35475
36995
|
this.table = table;
|
|
@@ -35519,7 +37039,7 @@ class Column {
|
|
|
35519
37039
|
}
|
|
35520
37040
|
}
|
|
35521
37041
|
|
|
35522
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37042
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/column-builder.js
|
|
35523
37043
|
class ColumnBuilder {
|
|
35524
37044
|
static [entityKind] = "ColumnBuilder";
|
|
35525
37045
|
config;
|
|
@@ -35575,20 +37095,20 @@ class ColumnBuilder {
|
|
|
35575
37095
|
}
|
|
35576
37096
|
}
|
|
35577
37097
|
|
|
35578
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37098
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/table.utils.js
|
|
35579
37099
|
var TableName = Symbol.for("drizzle:Name");
|
|
35580
37100
|
|
|
35581
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37101
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/tracing-utils.js
|
|
35582
37102
|
function iife(fn, ...args) {
|
|
35583
37103
|
return fn(...args);
|
|
35584
37104
|
}
|
|
35585
37105
|
|
|
35586
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37106
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/unique-constraint.js
|
|
35587
37107
|
function uniqueKeyName(table, columns) {
|
|
35588
37108
|
return `${table[TableName]}_${columns.join("_")}_unique`;
|
|
35589
37109
|
}
|
|
35590
37110
|
|
|
35591
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37111
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/columns/common.js
|
|
35592
37112
|
class PgColumn extends Column {
|
|
35593
37113
|
constructor(table, config2) {
|
|
35594
37114
|
if (!config2.uniqueName) {
|
|
@@ -35637,7 +37157,7 @@ class ExtraConfigColumn extends PgColumn {
|
|
|
35637
37157
|
}
|
|
35638
37158
|
}
|
|
35639
37159
|
|
|
35640
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37160
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/columns/enum.js
|
|
35641
37161
|
class PgEnumObjectColumn extends PgColumn {
|
|
35642
37162
|
static [entityKind] = "PgEnumObjectColumn";
|
|
35643
37163
|
enum;
|
|
@@ -35667,7 +37187,7 @@ class PgEnumColumn extends PgColumn {
|
|
|
35667
37187
|
}
|
|
35668
37188
|
}
|
|
35669
37189
|
|
|
35670
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37190
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/subquery.js
|
|
35671
37191
|
class Subquery {
|
|
35672
37192
|
static [entityKind] = "Subquery";
|
|
35673
37193
|
constructor(sql, fields, alias, isWith = false, usedTables = []) {
|
|
@@ -35682,10 +37202,10 @@ class Subquery {
|
|
|
35682
37202
|
}
|
|
35683
37203
|
}
|
|
35684
37204
|
|
|
35685
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37205
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/version.js
|
|
35686
37206
|
var version2 = "0.45.2";
|
|
35687
37207
|
|
|
35688
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37208
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/tracing.js
|
|
35689
37209
|
var otel;
|
|
35690
37210
|
var rawTracer;
|
|
35691
37211
|
var tracer = {
|
|
@@ -35712,10 +37232,10 @@ var tracer = {
|
|
|
35712
37232
|
}
|
|
35713
37233
|
};
|
|
35714
37234
|
|
|
35715
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37235
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/view-common.js
|
|
35716
37236
|
var ViewBaseConfig = Symbol.for("drizzle:ViewBaseConfig");
|
|
35717
37237
|
|
|
35718
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37238
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/table.js
|
|
35719
37239
|
var Schema = Symbol.for("drizzle:Schema");
|
|
35720
37240
|
var Columns = Symbol.for("drizzle:Columns");
|
|
35721
37241
|
var ExtraConfigColumns = Symbol.for("drizzle:ExtraConfigColumns");
|
|
@@ -35753,7 +37273,7 @@ class Table {
|
|
|
35753
37273
|
}
|
|
35754
37274
|
}
|
|
35755
37275
|
|
|
35756
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37276
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sql/sql.js
|
|
35757
37277
|
function isSQLWrapper(value) {
|
|
35758
37278
|
return value !== null && value !== undefined && typeof value.getSQL === "function";
|
|
35759
37279
|
}
|
|
@@ -36030,7 +37550,7 @@ function sql(strings, ...params) {
|
|
|
36030
37550
|
return new SQL([new StringChunk(str)]);
|
|
36031
37551
|
}
|
|
36032
37552
|
sql2.raw = raw;
|
|
36033
|
-
function
|
|
37553
|
+
function join26(chunks, separator) {
|
|
36034
37554
|
const result = [];
|
|
36035
37555
|
for (const [i, chunk] of chunks.entries()) {
|
|
36036
37556
|
if (i > 0 && separator !== undefined) {
|
|
@@ -36040,7 +37560,7 @@ function sql(strings, ...params) {
|
|
|
36040
37560
|
}
|
|
36041
37561
|
return new SQL(result);
|
|
36042
37562
|
}
|
|
36043
|
-
sql2.join =
|
|
37563
|
+
sql2.join = join26;
|
|
36044
37564
|
function identifier(value) {
|
|
36045
37565
|
return new Name(value);
|
|
36046
37566
|
}
|
|
@@ -36113,7 +37633,7 @@ Subquery.prototype.getSQL = function() {
|
|
|
36113
37633
|
return new SQL([this]);
|
|
36114
37634
|
};
|
|
36115
37635
|
|
|
36116
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37636
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/utils.js
|
|
36117
37637
|
function getColumnNameAndConfig(a, b) {
|
|
36118
37638
|
return {
|
|
36119
37639
|
name: typeof a === "string" && a.length > 0 ? a : "",
|
|
@@ -36122,7 +37642,7 @@ function getColumnNameAndConfig(a, b) {
|
|
|
36122
37642
|
}
|
|
36123
37643
|
var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder;
|
|
36124
37644
|
|
|
36125
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37645
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
|
|
36126
37646
|
class ForeignKeyBuilder {
|
|
36127
37647
|
static [entityKind] = "SQLiteForeignKeyBuilder";
|
|
36128
37648
|
reference;
|
|
@@ -36176,12 +37696,12 @@ class ForeignKey {
|
|
|
36176
37696
|
}
|
|
36177
37697
|
}
|
|
36178
37698
|
|
|
36179
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37699
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
|
|
36180
37700
|
function uniqueKeyName2(table, columns) {
|
|
36181
37701
|
return `${table[TableName]}_${columns.join("_")}_unique`;
|
|
36182
37702
|
}
|
|
36183
37703
|
|
|
36184
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37704
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/common.js
|
|
36185
37705
|
class SQLiteColumnBuilder extends ColumnBuilder {
|
|
36186
37706
|
static [entityKind] = "SQLiteColumnBuilder";
|
|
36187
37707
|
foreignKeyConfigs = [];
|
|
@@ -36232,7 +37752,7 @@ class SQLiteColumn extends Column {
|
|
|
36232
37752
|
static [entityKind] = "SQLiteColumn";
|
|
36233
37753
|
}
|
|
36234
37754
|
|
|
36235
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37755
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/blob.js
|
|
36236
37756
|
class SQLiteBigIntBuilder extends SQLiteColumnBuilder {
|
|
36237
37757
|
static [entityKind] = "SQLiteBigIntBuilder";
|
|
36238
37758
|
constructor(name) {
|
|
@@ -36320,7 +37840,7 @@ function blob(a, b) {
|
|
|
36320
37840
|
return new SQLiteBlobBufferBuilder(name);
|
|
36321
37841
|
}
|
|
36322
37842
|
|
|
36323
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37843
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/custom.js
|
|
36324
37844
|
class SQLiteCustomColumnBuilder extends SQLiteColumnBuilder {
|
|
36325
37845
|
static [entityKind] = "SQLiteCustomColumnBuilder";
|
|
36326
37846
|
constructor(name, fieldConfig, customTypeParams) {
|
|
@@ -36361,7 +37881,7 @@ function customType(customTypeParams) {
|
|
|
36361
37881
|
};
|
|
36362
37882
|
}
|
|
36363
37883
|
|
|
36364
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37884
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/integer.js
|
|
36365
37885
|
class SQLiteBaseIntegerBuilder extends SQLiteColumnBuilder {
|
|
36366
37886
|
static [entityKind] = "SQLiteBaseIntegerBuilder";
|
|
36367
37887
|
constructor(name, dataType, columnType) {
|
|
@@ -36463,7 +37983,7 @@ function integer2(a, b) {
|
|
|
36463
37983
|
return new SQLiteIntegerBuilder(name);
|
|
36464
37984
|
}
|
|
36465
37985
|
|
|
36466
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
37986
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
|
|
36467
37987
|
class SQLiteNumericBuilder extends SQLiteColumnBuilder {
|
|
36468
37988
|
static [entityKind] = "SQLiteNumericBuilder";
|
|
36469
37989
|
constructor(name) {
|
|
@@ -36533,7 +38053,7 @@ function numeric(a, b) {
|
|
|
36533
38053
|
return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
|
|
36534
38054
|
}
|
|
36535
38055
|
|
|
36536
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
38056
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/real.js
|
|
36537
38057
|
class SQLiteRealBuilder extends SQLiteColumnBuilder {
|
|
36538
38058
|
static [entityKind] = "SQLiteRealBuilder";
|
|
36539
38059
|
constructor(name) {
|
|
@@ -36554,7 +38074,7 @@ function real(name) {
|
|
|
36554
38074
|
return new SQLiteRealBuilder(name ?? "");
|
|
36555
38075
|
}
|
|
36556
38076
|
|
|
36557
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
38077
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/text.js
|
|
36558
38078
|
class SQLiteTextBuilder extends SQLiteColumnBuilder {
|
|
36559
38079
|
static [entityKind] = "SQLiteTextBuilder";
|
|
36560
38080
|
constructor(name, config2) {
|
|
@@ -36609,7 +38129,7 @@ function text(a, b = {}) {
|
|
|
36609
38129
|
return new SQLiteTextBuilder(name, config2);
|
|
36610
38130
|
}
|
|
36611
38131
|
|
|
36612
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
38132
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/all.js
|
|
36613
38133
|
function getSQLiteColumnBuilders() {
|
|
36614
38134
|
return {
|
|
36615
38135
|
blob,
|
|
@@ -36621,7 +38141,7 @@ function getSQLiteColumnBuilders() {
|
|
|
36621
38141
|
};
|
|
36622
38142
|
}
|
|
36623
38143
|
|
|
36624
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
38144
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/table.js
|
|
36625
38145
|
var InlineForeignKeys = Symbol.for("drizzle:SQLiteInlineForeignKeys");
|
|
36626
38146
|
|
|
36627
38147
|
class SQLiteTable extends Table {
|
|
@@ -36655,7 +38175,7 @@ var sqliteTable = (name, columns, extraConfig) => {
|
|
|
36655
38175
|
return sqliteTableBase(name, columns, extraConfig);
|
|
36656
38176
|
};
|
|
36657
38177
|
|
|
36658
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.2+
|
|
38178
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/indexes.js
|
|
36659
38179
|
class IndexBuilderOn {
|
|
36660
38180
|
constructor(name, unique2) {
|
|
36661
38181
|
this.name = name;
|
|
@@ -36699,6 +38219,7 @@ function index(name) {
|
|
|
36699
38219
|
}
|
|
36700
38220
|
|
|
36701
38221
|
// ../runtime/src/drizzle-schema.ts
|
|
38222
|
+
var epochMsNow = sql`(CAST(strftime('%s','now') AS INTEGER) * 1000)`;
|
|
36702
38223
|
var deliveriesTable = sqliteTable("deliveries", {
|
|
36703
38224
|
id: text("id").primaryKey(),
|
|
36704
38225
|
messageId: text("message_id"),
|
|
@@ -36714,7 +38235,7 @@ var deliveriesTable = sqliteTable("deliveries", {
|
|
|
36714
38235
|
leaseOwner: text("lease_owner"),
|
|
36715
38236
|
leaseExpiresAt: integer2("lease_expires_at"),
|
|
36716
38237
|
metadataJson: text("metadata_json"),
|
|
36717
|
-
createdAt: integer2("created_at").notNull().default(
|
|
38238
|
+
createdAt: integer2("created_at").notNull().default(epochMsNow)
|
|
36718
38239
|
}, (table) => [
|
|
36719
38240
|
index("idx_deliveries_status_transport").on(table.status, table.transport)
|
|
36720
38241
|
]);
|
|
@@ -36741,7 +38262,7 @@ var briefingsTable = sqliteTable("briefings", {
|
|
|
36741
38262
|
snapshotJson: text("snapshot_json").notNull(),
|
|
36742
38263
|
callJson: text("call_json").notNull(),
|
|
36743
38264
|
markdown: text("markdown"),
|
|
36744
|
-
createdAt: integer2("created_at").notNull().default(
|
|
38265
|
+
createdAt: integer2("created_at").notNull().default(epochMsNow)
|
|
36745
38266
|
}, (table) => [
|
|
36746
38267
|
index("idx_briefings_created_at").on(table.createdAt),
|
|
36747
38268
|
index("idx_briefings_kind_created_at").on(table.kind, table.createdAt)
|
|
@@ -37442,6 +38963,11 @@ init_support_paths();
|
|
|
37442
38963
|
// ../runtime/src/index.ts
|
|
37443
38964
|
init_broker_process_manager();
|
|
37444
38965
|
init_broker_api();
|
|
38966
|
+
|
|
38967
|
+
// ../runtime/src/invocation-lifecycle-read-model.ts
|
|
38968
|
+
init_src2();
|
|
38969
|
+
|
|
38970
|
+
// ../runtime/src/index.ts
|
|
37445
38971
|
init_local_agents();
|
|
37446
38972
|
|
|
37447
38973
|
// ../runtime/src/scout-broker.ts
|
|
@@ -37456,6 +38982,7 @@ init_broker_process_manager();
|
|
|
37456
38982
|
init_codex_app_server();
|
|
37457
38983
|
init_setup();
|
|
37458
38984
|
init_support_paths();
|
|
38985
|
+
init_user_config();
|
|
37459
38986
|
|
|
37460
38987
|
// ../runtime/src/thread-events.ts
|
|
37461
38988
|
class ThreadWatchProtocolError extends Error {
|
|
@@ -37692,10 +39219,10 @@ class ThreadEventPlane {
|
|
|
37692
39219
|
// ../runtime/src/mobile-push.ts
|
|
37693
39220
|
init_support_paths();
|
|
37694
39221
|
import { Database as Database2 } from "bun:sqlite";
|
|
37695
|
-
import { mkdirSync as
|
|
39222
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync14 } from "fs";
|
|
37696
39223
|
import { connect as connectHttp2 } from "http2";
|
|
37697
39224
|
import { createPrivateKey, sign as signWithKey } from "crypto";
|
|
37698
|
-
import { dirname as
|
|
39225
|
+
import { dirname as dirname13, join as join26 } from "path";
|
|
37699
39226
|
var MOBILE_PUSH_SCHEMA = `
|
|
37700
39227
|
CREATE TABLE IF NOT EXISTS mobile_push_registrations (
|
|
37701
39228
|
id TEXT PRIMARY KEY,
|
|
@@ -37723,7 +39250,7 @@ var dbHandle = null;
|
|
|
37723
39250
|
var dbPath = null;
|
|
37724
39251
|
var cachedApnsJwt = null;
|
|
37725
39252
|
function resolveControlPlaneDbPath() {
|
|
37726
|
-
return
|
|
39253
|
+
return join26(resolveOpenScoutSupportPaths().controlHome, "control-plane.sqlite");
|
|
37727
39254
|
}
|
|
37728
39255
|
function ensureMobilePushSchema(database) {
|
|
37729
39256
|
database.exec("PRAGMA busy_timeout = 5000;");
|
|
@@ -37737,7 +39264,7 @@ function writeDb() {
|
|
|
37737
39264
|
dbHandle = null;
|
|
37738
39265
|
}
|
|
37739
39266
|
if (!dbHandle) {
|
|
37740
|
-
|
|
39267
|
+
mkdirSync11(dirname13(nextPath), { recursive: true });
|
|
37741
39268
|
dbHandle = new Database2(nextPath, { create: true });
|
|
37742
39269
|
dbPath = nextPath;
|
|
37743
39270
|
ensureMobilePushSchema(dbHandle);
|
|
@@ -38029,7 +39556,7 @@ function loadApnsCredentials() {
|
|
|
38029
39556
|
privateKeyPem = Buffer.from(inlineBase64, "base64").toString("utf8");
|
|
38030
39557
|
}
|
|
38031
39558
|
if (!privateKeyPem && path2) {
|
|
38032
|
-
privateKeyPem =
|
|
39559
|
+
privateKeyPem = readFileSync14(path2, "utf8");
|
|
38033
39560
|
}
|
|
38034
39561
|
if (!teamId || !keyId || !privateKeyPem) {
|
|
38035
39562
|
return null;
|
|
@@ -38520,8 +40047,8 @@ function candidateFromValue(key, value, root, now) {
|
|
|
38520
40047
|
title: firstMetadataString(root, "title", "attentionTitle"),
|
|
38521
40048
|
summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
|
|
38522
40049
|
detail: firstMetadataString(root, "detail", "statusDetail"),
|
|
38523
|
-
turnId:
|
|
38524
|
-
blockId:
|
|
40050
|
+
turnId: metadataString7(root, "turnId"),
|
|
40051
|
+
blockId: metadataString7(root, "blockId"),
|
|
38525
40052
|
version: metadataNumber(root, "version"),
|
|
38526
40053
|
updatedAt: metadataTimestamp(root, now),
|
|
38527
40054
|
severity: nativeSeverity(root)
|
|
@@ -38540,8 +40067,8 @@ function candidateFromValue(key, value, root, now) {
|
|
|
38540
40067
|
title: firstMetadataString(root, "title", "attentionTitle"),
|
|
38541
40068
|
summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
|
|
38542
40069
|
detail: firstMetadataString(root, "detail", "statusDetail"),
|
|
38543
|
-
turnId:
|
|
38544
|
-
blockId:
|
|
40070
|
+
turnId: metadataString7(root, "turnId"),
|
|
40071
|
+
blockId: metadataString7(root, "blockId"),
|
|
38545
40072
|
version: metadataNumber(root, "version"),
|
|
38546
40073
|
updatedAt: metadataTimestamp(root, now),
|
|
38547
40074
|
severity: nativeSeverity(root)
|
|
@@ -38570,8 +40097,8 @@ function candidateFromRecord(key, record2, now, keyImpliesAttention) {
|
|
|
38570
40097
|
title: firstMetadataString(record2, "title", "label"),
|
|
38571
40098
|
summary: firstMetadataString(record2, "summary", "message", "reason", "description"),
|
|
38572
40099
|
detail: firstMetadataString(record2, "detail", "statusDetail", "hint"),
|
|
38573
|
-
turnId:
|
|
38574
|
-
blockId:
|
|
40100
|
+
turnId: metadataString7(record2, "turnId"),
|
|
40101
|
+
blockId: metadataString7(record2, "blockId"),
|
|
38575
40102
|
version: metadataNumber(record2, "version"),
|
|
38576
40103
|
updatedAt: metadataTimestamp(record2, now),
|
|
38577
40104
|
severity: nativeSeverity(record2)
|
|
@@ -38591,7 +40118,7 @@ function stableNativeIdPart(value) {
|
|
|
38591
40118
|
return stable || "blocked";
|
|
38592
40119
|
}
|
|
38593
40120
|
function nativeSeverity(record2) {
|
|
38594
|
-
const severity =
|
|
40121
|
+
const severity = metadataString7(record2, "severity")?.toLowerCase();
|
|
38595
40122
|
if (severity === "critical" || severity === "warning" || severity === "info") {
|
|
38596
40123
|
return severity;
|
|
38597
40124
|
}
|
|
@@ -38600,7 +40127,7 @@ function nativeSeverity(record2) {
|
|
|
38600
40127
|
function metadataRecord3(value) {
|
|
38601
40128
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
38602
40129
|
}
|
|
38603
|
-
function
|
|
40130
|
+
function metadataString7(record2, key) {
|
|
38604
40131
|
const value = record2?.[key];
|
|
38605
40132
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
38606
40133
|
}
|
|
@@ -38621,7 +40148,7 @@ function metadataTimestamp(record2, fallback) {
|
|
|
38621
40148
|
}
|
|
38622
40149
|
function firstMetadataString(record2, ...keys) {
|
|
38623
40150
|
for (const key of keys) {
|
|
38624
|
-
const value =
|
|
40151
|
+
const value = metadataString7(record2, key);
|
|
38625
40152
|
if (value) {
|
|
38626
40153
|
return value;
|
|
38627
40154
|
}
|
|
@@ -38639,10 +40166,10 @@ function compactAttentionSummary(value, max = 220) {
|
|
|
38639
40166
|
init_src2();
|
|
38640
40167
|
// ../web/server/core/pairing/runtime/bridge/router.ts
|
|
38641
40168
|
init_local_agents();
|
|
38642
|
-
import { readFileSync as
|
|
40169
|
+
import { readFileSync as readFileSync15, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync6 } from "fs";
|
|
38643
40170
|
import { execSync as execSync4 } from "child_process";
|
|
38644
|
-
import { basename as
|
|
38645
|
-
import { homedir as
|
|
40171
|
+
import { basename as basename12, isAbsolute as isAbsolute4, join as join27, relative as relative4 } from "path";
|
|
40172
|
+
import { homedir as homedir23 } from "os";
|
|
38646
40173
|
var t = initTRPC.context().create();
|
|
38647
40174
|
var logged = t.middleware(async ({ path: path2, type, next }) => {
|
|
38648
40175
|
const start = Date.now();
|
|
@@ -38669,13 +40196,13 @@ function resolveMobileCurrentDirectory2() {
|
|
|
38669
40196
|
}
|
|
38670
40197
|
}
|
|
38671
40198
|
function resolveWorkspaceRoot2(root) {
|
|
38672
|
-
const expandedRoot = root.replace(/^~/,
|
|
40199
|
+
const expandedRoot = root.replace(/^~/, homedir23());
|
|
38673
40200
|
return realpathSync2(expandedRoot);
|
|
38674
40201
|
}
|
|
38675
40202
|
function resolveWorkspacePath2(root, requestedPath) {
|
|
38676
40203
|
const normalizedRoot = resolveWorkspaceRoot2(root);
|
|
38677
|
-
const expandedPath = requestedPath?.replace(/^~/,
|
|
38678
|
-
const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath :
|
|
40204
|
+
const expandedPath = requestedPath?.replace(/^~/, homedir23());
|
|
40205
|
+
const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath : join27(normalizedRoot, expandedPath) : normalizedRoot;
|
|
38679
40206
|
const resolvedCandidate = realpathSync2(candidate);
|
|
38680
40207
|
const rel = relative4(normalizedRoot, resolvedCandidate);
|
|
38681
40208
|
if (rel === "" || !rel.startsWith("..") && !isAbsolute4(rel)) {
|
|
@@ -38705,7 +40232,7 @@ function listDirectories2(dirPath) {
|
|
|
38705
40232
|
continue;
|
|
38706
40233
|
if (name === "node_modules" || name === ".build" || name === "target")
|
|
38707
40234
|
continue;
|
|
38708
|
-
const fullPath =
|
|
40235
|
+
const fullPath = join27(dirPath, name);
|
|
38709
40236
|
try {
|
|
38710
40237
|
const stat4 = statSync6(fullPath);
|
|
38711
40238
|
if (!stat4.isDirectory())
|
|
@@ -38744,7 +40271,7 @@ function detectAgent2(filePath) {
|
|
|
38744
40271
|
return "unknown";
|
|
38745
40272
|
}
|
|
38746
40273
|
async function discoverSessionFiles2(maxAgeDays, limit) {
|
|
38747
|
-
const home =
|
|
40274
|
+
const home = homedir23();
|
|
38748
40275
|
const results = [];
|
|
38749
40276
|
const searchPaths = [
|
|
38750
40277
|
{ pattern: `${home}/.claude/projects`, agent: "claude-code" },
|
|
@@ -39038,9 +40565,9 @@ var sessionRouter = t.router({
|
|
|
39038
40565
|
adapterType: exports_external.string().optional(),
|
|
39039
40566
|
name: exports_external.string().optional()
|
|
39040
40567
|
})).mutation(async ({ input, ctx }) => {
|
|
39041
|
-
const sessionFilename =
|
|
40568
|
+
const sessionFilename = basename12(input.sessionPath, ".jsonl");
|
|
39042
40569
|
const parentDir = input.sessionPath.substring(0, input.sessionPath.lastIndexOf("/"));
|
|
39043
|
-
const dirName =
|
|
40570
|
+
const dirName = basename12(parentDir);
|
|
39044
40571
|
let cwd;
|
|
39045
40572
|
if (dirName.startsWith("-")) {
|
|
39046
40573
|
const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
|
|
@@ -39340,7 +40867,7 @@ var workspaceRouter = t.router({
|
|
|
39340
40867
|
});
|
|
39341
40868
|
}
|
|
39342
40869
|
const adapterType = input.adapter ?? "claude-code";
|
|
39343
|
-
const name = input.name ??
|
|
40870
|
+
const name = input.name ?? basename12(projectPath);
|
|
39344
40871
|
return ctx.bridge.createSession(adapterType, {
|
|
39345
40872
|
name,
|
|
39346
40873
|
cwd: projectPath
|
|
@@ -39409,7 +40936,7 @@ var historyRouter = t.router({
|
|
|
39409
40936
|
});
|
|
39410
40937
|
}
|
|
39411
40938
|
try {
|
|
39412
|
-
const content =
|
|
40939
|
+
const content = readFileSync15(input.path, "utf-8");
|
|
39413
40940
|
const lines = content.split(`
|
|
39414
40941
|
`).filter((l) => l.trim().length > 0);
|
|
39415
40942
|
const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
|
|
@@ -39442,7 +40969,7 @@ var historyRouter = t.router({
|
|
|
39442
40969
|
}
|
|
39443
40970
|
try {
|
|
39444
40971
|
const fileStat = statSync6(input.path);
|
|
39445
|
-
const content =
|
|
40972
|
+
const content = readFileSync15(input.path, "utf-8");
|
|
39446
40973
|
const replay = createHistorySessionSnapshot({
|
|
39447
40974
|
path: input.path,
|
|
39448
40975
|
content,
|
|
@@ -43539,14 +45066,14 @@ function parseTRPCMessage(obj, transformer) {
|
|
|
43539
45066
|
}
|
|
43540
45067
|
// ../web/server/core/pairing/runtime/bridge/server-trpc.ts
|
|
43541
45068
|
import { realpathSync as realpathSync3 } from "fs";
|
|
43542
|
-
import { homedir as
|
|
45069
|
+
import { homedir as homedir24 } from "os";
|
|
43543
45070
|
function resolveCurrentDirectory() {
|
|
43544
45071
|
try {
|
|
43545
45072
|
const config2 = resolveConfig();
|
|
43546
45073
|
const configuredRoot = config2.workspace?.root;
|
|
43547
45074
|
if (!configuredRoot)
|
|
43548
45075
|
return process.cwd();
|
|
43549
|
-
const expanded = configuredRoot.replace(/^~/,
|
|
45076
|
+
const expanded = configuredRoot.replace(/^~/, homedir24());
|
|
43550
45077
|
return realpathSync3(expanded);
|
|
43551
45078
|
} catch {
|
|
43552
45079
|
return process.cwd();
|
|
@@ -44066,7 +45593,7 @@ async function autoStartConfiguredSessions(bridge, sessions3) {
|
|
|
44066
45593
|
try {
|
|
44067
45594
|
const session = await bridge.createSession(entry.adapter, {
|
|
44068
45595
|
name: entry.name,
|
|
44069
|
-
cwd: entry.cwd?.replace(/^~/,
|
|
45596
|
+
cwd: entry.cwd?.replace(/^~/, homedir25()),
|
|
44070
45597
|
options: entry.options
|
|
44071
45598
|
});
|
|
44072
45599
|
console.log(`[bridge] session started: ${session.name} (${entry.adapter})`);
|
|
@@ -44156,6 +45683,7 @@ async function startSupervisorRuntime(state) {
|
|
|
44156
45683
|
state.bonjour = managedRelay ? startBonjourRelayAdvertisement({
|
|
44157
45684
|
port: relayPort,
|
|
44158
45685
|
relayUrl: activeRelayUrl,
|
|
45686
|
+
fallbackRelayUrls: managedRelay.fallbackRelayUrls,
|
|
44159
45687
|
publicKeyHex
|
|
44160
45688
|
}) : null;
|
|
44161
45689
|
state.runtime = await startPairingRuntime({
|
|
@@ -44306,6 +45834,10 @@ function startBonjourRelayAdvertisement(input) {
|
|
|
44306
45834
|
`fp=${fingerprint}`,
|
|
44307
45835
|
`scheme=${scheme}`
|
|
44308
45836
|
];
|
|
45837
|
+
const fallbackRelayUrls = normalizedBonjourFallbackRelayUrls(input.fallbackRelayUrls);
|
|
45838
|
+
if (fallbackRelayUrls.length > 0) {
|
|
45839
|
+
args.push(`fallbackRelays=${fallbackRelayUrls.join("|")}`);
|
|
45840
|
+
}
|
|
44309
45841
|
let processRef = null;
|
|
44310
45842
|
try {
|
|
44311
45843
|
processRef = spawn4("/usr/bin/dns-sd", args, { stdio: "ignore" });
|
|
@@ -44330,6 +45862,19 @@ function startBonjourRelayAdvertisement(input) {
|
|
|
44330
45862
|
}
|
|
44331
45863
|
};
|
|
44332
45864
|
}
|
|
45865
|
+
function normalizedBonjourFallbackRelayUrls(relayUrls) {
|
|
45866
|
+
const seen = new Set;
|
|
45867
|
+
const result = [];
|
|
45868
|
+
for (const relayUrl of relayUrls ?? []) {
|
|
45869
|
+
const trimmed = relayUrl.trim();
|
|
45870
|
+
if (!trimmed || seen.has(trimmed)) {
|
|
45871
|
+
continue;
|
|
45872
|
+
}
|
|
45873
|
+
seen.add(trimmed);
|
|
45874
|
+
result.push(trimmed);
|
|
45875
|
+
}
|
|
45876
|
+
return result;
|
|
45877
|
+
}
|
|
44333
45878
|
function relayScheme(relayUrl) {
|
|
44334
45879
|
try {
|
|
44335
45880
|
const protocol = new URL(relayUrl).protocol;
|