@agentproto/runtime 0.8.0 → 1.0.0
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/dist/index.d.ts +1038 -95
- package/dist/index.mjs +2014 -338
- package/dist/index.mjs.map +1 -1
- package/dist/resume-strategies.mjs +42 -13
- package/dist/resume-strategies.mjs.map +1 -1
- package/package.json +14 -12
package/dist/index.mjs
CHANGED
|
@@ -12,10 +12,12 @@ import { getModelProvider, resolvePricing } from '@agentproto/model-catalog/llm'
|
|
|
12
12
|
import * as providers_store_star from '@agentproto/providers-store';
|
|
13
13
|
import { makeAdapterLister, makeAdapterResolver, makeCredsStore, discoverAdapterPackages, makeSetupLedger, makeListTool, makeSetupTool } from '@agentproto/provider-kit';
|
|
14
14
|
import { SandboxSpecSchema, resolveLifecyclePolicy, createSandboxAgentSessionHost } from '@agentproto/sandbox';
|
|
15
|
+
import { eligibleProfiles, getAuthProfile, KeychainStore } from '@agentproto/auth';
|
|
15
16
|
import matter from 'gray-matter';
|
|
16
17
|
import { createServer } from 'http';
|
|
17
18
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
18
19
|
import { WebSocketServer } from 'ws';
|
|
20
|
+
import { resolveLlmModelRoute, formatModelRef, tryParseModelRef } from '@agentproto/model-catalog/route-identity';
|
|
19
21
|
import { anthropicGatewayPresetList } from '@agentproto/provider-presets';
|
|
20
22
|
import { createIngestionClient } from '@agentproto/telemetry-langfuse';
|
|
21
23
|
import { resolveRedactor } from '@agentproto/redaction';
|
|
@@ -33,6 +35,7 @@ import { createServer as createServer$1 } from 'net';
|
|
|
33
35
|
import { daemonHandshakeOverSink } from '@agentproto/acp/tunnel';
|
|
34
36
|
import { PairingError, encodeOfferUrl, HOSTED_RENDEZVOUS_URL, decodePairingHello, respondToHandshake, encodePairingMessage, derivePairRoot, deriveEpochRoutingToken, currentEpoch } from '@agentproto/secrets/pairing';
|
|
35
37
|
import { identityFingerprint } from '@agentproto/secrets/identity';
|
|
38
|
+
import { inferLegacyModeKind } from '@agentproto/driver-agent-cli';
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* @agentproto/runtime v0.1.0-alpha
|
|
@@ -445,6 +448,156 @@ var init_transcript_writer = __esm({
|
|
|
445
448
|
DEBOUNCE_MS = 250;
|
|
446
449
|
}
|
|
447
450
|
});
|
|
451
|
+
function claudeProjectSlug(cwd) {
|
|
452
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
453
|
+
}
|
|
454
|
+
function claudeCodeProjectDir(cwd) {
|
|
455
|
+
return resolve(homedir(), ".claude", "projects", claudeProjectSlug(cwd));
|
|
456
|
+
}
|
|
457
|
+
function extractFirstText(content) {
|
|
458
|
+
if (typeof content === "string") {
|
|
459
|
+
const t = content.trim();
|
|
460
|
+
return t || void 0;
|
|
461
|
+
}
|
|
462
|
+
if (Array.isArray(content)) {
|
|
463
|
+
for (const block of content) {
|
|
464
|
+
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
465
|
+
const t = block.text.trim();
|
|
466
|
+
if (t) return t;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return void 0;
|
|
471
|
+
}
|
|
472
|
+
async function scanClaudeJsonl(filePath) {
|
|
473
|
+
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
474
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
475
|
+
let startedAt;
|
|
476
|
+
let lastActivityAt;
|
|
477
|
+
let messageCount = 0;
|
|
478
|
+
let preview2;
|
|
479
|
+
let lastWriter;
|
|
480
|
+
for await (const line of rl) {
|
|
481
|
+
const trimmed = line.trim();
|
|
482
|
+
if (!trimmed) continue;
|
|
483
|
+
let entry;
|
|
484
|
+
try {
|
|
485
|
+
entry = JSON.parse(trimmed);
|
|
486
|
+
} catch {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (typeof entry.timestamp === "string") {
|
|
490
|
+
if (!startedAt) startedAt = entry.timestamp;
|
|
491
|
+
lastActivityAt = entry.timestamp;
|
|
492
|
+
}
|
|
493
|
+
if (typeof entry.entrypoint === "string") {
|
|
494
|
+
lastWriter = entry.entrypoint;
|
|
495
|
+
}
|
|
496
|
+
if (entry.type === "user" || entry.type === "assistant") {
|
|
497
|
+
messageCount += 1;
|
|
498
|
+
if (preview2 === void 0 && entry.type === "user") {
|
|
499
|
+
const text6 = extractFirstText(entry.message?.content);
|
|
500
|
+
if (text6 !== void 0) {
|
|
501
|
+
preview2 = text6.length > 120 ? text6.slice(0, 120) : text6;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return { startedAt, lastActivityAt, messageCount, preview: preview2, lastWriter };
|
|
507
|
+
}
|
|
508
|
+
async function buildClaudeCandidate(filePath, conversationId) {
|
|
509
|
+
const scanned = await scanClaudeJsonl(filePath);
|
|
510
|
+
return { conversationId, ...scanned };
|
|
511
|
+
}
|
|
512
|
+
function claudeEntrypointFor(mode) {
|
|
513
|
+
return mode === "native" ? "cli" : "sdk-ts";
|
|
514
|
+
}
|
|
515
|
+
async function discoverClaudeCode(input) {
|
|
516
|
+
const { cwd, since, until, attachmentMode, expectedId } = input;
|
|
517
|
+
const dir = claudeCodeProjectDir(cwd);
|
|
518
|
+
if (expectedId) {
|
|
519
|
+
const filePath = join(dir, `${expectedId}.jsonl`);
|
|
520
|
+
try {
|
|
521
|
+
await promises.stat(filePath);
|
|
522
|
+
} catch {
|
|
523
|
+
return [];
|
|
524
|
+
}
|
|
525
|
+
return [await buildClaudeCandidate(filePath, expectedId)];
|
|
526
|
+
}
|
|
527
|
+
let entries;
|
|
528
|
+
try {
|
|
529
|
+
entries = await promises.readdir(dir);
|
|
530
|
+
} catch {
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
|
|
534
|
+
if (jsonlFiles.length === 0) return [];
|
|
535
|
+
const sinceMs = since ? Date.parse(since) : NaN;
|
|
536
|
+
const untilMs = until ? Date.parse(until) : NaN;
|
|
537
|
+
const wantEntrypoint = attachmentMode ? claudeEntrypointFor(attachmentMode) : void 0;
|
|
538
|
+
const scored = [];
|
|
539
|
+
for (const f of jsonlFiles) {
|
|
540
|
+
const filePath = join(dir, f);
|
|
541
|
+
let mtimeMs;
|
|
542
|
+
try {
|
|
543
|
+
mtimeMs = (await promises.stat(filePath)).mtimeMs;
|
|
544
|
+
} catch {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1e3) continue;
|
|
548
|
+
const conversationId = f.replace(/\.jsonl$/, "");
|
|
549
|
+
const candidate = await buildClaudeCandidate(filePath, conversationId);
|
|
550
|
+
if (Number.isFinite(untilMs) && candidate.startedAt !== void 0) {
|
|
551
|
+
const startedMs = Date.parse(candidate.startedAt);
|
|
552
|
+
if (Number.isFinite(startedMs) && startedMs > untilMs) continue;
|
|
553
|
+
}
|
|
554
|
+
if (wantEntrypoint !== void 0 && candidate.lastWriter !== void 0 && candidate.lastWriter !== wantEntrypoint) {
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
scored.push({ candidate, mtimeMs });
|
|
558
|
+
}
|
|
559
|
+
scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
560
|
+
return scored.map((s) => s.candidate);
|
|
561
|
+
}
|
|
562
|
+
async function readClaudeCode(conversationId, cwd) {
|
|
563
|
+
const { exportClaudeCodeSession: exportClaudeCodeSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
564
|
+
return exportClaudeCodeSession2(conversationId, cwd);
|
|
565
|
+
}
|
|
566
|
+
async function discoverHermes(input) {
|
|
567
|
+
const { discoverHermesSessions: discoverHermesSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
568
|
+
return discoverHermesSessions2(input.cwd, input.since, input.expectedId);
|
|
569
|
+
}
|
|
570
|
+
async function readHermes(conversationId) {
|
|
571
|
+
const { exportHermesSession: exportHermesSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
572
|
+
return exportHermesSession2(conversationId);
|
|
573
|
+
}
|
|
574
|
+
var CONVERSATION_STORES;
|
|
575
|
+
var init_conversation_store = __esm({
|
|
576
|
+
"src/conversation-store.ts"() {
|
|
577
|
+
CONVERSATION_STORES = {
|
|
578
|
+
"claude-code": {
|
|
579
|
+
storeAs: "claudeResumeId",
|
|
580
|
+
// Printed by claude on graceful exit when session persistence is on
|
|
581
|
+
// (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
|
|
582
|
+
outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
|
|
583
|
+
attachArgv: (conversationId) => ["claude", "--resume", conversationId],
|
|
584
|
+
discover: discoverClaudeCode,
|
|
585
|
+
read: readClaudeCode
|
|
586
|
+
},
|
|
587
|
+
hermes: {
|
|
588
|
+
storeAs: "hermesResumeId",
|
|
589
|
+
// `hermes acp` is the ACP arm (bin_args in adapters/hermes/src/index.ts);
|
|
590
|
+
// `--resume SESSION --tui` is the native TUI resume path — same binary,
|
|
591
|
+
// different flags, unlike claude-code where the two arms are different
|
|
592
|
+
// binaries. Verified via `hermes --help`: `--resume SESSION, -r` =
|
|
593
|
+
// "Resume a previous session by ID or title", `--tui` = the real TUI.
|
|
594
|
+
attachArgv: (conversationId) => ["hermes", "--resume", conversationId, "--tui"],
|
|
595
|
+
discover: discoverHermes,
|
|
596
|
+
read: readHermes
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
});
|
|
448
601
|
|
|
449
602
|
// src/transcript-export.ts
|
|
450
603
|
var transcript_export_exports = {};
|
|
@@ -549,7 +702,7 @@ async function exportClaudeCodeSession(adapterSessionId, cwd) {
|
|
|
549
702
|
"claude-code exporter: cwd is required to locate the JSONL file.\nPass cwd explicitly or use a session id that is in the registry."
|
|
550
703
|
);
|
|
551
704
|
}
|
|
552
|
-
const encoded = cwd
|
|
705
|
+
const encoded = claudeProjectSlug(cwd);
|
|
553
706
|
const filePath = join(homedir(), ".claude", "projects", encoded, `${adapterSessionId}.jsonl`);
|
|
554
707
|
let stream;
|
|
555
708
|
try {
|
|
@@ -1057,6 +1210,7 @@ var init_transcript_export = __esm({
|
|
|
1057
1210
|
"src/transcript-export.ts"() {
|
|
1058
1211
|
init_tool_presenter();
|
|
1059
1212
|
init_transcript_writer();
|
|
1213
|
+
init_conversation_store();
|
|
1060
1214
|
ROLE_ICON = {
|
|
1061
1215
|
user: "\u{1F9D1} User",
|
|
1062
1216
|
assistant: "\u{1F916} Assistant",
|
|
@@ -1743,6 +1897,61 @@ function loadWorkspacesConfigSync(path = DEFAULT_CONFIG_PATH()) {
|
|
|
1743
1897
|
}
|
|
1744
1898
|
return normalizeConfig(parsed);
|
|
1745
1899
|
}
|
|
1900
|
+
async function saveWorkspacesConfig(config, path = DEFAULT_CONFIG_PATH()) {
|
|
1901
|
+
const normalized = normalizeConfig(config);
|
|
1902
|
+
await promises.mkdir(dirname(path), { recursive: true });
|
|
1903
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
1904
|
+
await promises.writeFile(tmp, JSON.stringify(normalized, null, 2) + "\n", "utf8");
|
|
1905
|
+
await promises.rename(tmp, path);
|
|
1906
|
+
}
|
|
1907
|
+
function addWorkspace(config, input) {
|
|
1908
|
+
if (!isAbsolute(input.path)) {
|
|
1909
|
+
throw new Error(
|
|
1910
|
+
`addWorkspace: path must be absolute, got "${input.path}".`
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
const slug = sanitizeSlug(input.slug);
|
|
1914
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1915
|
+
const existingIdx = config.workspaces.findIndex((w) => w.slug === slug);
|
|
1916
|
+
const next = existingIdx >= 0 ? {
|
|
1917
|
+
...config.workspaces[existingIdx],
|
|
1918
|
+
path: input.path,
|
|
1919
|
+
...input.label !== void 0 ? { label: input.label } : {},
|
|
1920
|
+
updatedAt: now
|
|
1921
|
+
} : {
|
|
1922
|
+
slug,
|
|
1923
|
+
path: input.path,
|
|
1924
|
+
...input.label ? { label: input.label } : {},
|
|
1925
|
+
addedAt: now,
|
|
1926
|
+
updatedAt: now
|
|
1927
|
+
};
|
|
1928
|
+
const workspaces = [...config.workspaces];
|
|
1929
|
+
if (existingIdx >= 0) workspaces[existingIdx] = next;
|
|
1930
|
+
else workspaces.push(next);
|
|
1931
|
+
const active = config.active ?? slug;
|
|
1932
|
+
return { ...config, workspaces, active };
|
|
1933
|
+
}
|
|
1934
|
+
function removeWorkspace(config, slug) {
|
|
1935
|
+
const sanitised = sanitizeSlug(slug);
|
|
1936
|
+
const workspaces = config.workspaces.filter((w) => w.slug !== sanitised);
|
|
1937
|
+
let active = config.active;
|
|
1938
|
+
if (active === sanitised) {
|
|
1939
|
+
active = workspaces[0]?.slug;
|
|
1940
|
+
}
|
|
1941
|
+
const next = { ...config, workspaces };
|
|
1942
|
+
if (active !== void 0) next.active = active;
|
|
1943
|
+
else delete next.active;
|
|
1944
|
+
return next;
|
|
1945
|
+
}
|
|
1946
|
+
function setActiveWorkspace(config, slug) {
|
|
1947
|
+
const sanitised = sanitizeSlug(slug);
|
|
1948
|
+
if (!config.workspaces.some((w) => w.slug === sanitised)) {
|
|
1949
|
+
throw new Error(
|
|
1950
|
+
`setActiveWorkspace: no workspace registered with slug "${sanitised}". Run \`agentproto workspace add <path> --slug ${sanitised}\` first.`
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
return { ...config, active: sanitised };
|
|
1954
|
+
}
|
|
1746
1955
|
function findWorkspace(config, slug) {
|
|
1747
1956
|
return config.workspaces.find((w) => w.slug === sanitizeSlug(slug));
|
|
1748
1957
|
}
|
|
@@ -2215,6 +2424,8 @@ function getMcpCredentialDeps() {
|
|
|
2215
2424
|
// src/sandbox-agent-session-proxy.ts
|
|
2216
2425
|
var MAX_POLL_MS = 49e3;
|
|
2217
2426
|
var MAX_OUTPUT_LINES = 500;
|
|
2427
|
+
var MAX_CONSECUTIVE_POLL_FAILURES = 6;
|
|
2428
|
+
var POLL_RETRY_DELAY_MS = 5e3;
|
|
2218
2429
|
function extractPromptText(message) {
|
|
2219
2430
|
if (typeof message === "string") return message;
|
|
2220
2431
|
if (message && typeof message === "object" && "text" in message) {
|
|
@@ -2232,29 +2443,63 @@ function createSandboxAgentSessionProxy(opts) {
|
|
|
2232
2443
|
async *send(message) {
|
|
2233
2444
|
const prompt = extractPromptText(message);
|
|
2234
2445
|
lastPrompt = prompt;
|
|
2235
|
-
await host.prompt(remoteSessionId, prompt);
|
|
2236
2446
|
let seenLength = 0;
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2447
|
+
try {
|
|
2448
|
+
await host.prompt(remoteSessionId, prompt);
|
|
2449
|
+
let consecutivePollFailures = 0;
|
|
2450
|
+
for (; ; ) {
|
|
2451
|
+
let result;
|
|
2452
|
+
try {
|
|
2453
|
+
result = await host.waitForAny([remoteSessionId], {
|
|
2454
|
+
event: "any",
|
|
2455
|
+
timeoutMs: MAX_POLL_MS
|
|
2456
|
+
});
|
|
2457
|
+
consecutivePollFailures = 0;
|
|
2458
|
+
} catch (pollErr) {
|
|
2459
|
+
consecutivePollFailures++;
|
|
2460
|
+
if (consecutivePollFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
|
|
2461
|
+
throw new Error(
|
|
2462
|
+
`sandbox proxy: ${consecutivePollFailures} consecutive poll failures against the box daemon (session "${remoteSessionId}") \u2014 giving up. Last error: ${pollErr instanceof Error ? pollErr.message : String(pollErr)}`
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
await new Promise((resolve14) => setTimeout(resolve14, POLL_RETRY_DELAY_MS));
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
if (result.timedOut) continue;
|
|
2469
|
+
let tail;
|
|
2470
|
+
try {
|
|
2471
|
+
tail = await host.output(remoteSessionId, MAX_OUTPUT_LINES);
|
|
2472
|
+
} catch {
|
|
2473
|
+
try {
|
|
2474
|
+
tail = await host.output(remoteSessionId, MAX_OUTPUT_LINES);
|
|
2475
|
+
} catch {
|
|
2476
|
+
tail = void 0;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
if (tail !== void 0 && tail.length > seenLength) {
|
|
2480
|
+
yield { kind: "text-delta", text: tail.slice(seenLength) };
|
|
2481
|
+
}
|
|
2482
|
+
if (tail !== void 0) seenLength = tail.length;
|
|
2483
|
+
if (result.event === "exited") {
|
|
2484
|
+
throw new Error(
|
|
2485
|
+
`sandbox proxy: remote session "${remoteSessionId}" exited \u2014 the box's own agentproto daemon ended this session (crash, OOM, or an out-of-band kill).`
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
yield {
|
|
2489
|
+
kind: "turn-end",
|
|
2490
|
+
reason: result.event === "awaiting-input" ? "awaiting-input" : "completed"
|
|
2491
|
+
};
|
|
2492
|
+
return;
|
|
2246
2493
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
try {
|
|
2496
|
+
const tail = await host.output(remoteSessionId, MAX_OUTPUT_LINES);
|
|
2497
|
+
if (tail.length > seenLength) {
|
|
2498
|
+
yield { kind: "text-delta", text: tail.slice(seenLength) };
|
|
2499
|
+
}
|
|
2500
|
+
} catch {
|
|
2252
2501
|
}
|
|
2253
|
-
|
|
2254
|
-
kind: "turn-end",
|
|
2255
|
-
reason: result.event === "awaiting-input" ? "awaiting-input" : "completed"
|
|
2256
|
-
};
|
|
2257
|
-
return;
|
|
2502
|
+
throw err;
|
|
2258
2503
|
}
|
|
2259
2504
|
},
|
|
2260
2505
|
async cancel() {
|
|
@@ -2352,6 +2597,16 @@ async function spawnAgentSession(deps2, input) {
|
|
|
2352
2597
|
provisionWorktree,
|
|
2353
2598
|
resolveWorktreeIsolation
|
|
2354
2599
|
} = deps2;
|
|
2600
|
+
const explicitCwd = input.cwd !== void 0;
|
|
2601
|
+
const explicitWorkspaceSlug = input.workspaceSlug !== void 0;
|
|
2602
|
+
const childDepth = callerScope ? callerScope.depth + 1 : 0;
|
|
2603
|
+
if (childDepth === 0 && !explicitCwd && !explicitWorkspaceSlug && normalizeWorktreeField(input.worktree) !== void 0) {
|
|
2604
|
+
return {
|
|
2605
|
+
ok: false,
|
|
2606
|
+
code: "worktree_requires_explicit_repo",
|
|
2607
|
+
message: "agent_start: `worktree` isolation was requested but neither `cwd` nor `workspaceSlug` was passed \u2014 refusing to guess the base repo from the daemon's active workspace. Pass `cwd` (an explicit path inside the repo to worktree from) or `workspaceSlug` (a slug from `agentproto workspace list`) to `agent_start`."
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2355
2610
|
let cwd = input.cwd;
|
|
2356
2611
|
let resolvedSlug = input.workspaceSlug;
|
|
2357
2612
|
if (!cwd || !resolvedSlug) {
|
|
@@ -2388,7 +2643,6 @@ async function spawnAgentSession(deps2, input) {
|
|
|
2388
2643
|
message: `agent_start: adapter "${input.adapter}" could not be resolved. If it was working a moment ago, something may be mid-rebuild \u2014 wait and retry. If it has never been installed, run \`agentproto install ${input.adapter}\` first.`
|
|
2389
2644
|
};
|
|
2390
2645
|
}
|
|
2391
|
-
const childDepth = callerScope ? callerScope.depth + 1 : 0;
|
|
2392
2646
|
const parentSessionId = callerScope?.ownerSessionId;
|
|
2393
2647
|
if (callerScope) {
|
|
2394
2648
|
if (childDepth > callerScope.maxDepth) {
|
|
@@ -2430,6 +2684,13 @@ async function spawnAgentSession(deps2, input) {
|
|
|
2430
2684
|
return { ok: false, code: "worktree_disabled", message: decision.message };
|
|
2431
2685
|
}
|
|
2432
2686
|
if (decision.action === "provision") {
|
|
2687
|
+
if (!explicitCwd && !explicitWorkspaceSlug) {
|
|
2688
|
+
return {
|
|
2689
|
+
ok: false,
|
|
2690
|
+
code: "worktree_requires_explicit_repo",
|
|
2691
|
+
message: "agent_start: `worktree` isolation was requested but neither `cwd` nor `workspaceSlug` was passed \u2014 refusing to guess the base repo from the daemon's active workspace. Pass `cwd` (an explicit path inside the repo to worktree from) or `workspaceSlug` (a slug from `agentproto workspace list`) to `agent_start`."
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2433
2694
|
if (!provisionWorktree) {
|
|
2434
2695
|
return {
|
|
2435
2696
|
ok: false,
|
|
@@ -2517,9 +2778,10 @@ async function spawnAgentSession(deps2, input) {
|
|
|
2517
2778
|
options: input.options,
|
|
2518
2779
|
auth: input.auth
|
|
2519
2780
|
});
|
|
2781
|
+
const hasGatewayBaseUrlOption = typeof spawnDefaults.options?.base_url === "string" && spawnDefaults.options.base_url.length > 0;
|
|
2520
2782
|
let authSpec;
|
|
2521
2783
|
let authEcho;
|
|
2522
|
-
if (resolved && input.sandbox === void 0 && resolved.authDescriptor) {
|
|
2784
|
+
if (resolved && input.sandbox === void 0 && resolved.authDescriptor && !hasGatewayBaseUrlOption) {
|
|
2523
2785
|
const authModel = input.model ?? resolved.defaultModel;
|
|
2524
2786
|
const pinnedProvider = spawnDefaults.auth.provider;
|
|
2525
2787
|
const resolvedProvider = pinnedProvider ?? resolved.authDescriptor.provider ?? (authModel ? getModelProvider(authModel) : void 0);
|
|
@@ -2642,7 +2904,14 @@ ${input.prompt}` : input.prompt;
|
|
|
2642
2904
|
...resolvedMcpServers ? { mcpServers: resolvedMcpServers } : {},
|
|
2643
2905
|
...input.model ? { model: input.model } : {},
|
|
2644
2906
|
...input.effort ? { effort: input.effort } : {},
|
|
2645
|
-
...input.label ? { label: input.label } : {}
|
|
2907
|
+
...input.label ? { label: input.label } : {},
|
|
2908
|
+
// Explicit billing-auth for the box's OWN agent_start. A fresh box
|
|
2909
|
+
// has no ~/.agentproto/config.json and claude-code never inherits
|
|
2910
|
+
// subscription auth from the shell env — the credential must ride the
|
|
2911
|
+
// spawn call itself. Only the caller's EXPLICIT `auth` is forwarded
|
|
2912
|
+
// (host config defaults stay host-scoped; the box resolves its own
|
|
2913
|
+
// defaults otherwise).
|
|
2914
|
+
...input.auth ? { auth: input.auth } : {}
|
|
2646
2915
|
});
|
|
2647
2916
|
if (!booted.ok) return booted;
|
|
2648
2917
|
agentSession = booted.agentSession;
|
|
@@ -2676,6 +2945,7 @@ ${input.prompt}` : input.prompt;
|
|
|
2676
2945
|
agentSession,
|
|
2677
2946
|
adapterSlug: input.adapter,
|
|
2678
2947
|
...input.model ? { model: input.model } : {},
|
|
2948
|
+
...input.mode ? { mode: input.mode } : {},
|
|
2679
2949
|
...input.wait && effectivePrompt ? {} : effectivePrompt ? { initialPrompt: effectivePrompt } : {},
|
|
2680
2950
|
...input.label ? { label: input.label } : {},
|
|
2681
2951
|
...resolvedMcpServers ? { mcpServers: resolvedMcpServers } : {},
|
|
@@ -2826,7 +3096,8 @@ async function bootSandboxAgentSession(opts) {
|
|
|
2826
3096
|
...opts.mcpServers ? { mcpServers: toMcpServerMounts(opts.mcpServers) } : {},
|
|
2827
3097
|
...opts.model ? { model: opts.model } : {},
|
|
2828
3098
|
...opts.effort ? { effort: opts.effort } : {},
|
|
2829
|
-
...opts.label ? { label: opts.label } : {}
|
|
3099
|
+
...opts.label ? { label: opts.label } : {},
|
|
3100
|
+
...opts.auth ? { auth: opts.auth } : {}
|
|
2830
3101
|
});
|
|
2831
3102
|
remoteSessionId = remoteDesc.id;
|
|
2832
3103
|
} catch (err) {
|
|
@@ -2857,6 +3128,60 @@ async function resolveSandboxSecret(slug) {
|
|
|
2857
3128
|
return null;
|
|
2858
3129
|
}
|
|
2859
3130
|
}
|
|
3131
|
+
|
|
3132
|
+
// src/canonical-posture.ts
|
|
3133
|
+
var POSTURE_PREAMBLES = {
|
|
3134
|
+
plan: "You are in PLAN mode. Investigate and propose a concrete plan for the user to approve; do NOT edit files, run commands, or make any other changes until the plan is explicitly approved. Reading and searching are fine.",
|
|
3135
|
+
"accept-edits": "You are in ACCEPT-EDITS mode. File edits are auto-approved, so apply them directly without pausing for confirmation on each one; commands and other actions still warrant the usual care.",
|
|
3136
|
+
bypass: "You are in BYPASS-PERMISSIONS mode. No approval prompts will interrupt you \u2014 every file edit and command runs without confirmation. Be deliberate and careful: there is no safety prompt between you and a destructive action.",
|
|
3137
|
+
"read-only": "You are in READ-ONLY mode. You may read, search, and analyze, but you must NOT edit files, run commands that mutate state, or make any other changes. Answer and advise only."
|
|
3138
|
+
};
|
|
3139
|
+
var POSTURE_NATIVE_ALIASES = {
|
|
3140
|
+
default: ["default", "build", "normal", "standard"],
|
|
3141
|
+
plan: ["plan", "planning", "plan-mode"],
|
|
3142
|
+
"accept-edits": ["accept-edits", "auto-accept", "auto-edit"],
|
|
3143
|
+
bypass: ["bypass", "bypass-permissions", "full-access", "yolo", "dangerously-skip-permissions"],
|
|
3144
|
+
"read-only": ["read-only", "chat", "ask"]
|
|
3145
|
+
};
|
|
3146
|
+
var CANONICAL_POSTURES = Object.keys(
|
|
3147
|
+
POSTURE_NATIVE_ALIASES
|
|
3148
|
+
);
|
|
3149
|
+
var CANONICAL_POSTURE_SET = new Set(CANONICAL_POSTURES);
|
|
3150
|
+
function isCanonicalPosture(value) {
|
|
3151
|
+
return CANONICAL_POSTURE_SET.has(value);
|
|
3152
|
+
}
|
|
3153
|
+
function parsePostureInput(raw) {
|
|
3154
|
+
return isCanonicalPosture(raw) ? raw : { harnessModeId: raw };
|
|
3155
|
+
}
|
|
3156
|
+
function normalizeModeId(id) {
|
|
3157
|
+
return id.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3158
|
+
}
|
|
3159
|
+
var CANONICAL_BY_NORMALIZED_ALIAS = (() => {
|
|
3160
|
+
const index = /* @__PURE__ */ new Map();
|
|
3161
|
+
for (const [posture, aliases] of Object.entries(POSTURE_NATIVE_ALIASES)) {
|
|
3162
|
+
for (const alias of aliases) index.set(normalizeModeId(alias), posture);
|
|
3163
|
+
}
|
|
3164
|
+
return index;
|
|
3165
|
+
})();
|
|
3166
|
+
function canonicalForModeId(modeId) {
|
|
3167
|
+
return CANONICAL_BY_NORMALIZED_ALIAS.get(normalizeModeId(modeId));
|
|
3168
|
+
}
|
|
3169
|
+
function findNativeMode(posture, availableModes) {
|
|
3170
|
+
if (typeof posture === "object") {
|
|
3171
|
+
const target = normalizeModeId(posture.harnessModeId);
|
|
3172
|
+
return availableModes.find((mode) => normalizeModeId(mode.id) === target);
|
|
3173
|
+
}
|
|
3174
|
+
return availableModes.find((mode) => canonicalForModeId(mode.id) === posture);
|
|
3175
|
+
}
|
|
3176
|
+
function resolvePosture(posture, availableModes) {
|
|
3177
|
+
const native = findNativeMode(posture, availableModes);
|
|
3178
|
+
if (native) return { kind: "native", mode: native };
|
|
3179
|
+
if (typeof posture === "object") {
|
|
3180
|
+
return { kind: "unavailable", requestedModeId: posture.harnessModeId };
|
|
3181
|
+
}
|
|
3182
|
+
if (posture === "default") return { kind: "noop", posture: "default" };
|
|
3183
|
+
return { kind: "prompt", posture, preamble: POSTURE_PREAMBLES[posture] };
|
|
3184
|
+
}
|
|
2860
3185
|
var sandboxSpecWithReuseSchema = z.object({
|
|
2861
3186
|
...SandboxSpecSchema.shape,
|
|
2862
3187
|
reuse: z.string().min(1).optional().describe(
|
|
@@ -2897,6 +3222,7 @@ function registerAgentTools(server, opts) {
|
|
|
2897
3222
|
registry,
|
|
2898
3223
|
resolveAgentAdapter,
|
|
2899
3224
|
listAgentAdapters,
|
|
3225
|
+
listCatalogModels,
|
|
2900
3226
|
buildOrchestratorMcp,
|
|
2901
3227
|
callerScope,
|
|
2902
3228
|
webhookNotifier,
|
|
@@ -3209,7 +3535,7 @@ function registerAgentTools(server, opts) {
|
|
|
3209
3535
|
if (callerScope) {
|
|
3210
3536
|
const subtree = collectSubtree(
|
|
3211
3537
|
callerScope.ownerSessionId,
|
|
3212
|
-
registry.list()
|
|
3538
|
+
registry.list({ includeArchived: true })
|
|
3213
3539
|
);
|
|
3214
3540
|
if (!subtree.has(sessionId)) {
|
|
3215
3541
|
return {
|
|
@@ -3277,66 +3603,32 @@ function registerAgentTools(server, opts) {
|
|
|
3277
3603
|
}
|
|
3278
3604
|
);
|
|
3279
3605
|
server.tool(
|
|
3280
|
-
"
|
|
3281
|
-
"
|
|
3606
|
+
"agent_set_model",
|
|
3607
|
+
"Switch the model on a LIVE agent-cli session without restarting it \u2014 the mid-session counterpart to picking a model at `agent_start` time. Dispatches on the adapter's own apply strategy: a session whose adapter selects models via ACP session config or a `/model` control turn switches live; one that takes its model as a spawn-time CLI argument (e.g. codex) can't, and reports `{applied:false, reason:\"requires-restart\"}` instead of failing. Never throws on a rejected switch \u2014 check `applied` in the result.",
|
|
3282
3608
|
{
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
)
|
|
3286
|
-
onlyAlive: z.boolean().optional().describe("When true, only running/starting sessions. Default false."),
|
|
3287
|
-
status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
|
|
3609
|
+
sessionId: sessionIdField,
|
|
3610
|
+
id: sessionIdAliasField,
|
|
3611
|
+
model: z.string().describe("Model id to switch to.")
|
|
3288
3612
|
},
|
|
3289
3613
|
async (input) => {
|
|
3290
|
-
|
|
3291
|
-
if (
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
}
|
|
3295
|
-
const kind = input.kind ?? "agent-cli";
|
|
3296
|
-
if (kind !== "all") {
|
|
3297
|
-
rows = rows.filter((s) => s.kind === kind);
|
|
3298
|
-
}
|
|
3299
|
-
if (input.status) {
|
|
3300
|
-
rows = rows.filter((s) => s.status === input.status);
|
|
3301
|
-
} else if (input.onlyAlive) {
|
|
3302
|
-
rows = rows.filter(
|
|
3303
|
-
(s) => s.status === "running" || s.status === "starting"
|
|
3304
|
-
);
|
|
3305
|
-
}
|
|
3306
|
-
return {
|
|
3307
|
-
content: [
|
|
3308
|
-
{ type: "text", text: JSON.stringify({ sessions: rows }, null, 2) }
|
|
3309
|
-
]
|
|
3310
|
-
};
|
|
3311
|
-
}
|
|
3312
|
-
);
|
|
3313
|
-
server.tool(
|
|
3314
|
-
"adapter_list",
|
|
3315
|
-
"Enumerate every agent CLI adapter installed on the host (claude-code, hermes, aider, \u2026). Returns slug + display name + version + protocol so callers can let users pick from the installed set instead of guessing. Use before `agent_start` when the model doesn't already know what's available.",
|
|
3316
|
-
{},
|
|
3317
|
-
async () => {
|
|
3318
|
-
if (!listAgentAdapters) {
|
|
3614
|
+
const sessionId = resolveSessionIdArg(input);
|
|
3615
|
+
if (!sessionId) return missingSessionIdError("agent_set_model");
|
|
3616
|
+
try {
|
|
3617
|
+
const result = await registry.setModel(sessionId, input.model);
|
|
3319
3618
|
return {
|
|
3320
3619
|
content: [
|
|
3321
3620
|
{
|
|
3322
3621
|
type: "text",
|
|
3323
|
-
text:
|
|
3622
|
+
text: JSON.stringify({ ok: true, sessionId, ...result }, null, 2)
|
|
3324
3623
|
}
|
|
3325
|
-
]
|
|
3326
|
-
isError: true
|
|
3327
|
-
};
|
|
3328
|
-
}
|
|
3329
|
-
try {
|
|
3330
|
-
const adapters = await listAgentAdapters();
|
|
3331
|
-
return {
|
|
3332
|
-
content: [{ type: "text", text: JSON.stringify({ adapters }, null, 2) }]
|
|
3624
|
+
]
|
|
3333
3625
|
};
|
|
3334
3626
|
} catch (err) {
|
|
3335
3627
|
return {
|
|
3336
3628
|
content: [
|
|
3337
3629
|
{
|
|
3338
3630
|
type: "text",
|
|
3339
|
-
text: `
|
|
3631
|
+
text: `agent_set_model: ${err instanceof Error ? err.message : String(err)}`
|
|
3340
3632
|
}
|
|
3341
3633
|
],
|
|
3342
3634
|
isError: true
|
|
@@ -3345,13 +3637,198 @@ function registerAgentTools(server, opts) {
|
|
|
3345
3637
|
}
|
|
3346
3638
|
);
|
|
3347
3639
|
server.tool(
|
|
3348
|
-
"
|
|
3349
|
-
"
|
|
3350
|
-
{
|
|
3351
|
-
|
|
3640
|
+
"agent_set_effort",
|
|
3641
|
+
"Switch the reasoning/compute budget (effort) on a LIVE agent-cli session without restarting it \u2014 the effort-axis counterpart to `agent_set_model`. Applied via the adapter's ACP session config. Effort is model-dependent: the same label means a different budget across models and some labels are model-gated (opus offers `ultracode`, haiku doesn't), so a label the current model rejects reports `{applied:false, reason}` instead of failing. Never throws on a rejected switch \u2014 check `applied` in the result.",
|
|
3642
|
+
{
|
|
3643
|
+
sessionId: sessionIdField,
|
|
3644
|
+
id: sessionIdAliasField,
|
|
3645
|
+
effort: z.string().describe(
|
|
3646
|
+
"Effort label to switch to (e.g. low/medium/high/xhigh/max/ultracode; the accepted set is model-dependent)."
|
|
3647
|
+
)
|
|
3648
|
+
},
|
|
3649
|
+
async (input) => {
|
|
3650
|
+
const sessionId = resolveSessionIdArg(input);
|
|
3651
|
+
if (!sessionId) return missingSessionIdError("agent_set_effort");
|
|
3352
3652
|
try {
|
|
3353
|
-
const
|
|
3354
|
-
|
|
3653
|
+
const result = await registry.setEffort(sessionId, input.effort);
|
|
3654
|
+
return {
|
|
3655
|
+
content: [
|
|
3656
|
+
{
|
|
3657
|
+
type: "text",
|
|
3658
|
+
text: JSON.stringify({ ok: true, sessionId, ...result }, null, 2)
|
|
3659
|
+
}
|
|
3660
|
+
]
|
|
3661
|
+
};
|
|
3662
|
+
} catch (err) {
|
|
3663
|
+
return {
|
|
3664
|
+
content: [
|
|
3665
|
+
{
|
|
3666
|
+
type: "text",
|
|
3667
|
+
text: `agent_set_effort: ${err instanceof Error ? err.message : String(err)}`
|
|
3668
|
+
}
|
|
3669
|
+
],
|
|
3670
|
+
isError: true
|
|
3671
|
+
};
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
);
|
|
3675
|
+
server.tool(
|
|
3676
|
+
"agent_set_posture",
|
|
3677
|
+
'Switch the posture (what the agent may DO \u2014 plan / accept-edits / bypass / read-only, or a raw harness mode id) on a LIVE agent-cli session. When the posture maps to a NATIVE mode the harness advertises, it switches live (`applied:true`). When there is no native mode (the posture would have to be prompt-injected or applied at spawn), it is NOT forced live \u2014 the result is `{applied:false, reason:"requires-restart"}` so the caller can re-apply it through a session restart instead. Never throws on a rejected switch \u2014 check `applied`.',
|
|
3678
|
+
{
|
|
3679
|
+
sessionId: sessionIdField,
|
|
3680
|
+
id: sessionIdAliasField,
|
|
3681
|
+
posture: z.string().describe(
|
|
3682
|
+
"Posture to switch to: a canonical value (default/plan/accept-edits/bypass/read-only) or a raw harness mode id."
|
|
3683
|
+
)
|
|
3684
|
+
},
|
|
3685
|
+
async (input) => {
|
|
3686
|
+
const sessionId = resolveSessionIdArg(input);
|
|
3687
|
+
if (!sessionId) return missingSessionIdError("agent_set_posture");
|
|
3688
|
+
try {
|
|
3689
|
+
const result = await registry.setPosture(sessionId, parsePostureInput(input.posture));
|
|
3690
|
+
return {
|
|
3691
|
+
content: [
|
|
3692
|
+
{
|
|
3693
|
+
type: "text",
|
|
3694
|
+
text: JSON.stringify({ ok: true, sessionId, ...result }, null, 2)
|
|
3695
|
+
}
|
|
3696
|
+
]
|
|
3697
|
+
};
|
|
3698
|
+
} catch (err) {
|
|
3699
|
+
return {
|
|
3700
|
+
content: [
|
|
3701
|
+
{
|
|
3702
|
+
type: "text",
|
|
3703
|
+
text: `agent_set_posture: ${err instanceof Error ? err.message : String(err)}`
|
|
3704
|
+
}
|
|
3705
|
+
],
|
|
3706
|
+
isError: true
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
);
|
|
3711
|
+
server.tool(
|
|
3712
|
+
"agent_sessions_list",
|
|
3713
|
+
"List agent-CLI sessions tracked by the daemon. Equivalent to `session_list({kind: 'agent-cli'})`. Each entry includes `kind`, `status`, age, etc. Use this when you only want the agent-CLI subset.",
|
|
3714
|
+
{
|
|
3715
|
+
kind: z.enum(["terminal", "agent-cli", "command", "all"]).optional().describe(
|
|
3716
|
+
"Optional override of the default `agent-cli` filter. `all` returns every kind."
|
|
3717
|
+
),
|
|
3718
|
+
onlyAlive: z.boolean().optional().describe("When true, only running/starting sessions. Default false."),
|
|
3719
|
+
status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
|
|
3720
|
+
},
|
|
3721
|
+
async (input) => {
|
|
3722
|
+
let rows = registry.list({ includeArchived: true });
|
|
3723
|
+
if (callerScope) {
|
|
3724
|
+
const subtree = collectSubtree(callerScope.ownerSessionId, rows);
|
|
3725
|
+
rows = rows.filter((s) => subtree.has(s.id));
|
|
3726
|
+
}
|
|
3727
|
+
rows = rows.filter((s) => !s.archived);
|
|
3728
|
+
const kind = input.kind ?? "agent-cli";
|
|
3729
|
+
if (kind !== "all") {
|
|
3730
|
+
rows = rows.filter((s) => s.kind === kind);
|
|
3731
|
+
}
|
|
3732
|
+
if (input.status) {
|
|
3733
|
+
rows = rows.filter((s) => s.status === input.status);
|
|
3734
|
+
} else if (input.onlyAlive) {
|
|
3735
|
+
rows = rows.filter(
|
|
3736
|
+
(s) => s.status === "running" || s.status === "starting"
|
|
3737
|
+
);
|
|
3738
|
+
}
|
|
3739
|
+
return {
|
|
3740
|
+
content: [
|
|
3741
|
+
{ type: "text", text: JSON.stringify({ sessions: rows }, null, 2) }
|
|
3742
|
+
]
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
);
|
|
3746
|
+
server.tool(
|
|
3747
|
+
"adapter_list",
|
|
3748
|
+
"Enumerate every agent CLI adapter installed on the host (claude-code, hermes, aider, \u2026). Returns slug + display name + version + protocol so callers can let users pick from the installed set instead of guessing. Use before `agent_start` when the model doesn't already know what's available.",
|
|
3749
|
+
{},
|
|
3750
|
+
async () => {
|
|
3751
|
+
if (!listAgentAdapters) {
|
|
3752
|
+
return {
|
|
3753
|
+
content: [
|
|
3754
|
+
{
|
|
3755
|
+
type: "text",
|
|
3756
|
+
text: "adapter_list is not enabled \u2014 the daemon was started without an adapter lister. Wire `@agentproto/cli`'s `listInstalledAdapters` via `createGateway({ listAgentAdapters })`."
|
|
3757
|
+
}
|
|
3758
|
+
],
|
|
3759
|
+
isError: true
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
try {
|
|
3763
|
+
const adapters = await listAgentAdapters();
|
|
3764
|
+
return {
|
|
3765
|
+
content: [{ type: "text", text: JSON.stringify({ adapters }, null, 2) }]
|
|
3766
|
+
};
|
|
3767
|
+
} catch (err) {
|
|
3768
|
+
return {
|
|
3769
|
+
content: [
|
|
3770
|
+
{
|
|
3771
|
+
type: "text",
|
|
3772
|
+
text: `adapter_list failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3773
|
+
}
|
|
3774
|
+
],
|
|
3775
|
+
isError: true
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
);
|
|
3780
|
+
server.tool(
|
|
3781
|
+
"catalog_models",
|
|
3782
|
+
"Read-only vendor/product/route catalog (SPEC \xA75) \u2014 every model this host can reach, widened beyond any one adapter's model list via OpenRouter/Requesty/HuggingFace routing, with a profile-aware `runnable` flag per route. Use before `agent_start` to see what's actually spawnable given the auth profiles configured on this host.",
|
|
3783
|
+
{
|
|
3784
|
+
adapter: z.string().optional().describe("Keep only routes reachable via this adapter slug."),
|
|
3785
|
+
vendor: z.string().optional().describe("Keep only this vendor's entry."),
|
|
3786
|
+
route: z.string().optional().describe("Keep only routes with this route id."),
|
|
3787
|
+
runnableOnly: mcpBool.optional().describe("Drop every route with runnable:false.")
|
|
3788
|
+
},
|
|
3789
|
+
async ({ adapter, vendor, route, runnableOnly }) => {
|
|
3790
|
+
if (!listCatalogModels) {
|
|
3791
|
+
return {
|
|
3792
|
+
content: [
|
|
3793
|
+
{
|
|
3794
|
+
type: "text",
|
|
3795
|
+
text: "catalog_models is not enabled \u2014 the daemon was started without a catalog lister. Wire `buildCatalogModels` via `createGateway({ listCatalogModels })`."
|
|
3796
|
+
}
|
|
3797
|
+
],
|
|
3798
|
+
isError: true
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3801
|
+
try {
|
|
3802
|
+
const catalog = await listCatalogModels({
|
|
3803
|
+
...adapter ? { adapter } : {},
|
|
3804
|
+
...vendor ? { vendor } : {},
|
|
3805
|
+
...route ? { route } : {},
|
|
3806
|
+
...runnableOnly ? { runnableOnly: true } : {}
|
|
3807
|
+
});
|
|
3808
|
+
return {
|
|
3809
|
+
content: [{ type: "text", text: JSON.stringify(catalog, null, 2) }]
|
|
3810
|
+
};
|
|
3811
|
+
} catch (err) {
|
|
3812
|
+
return {
|
|
3813
|
+
content: [
|
|
3814
|
+
{
|
|
3815
|
+
type: "text",
|
|
3816
|
+
text: `catalog_models failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3817
|
+
}
|
|
3818
|
+
],
|
|
3819
|
+
isError: true
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
);
|
|
3824
|
+
server.tool(
|
|
3825
|
+
"role_list",
|
|
3826
|
+
"Enumerate every spawn-time role known to the daemon \u2014 the two built-ins (executor, supervisor) plus any custom role installed as a role pack. Read-only: pure visibility into the same registry `agent_start`'s `role` field and privilege-lattice spawn gate use \u2014 this tool never itself grants or denies a spawn. Use before `agent_start` with `orchestrator` to discover which roles this session may in turn spawn.",
|
|
3827
|
+
{},
|
|
3828
|
+
async () => {
|
|
3829
|
+
try {
|
|
3830
|
+
const registry2 = loadRoleRegistry2 ? await loadRoleRegistry2() : await loadDefaultRoleRegistry();
|
|
3831
|
+
const roles = listRoles(registry2).map((role) => ({
|
|
3355
3832
|
name: role.name,
|
|
3356
3833
|
level: role.level,
|
|
3357
3834
|
delegation: role.toolPolicy.delegation,
|
|
@@ -3655,151 +4132,9 @@ function stringifyValues(raw) {
|
|
|
3655
4132
|
}
|
|
3656
4133
|
return out;
|
|
3657
4134
|
}
|
|
3658
|
-
function claudeCodeProjectDir(cwd) {
|
|
3659
|
-
const encoded = cwd.replace(/\//g, "-");
|
|
3660
|
-
return resolve(homedir(), ".claude", "projects", encoded);
|
|
3661
|
-
}
|
|
3662
|
-
function extractFirstText(content) {
|
|
3663
|
-
if (typeof content === "string") {
|
|
3664
|
-
const t = content.trim();
|
|
3665
|
-
return t || void 0;
|
|
3666
|
-
}
|
|
3667
|
-
if (Array.isArray(content)) {
|
|
3668
|
-
for (const block of content) {
|
|
3669
|
-
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
3670
|
-
const t = block.text.trim();
|
|
3671
|
-
if (t) return t;
|
|
3672
|
-
}
|
|
3673
|
-
}
|
|
3674
|
-
}
|
|
3675
|
-
return void 0;
|
|
3676
|
-
}
|
|
3677
|
-
async function scanClaudeJsonl(filePath) {
|
|
3678
|
-
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
3679
|
-
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
3680
|
-
let startedAt;
|
|
3681
|
-
let lastActivityAt;
|
|
3682
|
-
let messageCount = 0;
|
|
3683
|
-
let preview2;
|
|
3684
|
-
let lastWriter;
|
|
3685
|
-
for await (const line of rl) {
|
|
3686
|
-
const trimmed = line.trim();
|
|
3687
|
-
if (!trimmed) continue;
|
|
3688
|
-
let entry;
|
|
3689
|
-
try {
|
|
3690
|
-
entry = JSON.parse(trimmed);
|
|
3691
|
-
} catch {
|
|
3692
|
-
continue;
|
|
3693
|
-
}
|
|
3694
|
-
if (typeof entry.timestamp === "string") {
|
|
3695
|
-
if (!startedAt) startedAt = entry.timestamp;
|
|
3696
|
-
lastActivityAt = entry.timestamp;
|
|
3697
|
-
}
|
|
3698
|
-
if (typeof entry.entrypoint === "string") {
|
|
3699
|
-
lastWriter = entry.entrypoint;
|
|
3700
|
-
}
|
|
3701
|
-
if (entry.type === "user" || entry.type === "assistant") {
|
|
3702
|
-
messageCount += 1;
|
|
3703
|
-
if (preview2 === void 0 && entry.type === "user") {
|
|
3704
|
-
const text6 = extractFirstText(entry.message?.content);
|
|
3705
|
-
if (text6 !== void 0) {
|
|
3706
|
-
preview2 = text6.length > 120 ? text6.slice(0, 120) : text6;
|
|
3707
|
-
}
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
return { startedAt, lastActivityAt, messageCount, preview: preview2, lastWriter };
|
|
3712
|
-
}
|
|
3713
|
-
async function buildClaudeCandidate(filePath, conversationId) {
|
|
3714
|
-
const scanned = await scanClaudeJsonl(filePath);
|
|
3715
|
-
return { conversationId, ...scanned };
|
|
3716
|
-
}
|
|
3717
|
-
function claudeEntrypointFor(mode) {
|
|
3718
|
-
return mode === "native" ? "cli" : "sdk-ts";
|
|
3719
|
-
}
|
|
3720
|
-
async function discoverClaudeCode(input) {
|
|
3721
|
-
const { cwd, since, until, attachmentMode, expectedId } = input;
|
|
3722
|
-
const dir = claudeCodeProjectDir(cwd);
|
|
3723
|
-
if (expectedId) {
|
|
3724
|
-
const filePath = join(dir, `${expectedId}.jsonl`);
|
|
3725
|
-
try {
|
|
3726
|
-
await promises.stat(filePath);
|
|
3727
|
-
} catch {
|
|
3728
|
-
return [];
|
|
3729
|
-
}
|
|
3730
|
-
return [await buildClaudeCandidate(filePath, expectedId)];
|
|
3731
|
-
}
|
|
3732
|
-
let entries;
|
|
3733
|
-
try {
|
|
3734
|
-
entries = await promises.readdir(dir);
|
|
3735
|
-
} catch {
|
|
3736
|
-
return [];
|
|
3737
|
-
}
|
|
3738
|
-
const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
|
|
3739
|
-
if (jsonlFiles.length === 0) return [];
|
|
3740
|
-
const sinceMs = since ? Date.parse(since) : NaN;
|
|
3741
|
-
const untilMs = until ? Date.parse(until) : NaN;
|
|
3742
|
-
const wantEntrypoint = attachmentMode ? claudeEntrypointFor(attachmentMode) : void 0;
|
|
3743
|
-
const scored = [];
|
|
3744
|
-
for (const f of jsonlFiles) {
|
|
3745
|
-
const filePath = join(dir, f);
|
|
3746
|
-
let mtimeMs;
|
|
3747
|
-
try {
|
|
3748
|
-
mtimeMs = (await promises.stat(filePath)).mtimeMs;
|
|
3749
|
-
} catch {
|
|
3750
|
-
continue;
|
|
3751
|
-
}
|
|
3752
|
-
if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1e3) continue;
|
|
3753
|
-
const conversationId = f.replace(/\.jsonl$/, "");
|
|
3754
|
-
const candidate = await buildClaudeCandidate(filePath, conversationId);
|
|
3755
|
-
if (Number.isFinite(untilMs) && candidate.startedAt !== void 0) {
|
|
3756
|
-
const startedMs = Date.parse(candidate.startedAt);
|
|
3757
|
-
if (Number.isFinite(startedMs) && startedMs > untilMs) continue;
|
|
3758
|
-
}
|
|
3759
|
-
if (wantEntrypoint !== void 0 && candidate.lastWriter !== void 0 && candidate.lastWriter !== wantEntrypoint) {
|
|
3760
|
-
continue;
|
|
3761
|
-
}
|
|
3762
|
-
scored.push({ candidate, mtimeMs });
|
|
3763
|
-
}
|
|
3764
|
-
scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
3765
|
-
return scored.map((s) => s.candidate);
|
|
3766
|
-
}
|
|
3767
|
-
async function readClaudeCode(conversationId, cwd) {
|
|
3768
|
-
const { exportClaudeCodeSession: exportClaudeCodeSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
3769
|
-
return exportClaudeCodeSession2(conversationId, cwd);
|
|
3770
|
-
}
|
|
3771
|
-
async function discoverHermes(input) {
|
|
3772
|
-
const { discoverHermesSessions: discoverHermesSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
3773
|
-
return discoverHermesSessions2(input.cwd, input.since, input.expectedId);
|
|
3774
|
-
}
|
|
3775
|
-
async function readHermes(conversationId) {
|
|
3776
|
-
const { exportHermesSession: exportHermesSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
3777
|
-
return exportHermesSession2(conversationId);
|
|
3778
|
-
}
|
|
3779
|
-
var CONVERSATION_STORES = {
|
|
3780
|
-
"claude-code": {
|
|
3781
|
-
storeAs: "claudeResumeId",
|
|
3782
|
-
// Printed by claude on graceful exit when session persistence is on
|
|
3783
|
-
// (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
|
|
3784
|
-
outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
|
|
3785
|
-
attachArgv: (conversationId) => ["claude", "--resume", conversationId],
|
|
3786
|
-
discover: discoverClaudeCode,
|
|
3787
|
-
read: readClaudeCode
|
|
3788
|
-
},
|
|
3789
|
-
hermes: {
|
|
3790
|
-
storeAs: "hermesResumeId",
|
|
3791
|
-
// `hermes acp` is the ACP arm (bin_args in adapters/hermes/src/index.ts);
|
|
3792
|
-
// `--resume SESSION --tui` is the native TUI resume path — same binary,
|
|
3793
|
-
// different flags, unlike claude-code where the two arms are different
|
|
3794
|
-
// binaries. Verified via `hermes --help`: `--resume SESSION, -r` =
|
|
3795
|
-
// "Resume a previous session by ID or title", `--tui` = the real TUI.
|
|
3796
|
-
attachArgv: (conversationId) => ["hermes", "--resume", conversationId, "--tui"],
|
|
3797
|
-
discover: discoverHermes,
|
|
3798
|
-
read: readHermes
|
|
3799
|
-
}
|
|
3800
|
-
};
|
|
3801
4135
|
|
|
3802
4136
|
// src/resume-strategies.ts
|
|
4137
|
+
init_conversation_store();
|
|
3803
4138
|
var claudeCodeStore = CONVERSATION_STORES["claude-code"];
|
|
3804
4139
|
var RESUME_STRATEGIES = {
|
|
3805
4140
|
"claude-code": {
|
|
@@ -3907,6 +4242,46 @@ function tokenizeCommand(s) {
|
|
|
3907
4242
|
if (buf) out.push(buf);
|
|
3908
4243
|
return out;
|
|
3909
4244
|
}
|
|
4245
|
+
var RestartOverrideError = class extends Error {
|
|
4246
|
+
code = "restart_override_invalid";
|
|
4247
|
+
status = 400;
|
|
4248
|
+
constructor(message) {
|
|
4249
|
+
super(message);
|
|
4250
|
+
this.name = "RestartOverrideError";
|
|
4251
|
+
}
|
|
4252
|
+
};
|
|
4253
|
+
async function resolveAccessProfileFromStore(profileRef) {
|
|
4254
|
+
const profile = await getAuthProfile(profileRef);
|
|
4255
|
+
if (!profile) return void 0;
|
|
4256
|
+
const stored = await new KeychainStore().read({ path: profile.credentialRef });
|
|
4257
|
+
return { profile, ...stored?.value !== void 0 ? { credential: stored.value } : {} };
|
|
4258
|
+
}
|
|
4259
|
+
function methodToMode(method) {
|
|
4260
|
+
return method === "oauth-bearer" ? "subscription" : "api-key";
|
|
4261
|
+
}
|
|
4262
|
+
function directMethods(descriptor) {
|
|
4263
|
+
const methods = [];
|
|
4264
|
+
if (descriptor?.authSubscription) methods.push("oauth-bearer");
|
|
4265
|
+
if (descriptor?.provider) methods.push("api-key");
|
|
4266
|
+
return methods;
|
|
4267
|
+
}
|
|
4268
|
+
function eligibilityManifest(adapterSlug, descriptor, route, model) {
|
|
4269
|
+
const baseVendor = descriptor?.provider ?? (model ? getModelProvider(model) : void 0);
|
|
4270
|
+
const gateway = route?.gateway;
|
|
4271
|
+
const routeId = gateway ?? baseVendor;
|
|
4272
|
+
if (routeId === void 0) return void 0;
|
|
4273
|
+
const isDirect = baseVendor !== void 0 && routeId === baseVendor;
|
|
4274
|
+
const billedVendor2 = isDirect ? baseVendor : routeId;
|
|
4275
|
+
const methods = isDirect ? directMethods(descriptor) : ["api-key"];
|
|
4276
|
+
return {
|
|
4277
|
+
manifest: {
|
|
4278
|
+
id: adapterSlug,
|
|
4279
|
+
vendorByRoute: { [routeId]: billedVendor2 },
|
|
4280
|
+
methodsByRoute: { [routeId]: methods }
|
|
4281
|
+
},
|
|
4282
|
+
routeId
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
3910
4285
|
async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {}) {
|
|
3911
4286
|
const augmented = opts.forceAgentResume ? prev : await augmentWithFsResume(prev);
|
|
3912
4287
|
const strategy = opts.forceAgentResume ? { kind: "agent", resumeSessionId: prev.adapterSessionId } : decideRestartStrategy(augmented);
|
|
@@ -3928,15 +4303,80 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
3928
4303
|
let cwd = prev.cwd;
|
|
3929
4304
|
if (!cwd) console.warn(`[restartAgentSession] no cwd on prior descriptor ${prev.id} \u2014 falling back to daemon's cwd ${process.cwd()}`);
|
|
3930
4305
|
cwd ??= process.cwd();
|
|
4306
|
+
const overrides = opts.overrides ?? {};
|
|
4307
|
+
const effModel = overrides.model ?? prev.model;
|
|
4308
|
+
const effEffort = overrides.effort ?? prev.effort;
|
|
4309
|
+
const effRoute = overrides.route ?? prev.route;
|
|
4310
|
+
const effPosture = overrides.posture ?? prev.posture;
|
|
4311
|
+
const effContextProfile = overrides.contextProfile ?? prev.contextProfile;
|
|
4312
|
+
const effMode = overrides.mode ?? prev.mode;
|
|
4313
|
+
const accessOverrideRef = overrides.access?.profileRef;
|
|
3931
4314
|
let authSpec;
|
|
3932
4315
|
let authEcho;
|
|
3933
|
-
|
|
4316
|
+
let accessProfileEcho = prev.accessProfile;
|
|
4317
|
+
if (accessOverrideRef !== void 0) {
|
|
4318
|
+
const resolveProfile = opts.resolveAccessProfile ?? resolveAccessProfileFromStore;
|
|
4319
|
+
const found = await resolveProfile(accessOverrideRef);
|
|
4320
|
+
if (!found) {
|
|
4321
|
+
throw new RestartOverrideError(
|
|
4322
|
+
`restart access override: no auth profile "${accessOverrideRef}" found.`
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
const { profile, credential } = found;
|
|
4326
|
+
if (!resolved.authDescriptor) {
|
|
4327
|
+
throw new RestartOverrideError(
|
|
4328
|
+
`restart access override: adapter "${adapterSlug}" presents no billing-auth, so profile "${profile.id}" cannot be attached.`
|
|
4329
|
+
);
|
|
4330
|
+
}
|
|
4331
|
+
const projected = eligibilityManifest(
|
|
4332
|
+
adapterSlug,
|
|
4333
|
+
resolved.authDescriptor,
|
|
4334
|
+
effRoute,
|
|
4335
|
+
effModel ?? resolved.defaultModel
|
|
4336
|
+
);
|
|
4337
|
+
if (!projected) {
|
|
4338
|
+
throw new RestartOverrideError(
|
|
4339
|
+
`restart access override: cannot resolve a billing vendor for adapter "${adapterSlug}" (no fixed provider, no model) \u2014 profile "${profile.id}" eligibility is unverifiable, refusing to spawn.`
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
const { manifest, routeId } = projected;
|
|
4343
|
+
if (eligibleProfiles([profile], manifest, routeId).length === 0) {
|
|
4344
|
+
const billed = manifest.vendorByRoute[routeId];
|
|
4345
|
+
const methods = manifest.methodsByRoute[routeId] ?? [];
|
|
4346
|
+
throw new RestartOverrideError(
|
|
4347
|
+
`restart access override: profile "${profile.id}" (${profile.vendor}/${profile.method}) is not eligible for adapter "${adapterSlug}" on route "${routeId}" \u2014 that endpoint bills "${billed}" via [${methods.join(", ") || "no presentable methods"}]. Attach a profile whose vendor + method match the route.`
|
|
4348
|
+
);
|
|
4349
|
+
}
|
|
4350
|
+
const mode = methodToMode(profile.method);
|
|
4351
|
+
const result = resolveAuthSpec({
|
|
4352
|
+
descriptor: resolved.authDescriptor,
|
|
4353
|
+
...effModel ? { model: effModel } : {},
|
|
4354
|
+
requestedProvider: profile.vendor,
|
|
4355
|
+
requestedMode: mode,
|
|
4356
|
+
// Attaching a named profile is always an EXPLICIT billing choice — so a
|
|
4357
|
+
// missing credential fails loud (driver `missing_auth_credential`) rather
|
|
4358
|
+
// than falling back to ambient env or the prior credential.
|
|
4359
|
+
explicit: true,
|
|
4360
|
+
...mode === "subscription" && credential !== void 0 ? { subscriptionCredential: credential } : {},
|
|
4361
|
+
...mode === "api-key" && credential !== void 0 ? { apiKeyConfigCredential: credential } : {}
|
|
4362
|
+
});
|
|
4363
|
+
if (result) {
|
|
4364
|
+
authSpec = result.spec;
|
|
4365
|
+
authEcho = result.echo;
|
|
4366
|
+
}
|
|
4367
|
+
accessProfileEcho = {
|
|
4368
|
+
profileRef: profile.id,
|
|
4369
|
+
...profile.label !== void 0 ? { label: profile.label } : {},
|
|
4370
|
+
vendor: profile.vendor,
|
|
4371
|
+
method: profile.method
|
|
4372
|
+
};
|
|
4373
|
+
} else if (resolved.authDescriptor) {
|
|
3934
4374
|
const configDefaults = opts.loadDefaultsConfig ? await opts.loadDefaultsConfig() : (await loadConfig()).defaults;
|
|
3935
4375
|
const explicitAuthInput = prev.auth ? { mode: prev.auth.mode } : void 0;
|
|
3936
4376
|
const spawnDefaults = resolveSpawnDefaults(configDefaults, adapterSlug, {
|
|
3937
4377
|
auth: explicitAuthInput
|
|
3938
4378
|
});
|
|
3939
|
-
const authModel =
|
|
4379
|
+
const authModel = effModel ?? resolved.defaultModel;
|
|
3940
4380
|
const pinnedProvider = spawnDefaults.auth.provider;
|
|
3941
4381
|
const resolvedProvider = pinnedProvider ?? resolved.authDescriptor.provider ?? (authModel ? getModelProvider(authModel) : void 0);
|
|
3942
4382
|
const apiKeyStoreCredential = resolvedProvider && spawnDefaults.auth.explicit && spawnDefaults.auth.apiKeyCredential === void 0 ? await (0, providers_store_exports.getProviderKey)(resolvedProvider) : void 0;
|
|
@@ -3966,13 +4406,20 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
3966
4406
|
const agentSession = await resolved.startSession({
|
|
3967
4407
|
cwd,
|
|
3968
4408
|
...resumeSessionId ? { resumeSessionId } : {},
|
|
3969
|
-
...
|
|
4409
|
+
...effModel ? { model: effModel } : {},
|
|
4410
|
+
...effEffort ? { effort: effEffort } : {},
|
|
4411
|
+
// Legacy AIP-45 mode override only — the decomposed route/posture/
|
|
4412
|
+
// contextProfile spawn-env apply-path rides the driver's mode
|
|
4413
|
+
// decomposition (build step 2), out of scope here; they still round-trip
|
|
4414
|
+
// on the descriptor below.
|
|
4415
|
+
...overrides.mode ? { mode: overrides.mode } : {},
|
|
3970
4416
|
...prev.mcpServers ? { mcpServers: prev.mcpServers } : {},
|
|
3971
4417
|
...authSpec ? { auth: authSpec } : {},
|
|
3972
4418
|
onActivity: () => {
|
|
3973
4419
|
if (liveSessionId) registry.pulseActivity(liveSessionId);
|
|
3974
4420
|
}
|
|
3975
4421
|
});
|
|
4422
|
+
const resumeVia = !resumeSessionId ? "" : opts.forceAgentResume ? "resumed via ACP" : describeResumePath(augmented);
|
|
3976
4423
|
const desc2 = registry.spawnAgent({
|
|
3977
4424
|
workspaceSlug: prev.workspaceSlug,
|
|
3978
4425
|
cwd,
|
|
@@ -3980,7 +4427,17 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
3980
4427
|
adapterSlug,
|
|
3981
4428
|
...prev.label ? { label: prev.label } : {},
|
|
3982
4429
|
...prev.mcpServers ? { mcpServers: prev.mcpServers } : {},
|
|
3983
|
-
...
|
|
4430
|
+
...effModel ? { model: effModel } : {},
|
|
4431
|
+
// Decomposed config-axis echoes (SPEC §3.7) — carried forward from `prev`
|
|
4432
|
+
// and overlaid with any override so every axis round-trips onto the fresh
|
|
4433
|
+
// descriptor and the picker re-opens on it (SPEC §3.8), even the axes
|
|
4434
|
+
// whose spawn-env apply-path isn't wired here yet (route/posture/context).
|
|
4435
|
+
...effEffort ? { effort: effEffort } : {},
|
|
4436
|
+
...effPosture !== void 0 ? { posture: effPosture } : {},
|
|
4437
|
+
...effRoute ? { route: effRoute } : {},
|
|
4438
|
+
...effContextProfile ? { contextProfile: effContextProfile } : {},
|
|
4439
|
+
...accessProfileEcho ? { accessProfile: accessProfileEcho } : {},
|
|
4440
|
+
...effMode ? { mode: effMode } : {},
|
|
3984
4441
|
...resolved.commandPreview ? { commandPreview: resolved.commandPreview } : {},
|
|
3985
4442
|
// Verifiability echo (never the credential) — see the auth
|
|
3986
4443
|
// resolution block above. Absent when no credential resolved,
|
|
@@ -3993,31 +4450,53 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
3993
4450
|
credentialSource: authEcho.credentialSource,
|
|
3994
4451
|
setEnv: authEcho.setEnv
|
|
3995
4452
|
}
|
|
3996
|
-
} : {}
|
|
4453
|
+
} : {},
|
|
4454
|
+
resumedFrom: prev.id,
|
|
4455
|
+
resumeVia
|
|
3997
4456
|
});
|
|
3998
4457
|
liveSessionId = desc2.id;
|
|
3999
4458
|
return desc2;
|
|
4000
4459
|
};
|
|
4001
4460
|
let desc;
|
|
4002
4461
|
let resumeFallback = false;
|
|
4003
|
-
let usedResumeSessionId = strategy.resumeSessionId;
|
|
4004
4462
|
try {
|
|
4005
4463
|
desc = await spawnWithResume(strategy.resumeSessionId);
|
|
4006
4464
|
} catch (err) {
|
|
4007
4465
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4008
4466
|
if (strategy.resumeSessionId && /not found|Resource not found/i.test(msg)) {
|
|
4009
4467
|
desc = await spawnWithResume(void 0);
|
|
4010
|
-
usedResumeSessionId = void 0;
|
|
4011
4468
|
resumeFallback = true;
|
|
4012
4469
|
} else {
|
|
4013
4470
|
throw err;
|
|
4014
4471
|
}
|
|
4015
4472
|
}
|
|
4016
|
-
const
|
|
4473
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
4474
|
+
const emit = (axis, value) => {
|
|
4475
|
+
registry.emitConfigChanged({
|
|
4476
|
+
type: "session:config-changed",
|
|
4477
|
+
sessionId: desc.id,
|
|
4478
|
+
axis,
|
|
4479
|
+
value,
|
|
4480
|
+
...desc.label ? { label: desc.label } : {},
|
|
4481
|
+
ts
|
|
4482
|
+
});
|
|
4483
|
+
};
|
|
4484
|
+
if (overrides.model !== void 0) emit("model", overrides.model);
|
|
4485
|
+
if (overrides.effort !== void 0) emit("effort", overrides.effort);
|
|
4486
|
+
if (accessOverrideRef !== void 0) emit("access", { profileRef: accessOverrideRef });
|
|
4487
|
+
if (overrides.route !== void 0) emit("route", overrides.route);
|
|
4488
|
+
if (overrides.posture !== void 0) emit("posture", overrides.posture);
|
|
4489
|
+
if (overrides.contextProfile !== void 0) emit("contextProfile", overrides.contextProfile);
|
|
4017
4490
|
return {
|
|
4018
4491
|
desc,
|
|
4019
4492
|
resumedFrom: prev.id,
|
|
4020
|
-
|
|
4493
|
+
// `spawnWithResume` already computed + persisted this onto `desc` (the
|
|
4494
|
+
// fix this module carries — see `SessionDescriptor.resumedFrom`'s doc);
|
|
4495
|
+
// reading it back here rather than recomputing keeps the RESULT and the
|
|
4496
|
+
// STORED descriptor from ever being able to diverge. Never actually
|
|
4497
|
+
// undefined — every `spawnWithResume` call sets it, `?? ""` is just
|
|
4498
|
+
// satisfying the optional field's type.
|
|
4499
|
+
resumeVia: desc.resumeVia ?? "",
|
|
4021
4500
|
...resumeFallback ? { resumeFallback: true } : {}
|
|
4022
4501
|
};
|
|
4023
4502
|
}
|
|
@@ -4161,6 +4640,9 @@ function withToolExclusion(server, excluded) {
|
|
|
4161
4640
|
}
|
|
4162
4641
|
});
|
|
4163
4642
|
}
|
|
4643
|
+
|
|
4644
|
+
// src/conversation-read.ts
|
|
4645
|
+
init_conversation_store();
|
|
4164
4646
|
init_transcript_export();
|
|
4165
4647
|
var BINARY_TO_STORE_KEY = {
|
|
4166
4648
|
claude: "claude-code",
|
|
@@ -4340,7 +4822,7 @@ function buildSessionTree(sessions) {
|
|
|
4340
4822
|
});
|
|
4341
4823
|
return sessions.filter((s) => !s.parentSessionId || !idSet.has(s.parentSessionId)).sort((a, b) => a.startedAt.localeCompare(b.startedAt)).map(toNode);
|
|
4342
4824
|
}
|
|
4343
|
-
z.preprocess(
|
|
4825
|
+
var mcpBool2 = z.preprocess(
|
|
4344
4826
|
(v) => v === "true" ? true : v === "false" ? false : v,
|
|
4345
4827
|
z.boolean()
|
|
4346
4828
|
);
|
|
@@ -4362,14 +4844,20 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4362
4844
|
"Filter by session kind. `all` (default) returns every kind. Use `terminal` to list only PTY sessions, `agent-cli` for structured ACP agents."
|
|
4363
4845
|
),
|
|
4364
4846
|
onlyAlive: z.boolean().optional().describe("When true, only running/starting sessions. Default false."),
|
|
4365
|
-
status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
|
|
4847
|
+
status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive)."),
|
|
4848
|
+
includeArchived: z.boolean().optional().describe(
|
|
4849
|
+
"When true, also include archived sessions (hidden from every other view by `session_archive`). Default false."
|
|
4850
|
+
)
|
|
4366
4851
|
},
|
|
4367
4852
|
async (input) => {
|
|
4368
|
-
let rows = registry.list();
|
|
4853
|
+
let rows = registry.list({ includeArchived: true });
|
|
4369
4854
|
if (callerScope) {
|
|
4370
4855
|
const subtree = collectSubtree(callerScope.ownerSessionId, rows);
|
|
4371
4856
|
rows = rows.filter((s) => subtree.has(s.id));
|
|
4372
4857
|
}
|
|
4858
|
+
if (!input.includeArchived) {
|
|
4859
|
+
rows = rows.filter((s) => !s.archived);
|
|
4860
|
+
}
|
|
4373
4861
|
if (input.kind && input.kind !== "all") {
|
|
4374
4862
|
rows = rows.filter((s) => s.kind === input.kind);
|
|
4375
4863
|
}
|
|
@@ -4407,7 +4895,10 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4407
4895
|
};
|
|
4408
4896
|
}
|
|
4409
4897
|
if (callerScope) {
|
|
4410
|
-
const subtree = collectSubtree(
|
|
4898
|
+
const subtree = collectSubtree(
|
|
4899
|
+
callerScope.ownerSessionId,
|
|
4900
|
+
registry.list({ includeArchived: true })
|
|
4901
|
+
);
|
|
4411
4902
|
if (!subtree.has(desc.id)) {
|
|
4412
4903
|
return {
|
|
4413
4904
|
content: [
|
|
@@ -4443,11 +4934,12 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4443
4934
|
status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
|
|
4444
4935
|
},
|
|
4445
4936
|
async (input) => {
|
|
4446
|
-
let rows = registry.list();
|
|
4937
|
+
let rows = registry.list({ includeArchived: true });
|
|
4447
4938
|
if (callerScope) {
|
|
4448
4939
|
const subtree = collectSubtree(callerScope.ownerSessionId, rows);
|
|
4449
4940
|
rows = rows.filter((s) => subtree.has(s.id));
|
|
4450
4941
|
}
|
|
4942
|
+
rows = rows.filter((s) => !s.archived);
|
|
4451
4943
|
const kind = input.kind ?? "terminal";
|
|
4452
4944
|
if (kind !== "all") {
|
|
4453
4945
|
rows = rows.filter((s) => s.kind === kind);
|
|
@@ -4477,11 +4969,12 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4477
4969
|
status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
|
|
4478
4970
|
},
|
|
4479
4971
|
async (input) => {
|
|
4480
|
-
let rows = registry.list();
|
|
4972
|
+
let rows = registry.list({ includeArchived: true });
|
|
4481
4973
|
if (callerScope) {
|
|
4482
4974
|
const subtree = collectSubtree(callerScope.ownerSessionId, rows);
|
|
4483
4975
|
rows = rows.filter((s) => subtree.has(s.id));
|
|
4484
4976
|
}
|
|
4977
|
+
rows = rows.filter((s) => !s.archived);
|
|
4485
4978
|
const kind = input.kind ?? "command";
|
|
4486
4979
|
if (kind !== "all") {
|
|
4487
4980
|
rows = rows.filter((s) => s.kind === kind);
|
|
@@ -4765,11 +5258,12 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4765
5258
|
)
|
|
4766
5259
|
},
|
|
4767
5260
|
async (input) => {
|
|
4768
|
-
let rows = registry.list();
|
|
5261
|
+
let rows = registry.list({ includeArchived: true });
|
|
4769
5262
|
if (callerScope) {
|
|
4770
5263
|
const subtree = collectSubtree(callerScope.ownerSessionId, rows);
|
|
4771
5264
|
rows = rows.filter((s) => subtree.has(s.id));
|
|
4772
5265
|
}
|
|
5266
|
+
rows = rows.filter((s) => !s.archived);
|
|
4773
5267
|
if (input.onlyAlive) {
|
|
4774
5268
|
rows = rows.filter(
|
|
4775
5269
|
(s) => s.status === "running" || s.status === "starting"
|
|
@@ -4802,7 +5296,25 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4802
5296
|
cols: z.number().int().min(1).max(500).optional().describe(
|
|
4803
5297
|
"PTY cols \u2014 only used when the restart resolves to a provider-native or plain PTY resume. Default 80."
|
|
4804
5298
|
),
|
|
4805
|
-
rows: z.number().int().min(1).max(200).optional().describe("PTY rows \u2014 same case as `cols`. Default 24.")
|
|
5299
|
+
rows: z.number().int().min(1).max(200).optional().describe("PTY rows \u2014 same case as `cols`. Default 24."),
|
|
5300
|
+
// ── Restart-with-override axes (SPEC §4.3, step 6) — the single path
|
|
5301
|
+
// for all four restart-only axes. Each optional; an omitted axis is
|
|
5302
|
+
// carried forward from the prior session, an axis set here wins.
|
|
5303
|
+
model: z.string().min(1).optional().describe("Override the model on restart (route-identity ref)."),
|
|
5304
|
+
effort: z.enum(["low", "medium", "high", "xhigh", "max", "ultracode"]).optional().describe("Override the reasoning-effort level on restart."),
|
|
5305
|
+
access: z.object({
|
|
5306
|
+
profileRef: z.string().min(1).describe("Attach this NAMED auth profile (SPEC \xA71c). Rejected 400 if it's not eligible for the resolved (adapter \xD7 route).")
|
|
5307
|
+
}).optional().describe("Switch the session's billing wallet to a named auth profile."),
|
|
5308
|
+
route: z.object({
|
|
5309
|
+
gateway: z.string().min(1).describe("Endpoint/gateway id (anthropic|moonshot|\u2026)."),
|
|
5310
|
+
baseUrl: z.string().url().optional().describe("Explicit base URL for a custom gateway.")
|
|
5311
|
+
}).optional().describe("Override the endpoint/gateway rail on restart (access is downstream)."),
|
|
5312
|
+
posture: z.union([
|
|
5313
|
+
z.enum(["default", "plan", "accept-edits", "bypass", "read-only"]),
|
|
5314
|
+
z.object({ harnessModeId: z.string().min(1) })
|
|
5315
|
+
]).optional().describe("Override the posture (what the agent may DO) on restart."),
|
|
5316
|
+
contextProfile: z.string().min(1).optional().describe("Override what enters context (full|lean|\u2026) on restart."),
|
|
5317
|
+
mode: z.string().min(1).optional().describe("Legacy AIP-45 mode id override, forwarded verbatim to the driver at spawn.")
|
|
4806
5318
|
},
|
|
4807
5319
|
async (input) => {
|
|
4808
5320
|
const prev = registry.findByIdOrName(input.idOrName);
|
|
@@ -4818,7 +5330,10 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4818
5330
|
};
|
|
4819
5331
|
}
|
|
4820
5332
|
if (callerScope) {
|
|
4821
|
-
const subtree = collectSubtree(
|
|
5333
|
+
const subtree = collectSubtree(
|
|
5334
|
+
callerScope.ownerSessionId,
|
|
5335
|
+
registry.list({ includeArchived: true })
|
|
5336
|
+
);
|
|
4822
5337
|
if (!subtree.has(prev.id)) {
|
|
4823
5338
|
return {
|
|
4824
5339
|
content: [
|
|
@@ -4836,6 +5351,84 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4836
5351
|
};
|
|
4837
5352
|
}
|
|
4838
5353
|
}
|
|
5354
|
+
const overrides = {
|
|
5355
|
+
...input.model !== void 0 ? { model: input.model } : {},
|
|
5356
|
+
...input.effort !== void 0 ? { effort: input.effort } : {},
|
|
5357
|
+
...input.access !== void 0 ? { access: input.access } : {},
|
|
5358
|
+
...input.route !== void 0 ? { route: input.route } : {},
|
|
5359
|
+
...input.posture !== void 0 ? { posture: input.posture } : {},
|
|
5360
|
+
...input.contextProfile !== void 0 ? { contextProfile: input.contextProfile } : {},
|
|
5361
|
+
...input.mode !== void 0 ? { mode: input.mode } : {}
|
|
5362
|
+
};
|
|
5363
|
+
if (Object.keys(overrides).length > 0) {
|
|
5364
|
+
if (!prev.adapterSlug || !resolveAgentAdapter) {
|
|
5365
|
+
return {
|
|
5366
|
+
content: [
|
|
5367
|
+
{
|
|
5368
|
+
type: "text",
|
|
5369
|
+
text: JSON.stringify({
|
|
5370
|
+
error: "restart_override_invalid",
|
|
5371
|
+
status: 400,
|
|
5372
|
+
message: "session_restart: restart-with-override only applies to agent-cli sessions (a PTY/command session has no config axes to override).",
|
|
5373
|
+
ok: false,
|
|
5374
|
+
sessionId: prev.id
|
|
5375
|
+
})
|
|
5376
|
+
}
|
|
5377
|
+
],
|
|
5378
|
+
isError: true
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
try {
|
|
5382
|
+
const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
|
|
5383
|
+
forceAgentResume: true,
|
|
5384
|
+
overrides
|
|
5385
|
+
});
|
|
5386
|
+
return {
|
|
5387
|
+
content: [
|
|
5388
|
+
{
|
|
5389
|
+
type: "text",
|
|
5390
|
+
text: JSON.stringify(
|
|
5391
|
+
{
|
|
5392
|
+
...restarted.desc,
|
|
5393
|
+
resumedFrom: restarted.resumedFrom,
|
|
5394
|
+
resumeVia: restarted.resumeVia,
|
|
5395
|
+
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
5396
|
+
},
|
|
5397
|
+
null,
|
|
5398
|
+
2
|
|
5399
|
+
)
|
|
5400
|
+
}
|
|
5401
|
+
]
|
|
5402
|
+
};
|
|
5403
|
+
} catch (err) {
|
|
5404
|
+
if (err instanceof RestartOverrideError) {
|
|
5405
|
+
return {
|
|
5406
|
+
content: [
|
|
5407
|
+
{
|
|
5408
|
+
type: "text",
|
|
5409
|
+
text: JSON.stringify({
|
|
5410
|
+
error: err.code,
|
|
5411
|
+
status: err.status,
|
|
5412
|
+
message: err.message,
|
|
5413
|
+
ok: false,
|
|
5414
|
+
sessionId: prev.id
|
|
5415
|
+
})
|
|
5416
|
+
}
|
|
5417
|
+
],
|
|
5418
|
+
isError: true
|
|
5419
|
+
};
|
|
5420
|
+
}
|
|
5421
|
+
return {
|
|
5422
|
+
content: [
|
|
5423
|
+
{
|
|
5424
|
+
type: "text",
|
|
5425
|
+
text: `session_restart: ${err instanceof Error ? err.message : String(err)}`
|
|
5426
|
+
}
|
|
5427
|
+
],
|
|
5428
|
+
isError: true
|
|
5429
|
+
};
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
4839
5432
|
const augmented = await augmentWithFsResume(prev);
|
|
4840
5433
|
const strategy = decideRestartStrategy(augmented);
|
|
4841
5434
|
if (strategy.kind === "unsupported") {
|
|
@@ -4867,17 +5460,15 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4867
5460
|
cols: input.cols ?? 80,
|
|
4868
5461
|
rows: input.rows ?? 24,
|
|
4869
5462
|
...prev.name ? { name: prev.name } : {},
|
|
4870
|
-
...prev.label ? { label: prev.label } : {}
|
|
5463
|
+
...prev.label ? { label: prev.label } : {},
|
|
5464
|
+
resumedFrom: prev.id,
|
|
5465
|
+
resumeVia: describeResumePath(augmented)
|
|
4871
5466
|
});
|
|
4872
5467
|
return {
|
|
4873
5468
|
content: [
|
|
4874
5469
|
{
|
|
4875
5470
|
type: "text",
|
|
4876
|
-
text: JSON.stringify(
|
|
4877
|
-
{ ...desc, resumedFrom: prev.id, resumeVia: describeResumePath(augmented) },
|
|
4878
|
-
null,
|
|
4879
|
-
2
|
|
4880
|
-
)
|
|
5471
|
+
text: JSON.stringify(desc, null, 2)
|
|
4881
5472
|
}
|
|
4882
5473
|
]
|
|
4883
5474
|
};
|
|
@@ -4898,36 +5489,158 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4898
5489
|
content: [
|
|
4899
5490
|
{
|
|
4900
5491
|
type: "text",
|
|
4901
|
-
text: "session_restart: agent_start is not enabled \u2014 the daemon was started without an adapter resolver."
|
|
5492
|
+
text: "session_restart: agent_start is not enabled \u2014 the daemon was started without an adapter resolver."
|
|
5493
|
+
}
|
|
5494
|
+
],
|
|
5495
|
+
isError: true
|
|
5496
|
+
};
|
|
5497
|
+
}
|
|
5498
|
+
const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev);
|
|
5499
|
+
return {
|
|
5500
|
+
content: [
|
|
5501
|
+
{
|
|
5502
|
+
type: "text",
|
|
5503
|
+
text: JSON.stringify(
|
|
5504
|
+
{
|
|
5505
|
+
...restarted.desc,
|
|
5506
|
+
resumedFrom: restarted.resumedFrom,
|
|
5507
|
+
resumeVia: restarted.resumeVia,
|
|
5508
|
+
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
5509
|
+
},
|
|
5510
|
+
null,
|
|
5511
|
+
2
|
|
5512
|
+
)
|
|
5513
|
+
}
|
|
5514
|
+
]
|
|
5515
|
+
};
|
|
5516
|
+
} catch (err) {
|
|
5517
|
+
return {
|
|
5518
|
+
content: [
|
|
5519
|
+
{
|
|
5520
|
+
type: "text",
|
|
5521
|
+
text: `session_restart: ${err instanceof Error ? err.message : String(err)}`
|
|
5522
|
+
}
|
|
5523
|
+
],
|
|
5524
|
+
isError: true
|
|
5525
|
+
};
|
|
5526
|
+
}
|
|
5527
|
+
}
|
|
5528
|
+
);
|
|
5529
|
+
server.tool(
|
|
5530
|
+
"session_archive",
|
|
5531
|
+
"Archive a terminal-status session (exited/killed/error) so it drops out of `session_list`'s / `GET /sessions`'s default view \u2014 a housekeeping flag, not a daemon action: the session's history and transcript are untouched and stay fully readable (`session_usage`, `agent_export`, or `session_list({ includeArchived: true })`). Refuses a still-alive session (running/starting) \u2014 archiving one would hide it from view while it keeps working unattended. Use `session_unarchive` to restore visibility.",
|
|
5532
|
+
{
|
|
5533
|
+
idOrName: z.string().min(1).describe(
|
|
5534
|
+
"Session id or name to archive \u2014 from `session_list`, must be terminal-status."
|
|
5535
|
+
)
|
|
5536
|
+
},
|
|
5537
|
+
async (input) => {
|
|
5538
|
+
const prev = registry.findByIdOrName(input.idOrName);
|
|
5539
|
+
if (!prev) {
|
|
5540
|
+
return {
|
|
5541
|
+
content: [
|
|
5542
|
+
{
|
|
5543
|
+
type: "text",
|
|
5544
|
+
text: JSON.stringify({ error: `no session "${input.idOrName}" found` })
|
|
5545
|
+
}
|
|
5546
|
+
],
|
|
5547
|
+
isError: true
|
|
5548
|
+
};
|
|
5549
|
+
}
|
|
5550
|
+
if (callerScope) {
|
|
5551
|
+
const subtree = collectSubtree(
|
|
5552
|
+
callerScope.ownerSessionId,
|
|
5553
|
+
registry.list({ includeArchived: true })
|
|
5554
|
+
);
|
|
5555
|
+
if (!subtree.has(prev.id)) {
|
|
5556
|
+
return {
|
|
5557
|
+
content: [
|
|
5558
|
+
{
|
|
5559
|
+
type: "text",
|
|
5560
|
+
text: JSON.stringify({
|
|
5561
|
+
error: "orchestrator_session_out_of_scope",
|
|
5562
|
+
message: `session_archive: session "${prev.id}" is not in your subtree \u2014 a scoped orchestrator can only archive sessions it (transitively) spawned.`,
|
|
5563
|
+
ok: false,
|
|
5564
|
+
sessionId: prev.id
|
|
5565
|
+
})
|
|
5566
|
+
}
|
|
5567
|
+
],
|
|
5568
|
+
isError: true
|
|
5569
|
+
};
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
try {
|
|
5573
|
+
const desc = registry.archiveSession(prev.id);
|
|
5574
|
+
return {
|
|
5575
|
+
content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
|
|
5576
|
+
};
|
|
5577
|
+
} catch (err) {
|
|
5578
|
+
return {
|
|
5579
|
+
content: [
|
|
5580
|
+
{
|
|
5581
|
+
type: "text",
|
|
5582
|
+
text: `session_archive: ${err instanceof Error ? err.message : String(err)}`
|
|
5583
|
+
}
|
|
5584
|
+
],
|
|
5585
|
+
isError: true
|
|
5586
|
+
};
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
);
|
|
5590
|
+
server.tool(
|
|
5591
|
+
"session_unarchive",
|
|
5592
|
+
"Restore an archived session to `session_list`'s default view \u2014 the inverse of `session_archive`. No status guard: archiving never touches daemon state, so there is nothing to re-validate \u2014 any archived session can be unarchived at any time.",
|
|
5593
|
+
{
|
|
5594
|
+
idOrName: z.string().min(1).describe(
|
|
5595
|
+
"Session id or name to unarchive \u2014 find it via `session_list({ includeArchived: true })`."
|
|
5596
|
+
)
|
|
5597
|
+
},
|
|
5598
|
+
async (input) => {
|
|
5599
|
+
const prev = registry.findByIdOrName(input.idOrName);
|
|
5600
|
+
if (!prev) {
|
|
5601
|
+
return {
|
|
5602
|
+
content: [
|
|
5603
|
+
{
|
|
5604
|
+
type: "text",
|
|
5605
|
+
text: JSON.stringify({ error: `no session "${input.idOrName}" found` })
|
|
5606
|
+
}
|
|
5607
|
+
],
|
|
5608
|
+
isError: true
|
|
5609
|
+
};
|
|
5610
|
+
}
|
|
5611
|
+
if (callerScope) {
|
|
5612
|
+
const subtree = collectSubtree(
|
|
5613
|
+
callerScope.ownerSessionId,
|
|
5614
|
+
registry.list({ includeArchived: true })
|
|
5615
|
+
);
|
|
5616
|
+
if (!subtree.has(prev.id)) {
|
|
5617
|
+
return {
|
|
5618
|
+
content: [
|
|
5619
|
+
{
|
|
5620
|
+
type: "text",
|
|
5621
|
+
text: JSON.stringify({
|
|
5622
|
+
error: "orchestrator_session_out_of_scope",
|
|
5623
|
+
message: `session_unarchive: session "${prev.id}" is not in your subtree \u2014 a scoped orchestrator can only unarchive sessions it (transitively) spawned.`,
|
|
5624
|
+
ok: false,
|
|
5625
|
+
sessionId: prev.id
|
|
5626
|
+
})
|
|
4902
5627
|
}
|
|
4903
5628
|
],
|
|
4904
5629
|
isError: true
|
|
4905
5630
|
};
|
|
4906
5631
|
}
|
|
4907
|
-
|
|
5632
|
+
}
|
|
5633
|
+
try {
|
|
5634
|
+
const desc = registry.unarchiveSession(prev.id);
|
|
4908
5635
|
return {
|
|
4909
|
-
content: [
|
|
4910
|
-
{
|
|
4911
|
-
type: "text",
|
|
4912
|
-
text: JSON.stringify(
|
|
4913
|
-
{
|
|
4914
|
-
...restarted.desc,
|
|
4915
|
-
resumedFrom: restarted.resumedFrom,
|
|
4916
|
-
resumeVia: restarted.resumeVia,
|
|
4917
|
-
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
4918
|
-
},
|
|
4919
|
-
null,
|
|
4920
|
-
2
|
|
4921
|
-
)
|
|
4922
|
-
}
|
|
4923
|
-
]
|
|
5636
|
+
content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
|
|
4924
5637
|
};
|
|
4925
5638
|
} catch (err) {
|
|
4926
5639
|
return {
|
|
4927
5640
|
content: [
|
|
4928
5641
|
{
|
|
4929
5642
|
type: "text",
|
|
4930
|
-
text: `
|
|
5643
|
+
text: `session_unarchive: ${err instanceof Error ? err.message : String(err)}`
|
|
4931
5644
|
}
|
|
4932
5645
|
],
|
|
4933
5646
|
isError: true
|
|
@@ -4996,7 +5709,16 @@ function registerSessionTools(rawServer, opts) {
|
|
|
4996
5709
|
cols: input.cols ?? 80,
|
|
4997
5710
|
rows: input.rows ?? 24,
|
|
4998
5711
|
...input.name ? { name: input.name } : {},
|
|
4999
|
-
...input.label ? { label: input.label } : {}
|
|
5712
|
+
...input.label ? { label: input.label } : {},
|
|
5713
|
+
// Parent attribution + depth (orchestrator WP4) — same rule as
|
|
5714
|
+
// `agent_start` (session-spawn.ts): a spawn through a scoped
|
|
5715
|
+
// sub-gateway is attributed to the owning orchestrator so
|
|
5716
|
+
// `session_tree` shows the PTY as its child. Depth caps and
|
|
5717
|
+
// child quotas stay agent_start-only for now.
|
|
5718
|
+
...callerScope?.ownerSessionId ? {
|
|
5719
|
+
parentSessionId: callerScope.ownerSessionId,
|
|
5720
|
+
depth: callerScope.depth + 1
|
|
5721
|
+
} : {}
|
|
5000
5722
|
});
|
|
5001
5723
|
return {
|
|
5002
5724
|
content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
|
|
@@ -5075,10 +5797,13 @@ function registerSessionTools(rawServer, opts) {
|
|
|
5075
5797
|
);
|
|
5076
5798
|
server.tool(
|
|
5077
5799
|
"terminal_output",
|
|
5078
|
-
"Snapshot the recent byte buffer of a PTY session. Returns base64-encoded bytes (the buffer is RAW including ANSI escapes
|
|
5800
|
+
"Snapshot the recent byte buffer of a PTY session. Returns base64-encoded bytes (the buffer is RAW including ANSI escapes) by default; pass `clean: true` for ANSI-stripped plain text instead. `lastBytes` caps the read from the tail.",
|
|
5079
5801
|
{
|
|
5080
5802
|
sessionId: z.string().describe("Session id OR name from terminal_start."),
|
|
5081
|
-
lastBytes: z.number().int().min(1).max(64 * 1024).optional().describe("Max bytes from the tail. Default: full ring buffer (~64 KiB).")
|
|
5803
|
+
lastBytes: z.number().int().min(1).max(64 * 1024).optional().describe("Max bytes from the tail. Default: full ring buffer (~64 KiB)."),
|
|
5804
|
+
clean: mcpBool2.optional().describe(
|
|
5805
|
+
"Strip ANSI codes, returning human-readable text (as `text` instead of `b64`). Default false = raw base64."
|
|
5806
|
+
)
|
|
5082
5807
|
},
|
|
5083
5808
|
async (input) => {
|
|
5084
5809
|
if (!ptyEnabled) return ptyNotConfigured("terminal_output");
|
|
@@ -5118,7 +5843,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
5118
5843
|
sessionId: desc.id,
|
|
5119
5844
|
status: desc.status,
|
|
5120
5845
|
bytes: buf.byteLength,
|
|
5121
|
-
b64: buf.toString("base64")
|
|
5846
|
+
...input.clean ? { text: stripAnsi(buf.toString("utf8")) } : { b64: buf.toString("base64") }
|
|
5122
5847
|
},
|
|
5123
5848
|
null,
|
|
5124
5849
|
2
|
|
@@ -7935,6 +8660,9 @@ function filterSessionObserver(inner, shouldObserve) {
|
|
|
7935
8660
|
// src/sessions.ts
|
|
7936
8661
|
init_tool_presenter();
|
|
7937
8662
|
init_transcript_writer();
|
|
8663
|
+
|
|
8664
|
+
// src/conversation-index.ts
|
|
8665
|
+
init_conversation_store();
|
|
7938
8666
|
var DEFAULT_BUCKET = "default";
|
|
7939
8667
|
var BUCKETS_ROOT = () => resolve(homedir(), ".agentproto", "workspaces");
|
|
7940
8668
|
var LEGACY_SESSIONS_FILE = () => resolve(homedir(), ".agentproto", "sessions.json");
|
|
@@ -8058,6 +8786,110 @@ function readBucketRows(root, slug) {
|
|
|
8058
8786
|
return [];
|
|
8059
8787
|
}
|
|
8060
8788
|
}
|
|
8789
|
+
var rowId = (row) => row && typeof row === "object" && "id" in row && typeof row.id === "string" ? row.id : void 0;
|
|
8790
|
+
function mergeBucketRows(onDisk, rows, everHeldIds) {
|
|
8791
|
+
const incomingIds = new Set(rows.map(rowId).filter((id) => id !== void 0));
|
|
8792
|
+
const preserved = onDisk.filter((row) => {
|
|
8793
|
+
const id = rowId(row);
|
|
8794
|
+
return id !== void 0 && !incomingIds.has(id) && !everHeldIds.has(id);
|
|
8795
|
+
});
|
|
8796
|
+
return [...rows, ...preserved];
|
|
8797
|
+
}
|
|
8798
|
+
|
|
8799
|
+
// src/conversation-index.ts
|
|
8800
|
+
function conversationIndexPath(bucketsRoot, slug) {
|
|
8801
|
+
return join(bucketDir(bucketsRoot, slug), "conversations.jsonl");
|
|
8802
|
+
}
|
|
8803
|
+
async function listClaudeSubagents(projectDir, adapterSessionId) {
|
|
8804
|
+
const dir = join(projectDir, adapterSessionId, "subagents");
|
|
8805
|
+
let entries;
|
|
8806
|
+
try {
|
|
8807
|
+
entries = await promises.readdir(dir);
|
|
8808
|
+
} catch {
|
|
8809
|
+
return [];
|
|
8810
|
+
}
|
|
8811
|
+
return entries.filter((e) => e.startsWith("agent-") && e.endsWith(".jsonl")).map((e) => join(dir, e)).sort();
|
|
8812
|
+
}
|
|
8813
|
+
async function resolveNativeLink(input) {
|
|
8814
|
+
const { cwd, adapterSlug, adapterSessionId } = input;
|
|
8815
|
+
if (adapterSlug === "claude-code") {
|
|
8816
|
+
const dir = claudeCodeProjectDir(cwd);
|
|
8817
|
+
const path = join(dir, `${adapterSessionId}.jsonl`);
|
|
8818
|
+
const subagents = await listClaudeSubagents(dir, adapterSessionId);
|
|
8819
|
+
return { kind: "claude-jsonl", path, subagents };
|
|
8820
|
+
}
|
|
8821
|
+
if (adapterSlug === "hermes") {
|
|
8822
|
+
return {
|
|
8823
|
+
kind: "hermes-sqlite",
|
|
8824
|
+
dbPath: join(homedir(), ".hermes", "state.db"),
|
|
8825
|
+
rowId: adapterSessionId
|
|
8826
|
+
};
|
|
8827
|
+
}
|
|
8828
|
+
return void 0;
|
|
8829
|
+
}
|
|
8830
|
+
async function appendConversationRecord(bucketsRoot, slug, record) {
|
|
8831
|
+
const path = conversationIndexPath(bucketsRoot, slug);
|
|
8832
|
+
await promises.mkdir(dirname(path), { recursive: true });
|
|
8833
|
+
await promises.appendFile(path, JSON.stringify(record) + "\n", "utf8");
|
|
8834
|
+
}
|
|
8835
|
+
function isConversationIndexRecord(value) {
|
|
8836
|
+
if (!value || typeof value !== "object") return false;
|
|
8837
|
+
const r = value;
|
|
8838
|
+
return typeof r.sessionId === "string" && typeof r.workspace === "string" && typeof r.cwd === "string" && typeof r.adapterSlug === "string" && typeof r.adapterSessionId === "string" && typeof r.agentprotoTranscript === "string" && typeof r.startedAt === "string";
|
|
8839
|
+
}
|
|
8840
|
+
async function readConversationIndex(bucketsRoot, slug) {
|
|
8841
|
+
const path = conversationIndexPath(bucketsRoot, slug);
|
|
8842
|
+
let raw;
|
|
8843
|
+
try {
|
|
8844
|
+
raw = await promises.readFile(path, "utf8");
|
|
8845
|
+
} catch {
|
|
8846
|
+
return [];
|
|
8847
|
+
}
|
|
8848
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
8849
|
+
for (const line of raw.split("\n")) {
|
|
8850
|
+
const trimmed = line.trim();
|
|
8851
|
+
if (!trimmed) continue;
|
|
8852
|
+
let parsed;
|
|
8853
|
+
try {
|
|
8854
|
+
parsed = JSON.parse(trimmed);
|
|
8855
|
+
} catch {
|
|
8856
|
+
continue;
|
|
8857
|
+
}
|
|
8858
|
+
if (!isConversationIndexRecord(parsed)) continue;
|
|
8859
|
+
bySession.set(parsed.sessionId, parsed);
|
|
8860
|
+
}
|
|
8861
|
+
return Array.from(bySession.values());
|
|
8862
|
+
}
|
|
8863
|
+
async function findConversationRecord(bucketsRoot, slug, sessionId) {
|
|
8864
|
+
const rows = await readConversationIndex(bucketsRoot, slug);
|
|
8865
|
+
return rows.find((r) => r.sessionId === sessionId);
|
|
8866
|
+
}
|
|
8867
|
+
async function locateConversationBySessionId(bucketsRoot, listBuckets2, sessionId) {
|
|
8868
|
+
for (const slug of listBuckets2()) {
|
|
8869
|
+
const record = await findConversationRecord(bucketsRoot, slug, sessionId);
|
|
8870
|
+
if (record) return { workspace: slug, record };
|
|
8871
|
+
}
|
|
8872
|
+
return void 0;
|
|
8873
|
+
}
|
|
8874
|
+
async function locateConversationByNativePath(bucketsRoot, listBuckets2, nativePath) {
|
|
8875
|
+
const target = resolve(nativePath);
|
|
8876
|
+
for (const slug of listBuckets2()) {
|
|
8877
|
+
const rows = await readConversationIndex(bucketsRoot, slug);
|
|
8878
|
+
for (const record of rows) {
|
|
8879
|
+
if (!record.native || record.native.kind !== "claude-jsonl") continue;
|
|
8880
|
+
if (resolve(record.native.path) === target) {
|
|
8881
|
+
return { workspace: slug, record };
|
|
8882
|
+
}
|
|
8883
|
+
const matchedSubagentPath = record.native.subagents.find(
|
|
8884
|
+
(p) => resolve(p) === target
|
|
8885
|
+
);
|
|
8886
|
+
if (matchedSubagentPath) {
|
|
8887
|
+
return { workspace: slug, record, matchedSubagentPath };
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
}
|
|
8891
|
+
return void 0;
|
|
8892
|
+
}
|
|
8061
8893
|
|
|
8062
8894
|
// src/terminal-transcript-writer.ts
|
|
8063
8895
|
init_transcript_writer();
|
|
@@ -8235,6 +9067,15 @@ var SessionNotAliveError = class extends Error {
|
|
|
8235
9067
|
var RECENT_LINES_CAP = 500;
|
|
8236
9068
|
var RECENT_BYTES_CAP = 64 * 1024;
|
|
8237
9069
|
var PERSIST_DEBOUNCE_MS = 1500;
|
|
9070
|
+
var EMPTY_ID_SET = /* @__PURE__ */ new Set();
|
|
9071
|
+
function markHeldId(map, slug, id) {
|
|
9072
|
+
let set = map.get(slug);
|
|
9073
|
+
if (!set) {
|
|
9074
|
+
set = /* @__PURE__ */ new Set();
|
|
9075
|
+
map.set(slug, set);
|
|
9076
|
+
}
|
|
9077
|
+
set.add(id);
|
|
9078
|
+
}
|
|
8238
9079
|
var HISTORY_CAP = 200;
|
|
8239
9080
|
var INTERRUPT_SETTLE_TIMEOUT_MS = 6e4;
|
|
8240
9081
|
function stampProcessAlive(desc) {
|
|
@@ -8259,6 +9100,11 @@ function findPriorCommandSessionId(liveSessions, cwd) {
|
|
|
8259
9100
|
}
|
|
8260
9101
|
return best?.desc.id;
|
|
8261
9102
|
}
|
|
9103
|
+
function currentRouteOf(desc) {
|
|
9104
|
+
if (desc.route?.gateway) return desc.route.gateway;
|
|
9105
|
+
if (desc.model) return tryParseModelRef(desc.model)?.route;
|
|
9106
|
+
return void 0;
|
|
9107
|
+
}
|
|
8262
9108
|
function worktreeFields(cwd) {
|
|
8263
9109
|
const identity = resolveWorktreeIdentity(cwd);
|
|
8264
9110
|
if (!identity) return {};
|
|
@@ -8309,6 +9155,8 @@ function createSessionsRegistry(opts) {
|
|
|
8309
9155
|
let nextSubId = 1;
|
|
8310
9156
|
let shutdownDone = false;
|
|
8311
9157
|
const knownBuckets = /* @__PURE__ */ new Set();
|
|
9158
|
+
const sourceBucketOf = /* @__PURE__ */ new Map();
|
|
9159
|
+
const heldIdsByBucket = /* @__PURE__ */ new Map();
|
|
8312
9160
|
if (persist) {
|
|
8313
9161
|
if (partitioned) {
|
|
8314
9162
|
migrateLegacySessionsFile({
|
|
@@ -8321,13 +9169,17 @@ function createSessionsRegistry(opts) {
|
|
|
8321
9169
|
loadHistorySnapshot(
|
|
8322
9170
|
bucketSessionsFile(bucketsRoot, slug),
|
|
8323
9171
|
sessions,
|
|
8324
|
-
sessionEvents
|
|
9172
|
+
sessionEvents,
|
|
9173
|
+
slug,
|
|
9174
|
+
sourceBucketOf,
|
|
9175
|
+
heldIdsByBucket
|
|
8325
9176
|
);
|
|
8326
9177
|
}
|
|
8327
9178
|
} else {
|
|
8328
9179
|
loadHistorySnapshot(legacyPath, sessions, sessionEvents);
|
|
8329
9180
|
}
|
|
8330
9181
|
}
|
|
9182
|
+
const bootLoadedBuckets = new Set(knownBuckets);
|
|
8331
9183
|
const onProcessExit = () => {
|
|
8332
9184
|
shutdownImpl();
|
|
8333
9185
|
};
|
|
@@ -8411,6 +9263,39 @@ function createSessionsRegistry(opts) {
|
|
|
8411
9263
|
}
|
|
8412
9264
|
delete rt.desc.awaitingPermission;
|
|
8413
9265
|
};
|
|
9266
|
+
const recordConversationLink = (rt, adapterSessionIdOverride) => {
|
|
9267
|
+
if (!persist || !partitioned) return;
|
|
9268
|
+
const desc = rt.desc;
|
|
9269
|
+
if (desc.kind !== "agent-cli") return;
|
|
9270
|
+
const adapterSlug = desc.adapterSlug;
|
|
9271
|
+
const adapterSessionId = adapterSessionIdOverride ?? desc.adapterSessionId;
|
|
9272
|
+
const cwd = desc.cwd;
|
|
9273
|
+
if (!adapterSlug || !adapterSessionId || !cwd) return;
|
|
9274
|
+
void (async () => {
|
|
9275
|
+
try {
|
|
9276
|
+
const native = await resolveNativeLink({ cwd, adapterSlug, adapterSessionId });
|
|
9277
|
+
const registered = readRegisteredSlugs(workspacesConfigPath);
|
|
9278
|
+
const slug = resolveBucketSlug(desc.workspaceSlug, registered);
|
|
9279
|
+
const record = {
|
|
9280
|
+
sessionId: desc.id,
|
|
9281
|
+
workspace: slug,
|
|
9282
|
+
cwd,
|
|
9283
|
+
adapterSlug,
|
|
9284
|
+
adapterSessionId,
|
|
9285
|
+
...native ? { native } : {},
|
|
9286
|
+
agentprotoTranscript: sessionEventsPath(desc.id, transcriptBaseDir),
|
|
9287
|
+
...desc.title ? { title: desc.title } : {},
|
|
9288
|
+
startedAt: desc.startedAt,
|
|
9289
|
+
...desc.endedAt ? { endedAt: desc.endedAt } : {}
|
|
9290
|
+
};
|
|
9291
|
+
await appendConversationRecord(bucketsRoot, slug, record);
|
|
9292
|
+
} catch (err) {
|
|
9293
|
+
console.warn(
|
|
9294
|
+
`[sessions] conversation-index write failed for ${desc.id}: ${err instanceof Error ? err.message : String(err)}`
|
|
9295
|
+
);
|
|
9296
|
+
}
|
|
9297
|
+
})();
|
|
9298
|
+
};
|
|
8414
9299
|
const schedulePersist = () => {
|
|
8415
9300
|
if (!persist) return;
|
|
8416
9301
|
if (persistTimer) clearTimeout(persistTimer);
|
|
@@ -8427,7 +9312,8 @@ function createSessionsRegistry(opts) {
|
|
|
8427
9312
|
const groups = /* @__PURE__ */ new Map();
|
|
8428
9313
|
for (const slug of knownBuckets) groups.set(slug, []);
|
|
8429
9314
|
for (const desc of snapshotRows()) {
|
|
8430
|
-
const slug = resolveBucketSlug(desc.workspaceSlug, registered);
|
|
9315
|
+
const slug = sourceBucketOf.get(desc.id) ?? resolveBucketSlug(desc.workspaceSlug, registered);
|
|
9316
|
+
markHeldId(heldIdsByBucket, slug, desc.id);
|
|
8431
9317
|
const list = groups.get(slug);
|
|
8432
9318
|
if (list) list.push(desc);
|
|
8433
9319
|
else groups.set(slug, [desc]);
|
|
@@ -8435,6 +9321,11 @@ function createSessionsRegistry(opts) {
|
|
|
8435
9321
|
for (const slug of groups.keys()) knownBuckets.add(slug);
|
|
8436
9322
|
return groups;
|
|
8437
9323
|
};
|
|
9324
|
+
const rowsToWrite = (slug, rows) => bootLoadedBuckets.has(slug) ? rows : mergeBucketRows(
|
|
9325
|
+
readBucketRows(bucketsRoot, slug),
|
|
9326
|
+
rows,
|
|
9327
|
+
heldIdsByBucket.get(slug) ?? EMPTY_ID_SET
|
|
9328
|
+
);
|
|
8438
9329
|
const persistSnapshot = async () => {
|
|
8439
9330
|
try {
|
|
8440
9331
|
const savedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -8442,7 +9333,7 @@ function createSessionsRegistry(opts) {
|
|
|
8442
9333
|
for (const [slug, rows] of groupRowsByBucket()) {
|
|
8443
9334
|
await writeBucketSnapshot(bucketsRoot, slug, {
|
|
8444
9335
|
savedAt,
|
|
8445
|
-
sessions: rows
|
|
9336
|
+
sessions: rowsToWrite(slug, rows)
|
|
8446
9337
|
});
|
|
8447
9338
|
}
|
|
8448
9339
|
return;
|
|
@@ -8477,6 +9368,7 @@ function createSessionsRegistry(opts) {
|
|
|
8477
9368
|
[strategy.storeAs]: m[1]
|
|
8478
9369
|
};
|
|
8479
9370
|
schedulePersist();
|
|
9371
|
+
recordConversationLink(rt, m[1]);
|
|
8480
9372
|
}
|
|
8481
9373
|
};
|
|
8482
9374
|
const appendBytes = (rt, chunk) => {
|
|
@@ -8754,6 +9646,7 @@ function createSessionsRegistry(opts) {
|
|
|
8754
9646
|
rt.emitter.emit("status", rt.desc.status);
|
|
8755
9647
|
}
|
|
8756
9648
|
schedulePersist();
|
|
9649
|
+
recordConversationLink(rt);
|
|
8757
9650
|
} catch (err) {
|
|
8758
9651
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8759
9652
|
appendLine(rt, `[error] resume failed: ${msg}`, "stderr");
|
|
@@ -9098,11 +9991,25 @@ function createSessionsRegistry(opts) {
|
|
|
9098
9991
|
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
9099
9992
|
depth: input.depth ?? 0,
|
|
9100
9993
|
...input.model ? { model: input.model } : {},
|
|
9994
|
+
...input.mode ? { mode: input.mode } : {},
|
|
9101
9995
|
...input.auth ? { auth: input.auth } : {},
|
|
9996
|
+
// Decomposed config-axis echoes (SPEC §3.7), same optional-spread
|
|
9997
|
+
// shape as `model`/`mode`/`auth` above.
|
|
9998
|
+
...input.effort ? { effort: input.effort } : {},
|
|
9999
|
+
...input.posture !== void 0 ? { posture: input.posture } : {},
|
|
10000
|
+
...input.route ? { route: input.route } : {},
|
|
10001
|
+
...input.contextProfile ? { contextProfile: input.contextProfile } : {},
|
|
10002
|
+
...input.accessProfile ? { accessProfile: input.accessProfile } : {},
|
|
9102
10003
|
...priorCommandSessionId ? { priorCommandSessionId } : {},
|
|
9103
10004
|
...input.remote ? { remote: true } : {},
|
|
9104
10005
|
...input.sandboxId ? { sandboxId: input.sandboxId } : {},
|
|
9105
|
-
...input.sandboxTeardown ? { sandboxTeardown: input.sandboxTeardown } : {}
|
|
10006
|
+
...input.sandboxTeardown ? { sandboxTeardown: input.sandboxTeardown } : {},
|
|
10007
|
+
// Restart lineage (see SessionDescriptor.resumedFrom's doc). `resumeVia`
|
|
10008
|
+
// can legitimately be "" (a fresh fallback spawn with no continuity),
|
|
10009
|
+
// so it's gated on `!== undefined` rather than truthiness — a truthy
|
|
10010
|
+
// gate would silently drop the empty-string case.
|
|
10011
|
+
...input.resumedFrom ? { resumedFrom: input.resumedFrom } : {},
|
|
10012
|
+
...input.resumeVia !== void 0 ? { resumeVia: input.resumeVia } : {}
|
|
9106
10013
|
};
|
|
9107
10014
|
if (input.trace ?? opts?.langfuseTracingDefault ?? false) {
|
|
9108
10015
|
tracedSessions.add(id);
|
|
@@ -9130,6 +10037,7 @@ function createSessionsRegistry(opts) {
|
|
|
9130
10037
|
"stdout"
|
|
9131
10038
|
);
|
|
9132
10039
|
schedulePersist();
|
|
10040
|
+
recordConversationLink(rt);
|
|
9133
10041
|
if (input.initialPrompt) {
|
|
9134
10042
|
void runAgentTurn(rt, input.initialPrompt).catch((err) => {
|
|
9135
10043
|
appendLine(
|
|
@@ -9189,7 +10097,16 @@ function createSessionsRegistry(opts) {
|
|
|
9189
10097
|
...worktreeFields(input.cwd),
|
|
9190
10098
|
...input.name ? { name: input.name } : {},
|
|
9191
10099
|
...input.label ? { label: input.label } : {},
|
|
9192
|
-
...priorCommandSessionId ? { priorCommandSessionId } : {}
|
|
10100
|
+
...priorCommandSessionId ? { priorCommandSessionId } : {},
|
|
10101
|
+
// Parent attribution + depth (orchestrator WP4) — same recording
|
|
10102
|
+
// rule as spawnAgent above: depth always set so subtree/depth
|
|
10103
|
+
// logic never distinguishes "absent" from "root".
|
|
10104
|
+
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
10105
|
+
depth: input.depth ?? 0,
|
|
10106
|
+
// Restart lineage — same gating rule as spawnAgent above (`resumeVia`
|
|
10107
|
+
// can legitimately be "").
|
|
10108
|
+
...input.resumedFrom ? { resumedFrom: input.resumedFrom } : {},
|
|
10109
|
+
...input.resumeVia !== void 0 ? { resumeVia: input.resumeVia } : {}
|
|
9193
10110
|
};
|
|
9194
10111
|
const rt = {
|
|
9195
10112
|
desc,
|
|
@@ -9355,14 +10272,126 @@ function createSessionsRegistry(opts) {
|
|
|
9355
10272
|
await interruptInFlightTurn(rt, id, "interruptSession");
|
|
9356
10273
|
return { wasBusy: true };
|
|
9357
10274
|
},
|
|
10275
|
+
async setModel(id, modelId) {
|
|
10276
|
+
const rt = sessions.get(id);
|
|
10277
|
+
if (!rt) throw new Error(`setModel: no session "${id}"`);
|
|
10278
|
+
if (rt.desc.kind !== "agent-cli" || !rt.agentSession) {
|
|
10279
|
+
throw new Error(`setModel: session "${id}" is not an agent-cli session`);
|
|
10280
|
+
}
|
|
10281
|
+
const targetRoute = tryParseModelRef(modelId)?.route;
|
|
10282
|
+
const currentRoute = currentRouteOf(rt.desc);
|
|
10283
|
+
if (targetRoute && currentRoute && targetRoute !== currentRoute) {
|
|
10284
|
+
return {
|
|
10285
|
+
applied: false,
|
|
10286
|
+
reason: "requires-restart",
|
|
10287
|
+
suggestedOverride: { route: { gateway: targetRoute }, model: modelId }
|
|
10288
|
+
};
|
|
10289
|
+
}
|
|
10290
|
+
if (!rt.agentSession.setModel) {
|
|
10291
|
+
return { applied: false, reason: "not-supported" };
|
|
10292
|
+
}
|
|
10293
|
+
const result = await rt.agentSession.setModel(modelId);
|
|
10294
|
+
if (result.applied) {
|
|
10295
|
+
rt.desc.model = result.model ?? modelId;
|
|
10296
|
+
schedulePersist();
|
|
10297
|
+
if (sessionEvents) {
|
|
10298
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
10299
|
+
sessionEvents.emit({
|
|
10300
|
+
type: "session:config-changed",
|
|
10301
|
+
sessionId: id,
|
|
10302
|
+
axis: "model",
|
|
10303
|
+
value: rt.desc.model,
|
|
10304
|
+
label: rt.desc.label,
|
|
10305
|
+
ts
|
|
10306
|
+
});
|
|
10307
|
+
sessionEvents.emit({
|
|
10308
|
+
type: "session:model-changed",
|
|
10309
|
+
sessionId: id,
|
|
10310
|
+
model: rt.desc.model,
|
|
10311
|
+
label: rt.desc.label,
|
|
10312
|
+
ts
|
|
10313
|
+
});
|
|
10314
|
+
}
|
|
10315
|
+
}
|
|
10316
|
+
return result;
|
|
10317
|
+
},
|
|
10318
|
+
emitConfigChanged(ev) {
|
|
10319
|
+
sessionEvents?.emit(ev);
|
|
10320
|
+
},
|
|
10321
|
+
async setEffort(id, effort) {
|
|
10322
|
+
const rt = sessions.get(id);
|
|
10323
|
+
if (!rt) throw new Error(`setEffort: no session "${id}"`);
|
|
10324
|
+
if (rt.desc.kind !== "agent-cli" || !rt.agentSession) {
|
|
10325
|
+
throw new Error(`setEffort: session "${id}" is not an agent-cli session`);
|
|
10326
|
+
}
|
|
10327
|
+
if (!rt.agentSession.setEffort) {
|
|
10328
|
+
return { applied: false, reason: "not-supported" };
|
|
10329
|
+
}
|
|
10330
|
+
const result = await rt.agentSession.setEffort(effort);
|
|
10331
|
+
if (result.applied) {
|
|
10332
|
+
rt.desc.effort = result.effort ?? effort;
|
|
10333
|
+
schedulePersist();
|
|
10334
|
+
if (sessionEvents) {
|
|
10335
|
+
sessionEvents.emit({
|
|
10336
|
+
type: "session:config-changed",
|
|
10337
|
+
sessionId: id,
|
|
10338
|
+
axis: "effort",
|
|
10339
|
+
value: rt.desc.effort,
|
|
10340
|
+
label: rt.desc.label,
|
|
10341
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
10342
|
+
});
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
return result;
|
|
10346
|
+
},
|
|
10347
|
+
async setPosture(id, posture) {
|
|
10348
|
+
const rt = sessions.get(id);
|
|
10349
|
+
if (!rt) throw new Error(`setPosture: no session "${id}"`);
|
|
10350
|
+
if (rt.desc.kind !== "agent-cli" || !rt.agentSession) {
|
|
10351
|
+
throw new Error(`setPosture: session "${id}" is not an agent-cli session`);
|
|
10352
|
+
}
|
|
10353
|
+
const resolution = resolvePosture(posture, rt.agentSession.availableModes ?? []);
|
|
10354
|
+
if (resolution.kind !== "native") {
|
|
10355
|
+
return {
|
|
10356
|
+
applied: false,
|
|
10357
|
+
reason: "requires-restart",
|
|
10358
|
+
resolution: resolution.kind
|
|
10359
|
+
};
|
|
10360
|
+
}
|
|
10361
|
+
if (!rt.agentSession.setSessionMode) {
|
|
10362
|
+
return { applied: false, reason: "not-supported", resolution: "native" };
|
|
10363
|
+
}
|
|
10364
|
+
const result = await rt.agentSession.setSessionMode(resolution.mode.id);
|
|
10365
|
+
if (!result.applied) {
|
|
10366
|
+
return {
|
|
10367
|
+
applied: false,
|
|
10368
|
+
resolution: "native",
|
|
10369
|
+
...result.reason ? { reason: result.reason } : {}
|
|
10370
|
+
};
|
|
10371
|
+
}
|
|
10372
|
+
rt.desc.posture = posture;
|
|
10373
|
+
schedulePersist();
|
|
10374
|
+
if (sessionEvents) {
|
|
10375
|
+
sessionEvents.emit({
|
|
10376
|
+
type: "session:config-changed",
|
|
10377
|
+
sessionId: id,
|
|
10378
|
+
axis: "posture",
|
|
10379
|
+
value: posture,
|
|
10380
|
+
label: rt.desc.label,
|
|
10381
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
10382
|
+
});
|
|
10383
|
+
}
|
|
10384
|
+
return { applied: true, posture, modeId: resolution.mode.id, resolution: "native" };
|
|
10385
|
+
},
|
|
9358
10386
|
pulseActivity(id) {
|
|
9359
10387
|
const rt = sessions.get(id);
|
|
9360
10388
|
if (!rt) return;
|
|
9361
10389
|
rt.desc.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9362
10390
|
schedulePersist();
|
|
9363
10391
|
},
|
|
9364
|
-
list() {
|
|
9365
|
-
|
|
10392
|
+
list(opts2) {
|
|
10393
|
+
const includeArchived = opts2?.includeArchived ?? false;
|
|
10394
|
+
return Array.from(sessions.values()).map((s) => s.desc).filter((desc) => includeArchived || !desc.archived).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).map((desc) => {
|
|
9366
10395
|
stampProcessAlive(desc);
|
|
9367
10396
|
return desc;
|
|
9368
10397
|
});
|
|
@@ -9478,6 +10507,28 @@ function createSessionsRegistry(opts) {
|
|
|
9478
10507
|
emitExited(rt);
|
|
9479
10508
|
return true;
|
|
9480
10509
|
},
|
|
10510
|
+
archiveSession(id) {
|
|
10511
|
+
const rt = sessions.get(id);
|
|
10512
|
+
if (!rt) throw new Error(`archiveSession: no session "${id}"`);
|
|
10513
|
+
const isAlive = rt.desc.status === "running" || rt.desc.status === "starting";
|
|
10514
|
+
if (isAlive) {
|
|
10515
|
+
throw new Error(
|
|
10516
|
+
`archiveSession: session "${id}" is still ${rt.desc.status} \u2014 only a terminal-status session (exited/killed/error) can be archived.`
|
|
10517
|
+
);
|
|
10518
|
+
}
|
|
10519
|
+
rt.desc.archived = true;
|
|
10520
|
+
schedulePersist();
|
|
10521
|
+
stampProcessAlive(rt.desc);
|
|
10522
|
+
return rt.desc;
|
|
10523
|
+
},
|
|
10524
|
+
unarchiveSession(id) {
|
|
10525
|
+
const rt = sessions.get(id);
|
|
10526
|
+
if (!rt) throw new Error(`unarchiveSession: no session "${id}"`);
|
|
10527
|
+
rt.desc.archived = false;
|
|
10528
|
+
schedulePersist();
|
|
10529
|
+
stampProcessAlive(rt.desc);
|
|
10530
|
+
return rt.desc;
|
|
10531
|
+
},
|
|
9481
10532
|
listPendingPermissions(filter) {
|
|
9482
10533
|
const all = Array.from(pendingPermissions.values());
|
|
9483
10534
|
const scoped = filter?.sessionId ? all.filter((p) => p.sessionId === filter.sessionId) : all;
|
|
@@ -9602,7 +10653,7 @@ function createSessionsRegistry(opts) {
|
|
|
9602
10653
|
for (const [slug, rows] of groupRowsByBucket()) {
|
|
9603
10654
|
writeBucketSnapshotSync(bucketsRoot, slug, {
|
|
9604
10655
|
savedAt: nowIso,
|
|
9605
|
-
sessions: rows
|
|
10656
|
+
sessions: rowsToWrite(slug, rows)
|
|
9606
10657
|
});
|
|
9607
10658
|
}
|
|
9608
10659
|
} else {
|
|
@@ -9624,7 +10675,7 @@ function clearInFlightFlags(desc) {
|
|
|
9624
10675
|
desc.blockedOn = void 0;
|
|
9625
10676
|
desc.pendingToolCallId = void 0;
|
|
9626
10677
|
}
|
|
9627
|
-
function loadHistorySnapshot(persistPath, sessions, sessionEvents) {
|
|
10678
|
+
function loadHistorySnapshot(persistPath, sessions, sessionEvents, bucketSlug, sourceBucketOf, heldIdsByBucket) {
|
|
9628
10679
|
let raw;
|
|
9629
10680
|
try {
|
|
9630
10681
|
raw = readFileSync(persistPath, "utf8");
|
|
@@ -9676,6 +10727,10 @@ function loadHistorySnapshot(persistPath, sessions, sessionEvents) {
|
|
|
9676
10727
|
};
|
|
9677
10728
|
rt.emitter.setMaxListeners(50);
|
|
9678
10729
|
sessions.set(desc.id, rt);
|
|
10730
|
+
if (bucketSlug !== void 0) {
|
|
10731
|
+
sourceBucketOf?.set(desc.id, bucketSlug);
|
|
10732
|
+
if (heldIdsByBucket) markHeldId(heldIdsByBucket, bucketSlug, desc.id);
|
|
10733
|
+
}
|
|
9679
10734
|
if (wasAlive) {
|
|
9680
10735
|
sessionEvents?.emit({
|
|
9681
10736
|
type: "session:exited",
|
|
@@ -10551,7 +11606,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
10551
11606
|
const server = opts.toolSubset ? withToolSubset(rawServer, opts.toolSubset) : rawServer;
|
|
10552
11607
|
const { registry, sessionEvents, eventRing, callerScope, inboundWatcher } = opts;
|
|
10553
11608
|
const isPolicyInSubtree = (policy, ownerId) => {
|
|
10554
|
-
const subtree = collectSubtree(ownerId, registry.list());
|
|
11609
|
+
const subtree = collectSubtree(ownerId, registry.list({ includeArchived: true }));
|
|
10555
11610
|
const ids = policy.sessionIds.length > 0 ? policy.sessionIds : [policy.sessionId];
|
|
10556
11611
|
return ids.every((id) => subtree.has(id));
|
|
10557
11612
|
};
|
|
@@ -10583,7 +11638,10 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
10583
11638
|
const isSessionInScope = (sessionId) => {
|
|
10584
11639
|
if (!callerScope) return true;
|
|
10585
11640
|
if (!callerScope.ownerSessionId) return false;
|
|
10586
|
-
return collectSubtree(
|
|
11641
|
+
return collectSubtree(
|
|
11642
|
+
callerScope.ownerSessionId,
|
|
11643
|
+
registry.list({ includeArchived: true })
|
|
11644
|
+
).has(sessionId);
|
|
10587
11645
|
};
|
|
10588
11646
|
const enrichPermission2 = (p) => {
|
|
10589
11647
|
const desc = registry.get(p.sessionId);
|
|
@@ -10761,6 +11819,12 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
10761
11819
|
"Reuse the session spawned by an earlier step (any prior stage), by that step's `label`. Ignored if `adapter` is set. Lets a later-stage step act on an earlier stage's output (e.g. a 'verify' step reusing a 'produce' step's session)."
|
|
10762
11820
|
),
|
|
10763
11821
|
cacheable: z.boolean().optional().describe("Cache this step's output under the run's cacheKey (opt-in; for idempotent/pure steps only)."),
|
|
11822
|
+
sandbox: z.union([
|
|
11823
|
+
z.string().min(1).describe("Sandbox provider slug from `list_sandbox_providers` (e.g. 'local', 'e2b')."),
|
|
11824
|
+
z.object({ provider: z.string().min(1) }).passthrough().describe("Inline AIP-36 SandboxSpec ({ provider, config?, env?, \u2026 }).")
|
|
11825
|
+
]).optional().describe(
|
|
11826
|
+
"Run this step's session inside a sandbox instead of on the host \u2014 same semantics as `agent_start.sandbox`. Only meaningful with `adapter`."
|
|
11827
|
+
),
|
|
10764
11828
|
policy: z.discriminatedUnion("awaiting", [
|
|
10765
11829
|
z.object({ awaiting: z.literal("auto-allow"), prompt: z.string() }),
|
|
10766
11830
|
z.object({
|
|
@@ -10982,7 +12046,10 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
10982
12046
|
};
|
|
10983
12047
|
}
|
|
10984
12048
|
const targetIds = input.sessionIds && input.sessionIds.length > 0 ? input.sessionIds : input.sessionId ? [input.sessionId] : [];
|
|
10985
|
-
const subtree = collectSubtree(
|
|
12049
|
+
const subtree = collectSubtree(
|
|
12050
|
+
callerScope.ownerSessionId,
|
|
12051
|
+
registry.list({ includeArchived: true })
|
|
12052
|
+
);
|
|
10986
12053
|
const outside = targetIds.filter((id) => !subtree.has(id));
|
|
10987
12054
|
if (outside.length > 0) {
|
|
10988
12055
|
return {
|
|
@@ -11840,56 +12907,170 @@ async function startHttpServer(opts) {
|
|
|
11840
12907
|
await handleHeartbeatTick(req, res);
|
|
11841
12908
|
return;
|
|
11842
12909
|
}
|
|
11843
|
-
if (path === "/files/upload" && req.method === "POST") {
|
|
12910
|
+
if (path === "/files/upload" && req.method === "POST") {
|
|
12911
|
+
const gate = checkSessionsToken(req);
|
|
12912
|
+
if (gate !== "ok") {
|
|
12913
|
+
rejectUnauthorizedSession(req, res, gate);
|
|
12914
|
+
return;
|
|
12915
|
+
}
|
|
12916
|
+
await handleFileUpload(req, res, url);
|
|
12917
|
+
return;
|
|
12918
|
+
}
|
|
12919
|
+
if (opts.sessions && path.startsWith("/sessions")) {
|
|
12920
|
+
if (isMutatingSessionsRoute(req.method ?? "GET", path)) {
|
|
12921
|
+
const gate = checkSessionsToken(req);
|
|
12922
|
+
if (gate !== "ok") {
|
|
12923
|
+
rejectUnauthorizedSession(req, res, gate);
|
|
12924
|
+
return;
|
|
12925
|
+
}
|
|
12926
|
+
}
|
|
12927
|
+
const handled = await handleSessions(
|
|
12928
|
+
req,
|
|
12929
|
+
res,
|
|
12930
|
+
path,
|
|
12931
|
+
opts.sessions,
|
|
12932
|
+
opts.resolveAgentAdapter,
|
|
12933
|
+
opts.ptyEnabled === true,
|
|
12934
|
+
opts.resolveBrowserAdapter,
|
|
12935
|
+
opts.listBrowserAdapters,
|
|
12936
|
+
opts.sessionEvents,
|
|
12937
|
+
opts.eventRing,
|
|
12938
|
+
opts.buildOrchestratorMcp,
|
|
12939
|
+
opts.daemonMcpUrl,
|
|
12940
|
+
opts.provisionWorktree
|
|
12941
|
+
);
|
|
12942
|
+
if (handled) return;
|
|
12943
|
+
}
|
|
12944
|
+
if (path === "/workspaces" && req.method === "GET") {
|
|
12945
|
+
try {
|
|
12946
|
+
const config = await loadWorkspacesConfig();
|
|
12947
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
12948
|
+
res.end(JSON.stringify(config));
|
|
12949
|
+
} catch (err) {
|
|
12950
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
12951
|
+
res.end(
|
|
12952
|
+
JSON.stringify({
|
|
12953
|
+
error: "workspaces_load_failed",
|
|
12954
|
+
message: err instanceof Error ? err.message : String(err)
|
|
12955
|
+
})
|
|
12956
|
+
);
|
|
12957
|
+
}
|
|
12958
|
+
return;
|
|
12959
|
+
}
|
|
12960
|
+
if (path === "/workspaces" && req.method === "POST") {
|
|
12961
|
+
const gate = checkSessionsToken(req);
|
|
12962
|
+
if (gate !== "ok") {
|
|
12963
|
+
rejectUnauthorizedSession(req, res, gate);
|
|
12964
|
+
return;
|
|
12965
|
+
}
|
|
12966
|
+
const body = await readJsonBody(req);
|
|
12967
|
+
if (!body || typeof body.path !== "string" || !body.path) {
|
|
12968
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
12969
|
+
res.end(JSON.stringify({ error: "missing_path" }));
|
|
12970
|
+
return;
|
|
12971
|
+
}
|
|
12972
|
+
if (!isAbsolute(body.path)) {
|
|
12973
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
12974
|
+
res.end(
|
|
12975
|
+
JSON.stringify({
|
|
12976
|
+
error: "workspace_path_not_absolute",
|
|
12977
|
+
message: `path must be absolute, got "${body.path}".`
|
|
12978
|
+
})
|
|
12979
|
+
);
|
|
12980
|
+
return;
|
|
12981
|
+
}
|
|
12982
|
+
try {
|
|
12983
|
+
await stat(body.path);
|
|
12984
|
+
} catch {
|
|
12985
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
12986
|
+
res.end(
|
|
12987
|
+
JSON.stringify({
|
|
12988
|
+
error: "workspace_path_not_found",
|
|
12989
|
+
message: `"${body.path}" doesn't exist.`
|
|
12990
|
+
})
|
|
12991
|
+
);
|
|
12992
|
+
return;
|
|
12993
|
+
}
|
|
12994
|
+
if (body.slug !== void 0 && typeof body.slug !== "string") {
|
|
12995
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
12996
|
+
res.end(JSON.stringify({ error: "invalid_slug" }));
|
|
12997
|
+
return;
|
|
12998
|
+
}
|
|
12999
|
+
if (body.label !== void 0 && typeof body.label !== "string") {
|
|
13000
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
13001
|
+
res.end(JSON.stringify({ error: "invalid_label" }));
|
|
13002
|
+
return;
|
|
13003
|
+
}
|
|
13004
|
+
try {
|
|
13005
|
+
const config = await loadWorkspacesConfig();
|
|
13006
|
+
const next = addWorkspace(config, {
|
|
13007
|
+
slug: body.slug || basename(body.path),
|
|
13008
|
+
path: body.path,
|
|
13009
|
+
...body.label ? { label: body.label } : {}
|
|
13010
|
+
});
|
|
13011
|
+
await saveWorkspacesConfig(next);
|
|
13012
|
+
res.writeHead(201, { "content-type": "application/json" });
|
|
13013
|
+
res.end(JSON.stringify(next));
|
|
13014
|
+
} catch (err) {
|
|
13015
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
13016
|
+
res.end(
|
|
13017
|
+
JSON.stringify({
|
|
13018
|
+
error: "workspace_add_failed",
|
|
13019
|
+
message: err instanceof Error ? err.message : String(err)
|
|
13020
|
+
})
|
|
13021
|
+
);
|
|
13022
|
+
}
|
|
13023
|
+
return;
|
|
13024
|
+
}
|
|
13025
|
+
if (path === "/workspaces/active" && req.method === "PUT") {
|
|
11844
13026
|
const gate = checkSessionsToken(req);
|
|
11845
13027
|
if (gate !== "ok") {
|
|
11846
13028
|
rejectUnauthorizedSession(req, res, gate);
|
|
11847
13029
|
return;
|
|
11848
13030
|
}
|
|
11849
|
-
await
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
const gate = checkSessionsToken(req);
|
|
11855
|
-
if (gate !== "ok") {
|
|
11856
|
-
rejectUnauthorizedSession(req, res, gate);
|
|
11857
|
-
return;
|
|
11858
|
-
}
|
|
13031
|
+
const body = await readJsonBody(req);
|
|
13032
|
+
if (!body || typeof body.slug !== "string" || !body.slug) {
|
|
13033
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
13034
|
+
res.end(JSON.stringify({ error: "missing_slug" }));
|
|
13035
|
+
return;
|
|
11859
13036
|
}
|
|
11860
|
-
const handled = await handleSessions(
|
|
11861
|
-
req,
|
|
11862
|
-
res,
|
|
11863
|
-
path,
|
|
11864
|
-
opts.sessions,
|
|
11865
|
-
opts.resolveAgentAdapter,
|
|
11866
|
-
opts.ptyEnabled === true,
|
|
11867
|
-
opts.resolveBrowserAdapter,
|
|
11868
|
-
opts.listBrowserAdapters,
|
|
11869
|
-
opts.sessionEvents,
|
|
11870
|
-
opts.eventRing,
|
|
11871
|
-
opts.buildOrchestratorMcp,
|
|
11872
|
-
opts.daemonMcpUrl,
|
|
11873
|
-
opts.provisionWorktree
|
|
11874
|
-
);
|
|
11875
|
-
if (handled) return;
|
|
11876
|
-
}
|
|
11877
|
-
if (path === "/workspaces" && req.method === "GET") {
|
|
11878
13037
|
try {
|
|
11879
13038
|
const config = await loadWorkspacesConfig();
|
|
13039
|
+
const next = setActiveWorkspace(config, body.slug);
|
|
13040
|
+
await saveWorkspacesConfig(next);
|
|
11880
13041
|
res.writeHead(200, { "content-type": "application/json" });
|
|
11881
|
-
res.end(JSON.stringify(
|
|
13042
|
+
res.end(JSON.stringify(next));
|
|
11882
13043
|
} catch (err) {
|
|
11883
|
-
res.writeHead(
|
|
13044
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
11884
13045
|
res.end(
|
|
11885
13046
|
JSON.stringify({
|
|
11886
|
-
error: "
|
|
13047
|
+
error: "workspace_not_found",
|
|
11887
13048
|
message: err instanceof Error ? err.message : String(err)
|
|
11888
13049
|
})
|
|
11889
13050
|
);
|
|
11890
13051
|
}
|
|
11891
13052
|
return;
|
|
11892
13053
|
}
|
|
13054
|
+
const workspaceSlugMatch = path.match(/^\/workspaces\/([^/]+)$/);
|
|
13055
|
+
if (workspaceSlugMatch && req.method === "DELETE") {
|
|
13056
|
+
const gate = checkSessionsToken(req);
|
|
13057
|
+
if (gate !== "ok") {
|
|
13058
|
+
rejectUnauthorizedSession(req, res, gate);
|
|
13059
|
+
return;
|
|
13060
|
+
}
|
|
13061
|
+
const slug = decodeURIComponent(workspaceSlugMatch[1] ?? "");
|
|
13062
|
+
const config = await loadWorkspacesConfig();
|
|
13063
|
+
if (!findWorkspace(config, slug)) {
|
|
13064
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
13065
|
+
res.end(JSON.stringify({ error: "workspace_not_found", slug }));
|
|
13066
|
+
return;
|
|
13067
|
+
}
|
|
13068
|
+
const next = removeWorkspace(config, slug);
|
|
13069
|
+
await saveWorkspacesConfig(next);
|
|
13070
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
13071
|
+
res.end(JSON.stringify(next));
|
|
13072
|
+
return;
|
|
13073
|
+
}
|
|
11893
13074
|
if (path === "/mcps/imports" && req.method === "GET") {
|
|
11894
13075
|
const config = await loadImportedMcps();
|
|
11895
13076
|
res.writeHead(200, { "content-type": "application/json" });
|
|
@@ -12045,6 +13226,41 @@ async function startHttpServer(opts) {
|
|
|
12045
13226
|
}
|
|
12046
13227
|
return;
|
|
12047
13228
|
}
|
|
13229
|
+
if (path === "/catalog/models" && req.method === "GET") {
|
|
13230
|
+
if (!opts.listCatalogModels) {
|
|
13231
|
+
res.writeHead(501, { "content-type": "application/json" });
|
|
13232
|
+
res.end(
|
|
13233
|
+
JSON.stringify({
|
|
13234
|
+
error: "lister_not_configured",
|
|
13235
|
+
message: "Daemon was started without `listCatalogModels` \u2014 see `buildCatalogModels` in `catalog-models.ts`."
|
|
13236
|
+
})
|
|
13237
|
+
);
|
|
13238
|
+
return;
|
|
13239
|
+
}
|
|
13240
|
+
try {
|
|
13241
|
+
const qs = new URLSearchParams(
|
|
13242
|
+
url.includes("?") ? url.slice(url.indexOf("?") + 1) : ""
|
|
13243
|
+
);
|
|
13244
|
+
const runnableOnlyParam = qs.get("runnableOnly");
|
|
13245
|
+
const catalog = await opts.listCatalogModels({
|
|
13246
|
+
...qs.get("adapter") ? { adapter: qs.get("adapter") } : {},
|
|
13247
|
+
...qs.get("vendor") ? { vendor: qs.get("vendor") } : {},
|
|
13248
|
+
...qs.get("route") ? { route: qs.get("route") } : {},
|
|
13249
|
+
...runnableOnlyParam === "true" ? { runnableOnly: true } : {}
|
|
13250
|
+
});
|
|
13251
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
13252
|
+
res.end(JSON.stringify(catalog));
|
|
13253
|
+
} catch (err) {
|
|
13254
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
13255
|
+
res.end(
|
|
13256
|
+
JSON.stringify({
|
|
13257
|
+
error: "list_failed",
|
|
13258
|
+
message: err instanceof Error ? err.message : String(err)
|
|
13259
|
+
})
|
|
13260
|
+
);
|
|
13261
|
+
}
|
|
13262
|
+
return;
|
|
13263
|
+
}
|
|
12048
13264
|
if (path === "/presets" && req.method === "GET") {
|
|
12049
13265
|
const handled = await handlePresets(req, res, path);
|
|
12050
13266
|
if (handled) return;
|
|
@@ -12400,7 +13616,10 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
12400
13616
|
res.end(JSON.stringify(body));
|
|
12401
13617
|
};
|
|
12402
13618
|
if (path === "/sessions" && req.method === "GET") {
|
|
12403
|
-
|
|
13619
|
+
const reqUrl = req.url ?? "";
|
|
13620
|
+
const queryString = reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : "";
|
|
13621
|
+
const includeArchived = new URLSearchParams(queryString).get("includeArchived") === "true";
|
|
13622
|
+
json(200, { sessions: registry.list({ includeArchived }) });
|
|
12404
13623
|
return true;
|
|
12405
13624
|
}
|
|
12406
13625
|
if (path === "/sessions/agent" && req.method === "POST") {
|
|
@@ -12485,7 +13704,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
12485
13704
|
}
|
|
12486
13705
|
);
|
|
12487
13706
|
if (!result.ok) {
|
|
12488
|
-
const status = result.code === "adapter_not_found" || result.code === "no_cwd" ? 404 : result.code === "orchestrator_not_enabled" ? 501 : result.code === "orchestrator_max_depth_exceeded" || result.code === "orchestrator_child_quota_exceeded" || result.code === "role_spawn_denied" ? 409 : result.code === "invalid_role" ? 400 : 500;
|
|
13707
|
+
const status = result.code === "adapter_not_found" || result.code === "no_cwd" ? 404 : result.code === "orchestrator_not_enabled" ? 501 : result.code === "orchestrator_max_depth_exceeded" || result.code === "orchestrator_child_quota_exceeded" || result.code === "role_spawn_denied" ? 409 : result.code === "invalid_role" || result.code === "worktree_requires_explicit_repo" ? 400 : 500;
|
|
12489
13708
|
json(status, {
|
|
12490
13709
|
error: result.code,
|
|
12491
13710
|
message: result.message,
|
|
@@ -12704,6 +13923,129 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
12704
13923
|
}
|
|
12705
13924
|
return true;
|
|
12706
13925
|
}
|
|
13926
|
+
const modelMatch = path.match(/^\/sessions\/([^/]+)\/model$/);
|
|
13927
|
+
if (modelMatch && req.method === "POST") {
|
|
13928
|
+
const id2 = modelMatch[1];
|
|
13929
|
+
if (!id2) return false;
|
|
13930
|
+
const body = await readJsonBody(req);
|
|
13931
|
+
const model = body && typeof body === "object" && typeof body.model === "string" ? body.model : void 0;
|
|
13932
|
+
if (!model) {
|
|
13933
|
+
json(400, { error: "missing_model" });
|
|
13934
|
+
return true;
|
|
13935
|
+
}
|
|
13936
|
+
try {
|
|
13937
|
+
const result = await registry.setModel(id2, model);
|
|
13938
|
+
json(200, { ok: true, id: id2, ...result });
|
|
13939
|
+
} catch (err) {
|
|
13940
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13941
|
+
const status = msg.includes("no session") ? 404 : msg.includes("not an agent-cli session") ? 400 : 500;
|
|
13942
|
+
json(status, { error: "set_model_failed", message: msg });
|
|
13943
|
+
}
|
|
13944
|
+
return true;
|
|
13945
|
+
}
|
|
13946
|
+
const restartMatch = path.match(/^\/sessions\/([^/]+)\/restart$/);
|
|
13947
|
+
if (restartMatch && req.method === "POST") {
|
|
13948
|
+
const id2 = restartMatch[1];
|
|
13949
|
+
if (!id2) return false;
|
|
13950
|
+
if (!resolveAgentAdapter) {
|
|
13951
|
+
json(501, {
|
|
13952
|
+
error: "restart_not_enabled",
|
|
13953
|
+
message: "POST /sessions/:id/restart needs the host to inject `resolveAgentAdapter`."
|
|
13954
|
+
});
|
|
13955
|
+
return true;
|
|
13956
|
+
}
|
|
13957
|
+
const prev = registry.findByIdOrName(id2);
|
|
13958
|
+
if (!prev) {
|
|
13959
|
+
json(404, { error: "no_session", message: `no session "${id2}" found` });
|
|
13960
|
+
return true;
|
|
13961
|
+
}
|
|
13962
|
+
if (!prev.adapterSlug) {
|
|
13963
|
+
json(400, {
|
|
13964
|
+
error: "restart_override_invalid",
|
|
13965
|
+
message: "restart-with-override only applies to agent-cli sessions (a PTY/command session has no config axes to override)."
|
|
13966
|
+
});
|
|
13967
|
+
return true;
|
|
13968
|
+
}
|
|
13969
|
+
const body = await readJsonBody(req);
|
|
13970
|
+
const b = body && typeof body === "object" ? body : {};
|
|
13971
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
13972
|
+
const overrides = {
|
|
13973
|
+
...str(b.model) !== void 0 ? { model: str(b.model) } : {},
|
|
13974
|
+
...str(b.effort) !== void 0 ? { effort: str(b.effort) } : {},
|
|
13975
|
+
...b.access && typeof b.access === "object" && str(b.access.profileRef) !== void 0 ? { access: { profileRef: str(b.access.profileRef) } } : {},
|
|
13976
|
+
...b.route && typeof b.route === "object" && str(b.route.gateway) !== void 0 ? {
|
|
13977
|
+
route: {
|
|
13978
|
+
gateway: str(b.route.gateway),
|
|
13979
|
+
...str(b.route.baseUrl) !== void 0 ? { baseUrl: str(b.route.baseUrl) } : {}
|
|
13980
|
+
}
|
|
13981
|
+
} : {},
|
|
13982
|
+
...b.posture !== void 0 ? { posture: b.posture } : {},
|
|
13983
|
+
...str(b.contextProfile) !== void 0 ? { contextProfile: str(b.contextProfile) } : {},
|
|
13984
|
+
...str(b.mode) !== void 0 ? { mode: str(b.mode) } : {}
|
|
13985
|
+
};
|
|
13986
|
+
try {
|
|
13987
|
+
const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
|
|
13988
|
+
forceAgentResume: true,
|
|
13989
|
+
overrides
|
|
13990
|
+
});
|
|
13991
|
+
json(200, {
|
|
13992
|
+
...restarted.desc,
|
|
13993
|
+
resumedFrom: restarted.resumedFrom,
|
|
13994
|
+
resumeVia: restarted.resumeVia,
|
|
13995
|
+
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
13996
|
+
});
|
|
13997
|
+
} catch (err) {
|
|
13998
|
+
if (err instanceof RestartOverrideError) {
|
|
13999
|
+
json(err.status, { error: err.code, message: err.message, sessionId: prev.id });
|
|
14000
|
+
return true;
|
|
14001
|
+
}
|
|
14002
|
+
json(500, {
|
|
14003
|
+
error: "restart_failed",
|
|
14004
|
+
message: err instanceof Error ? err.message : String(err)
|
|
14005
|
+
});
|
|
14006
|
+
}
|
|
14007
|
+
return true;
|
|
14008
|
+
}
|
|
14009
|
+
const effortMatch = path.match(/^\/sessions\/([^/]+)\/effort$/);
|
|
14010
|
+
if (effortMatch && req.method === "POST") {
|
|
14011
|
+
const id2 = effortMatch[1];
|
|
14012
|
+
if (!id2) return false;
|
|
14013
|
+
const body = await readJsonBody(req);
|
|
14014
|
+
const effort = body && typeof body === "object" && typeof body.effort === "string" ? body.effort : void 0;
|
|
14015
|
+
if (!effort) {
|
|
14016
|
+
json(400, { error: "missing_effort" });
|
|
14017
|
+
return true;
|
|
14018
|
+
}
|
|
14019
|
+
try {
|
|
14020
|
+
const result = await registry.setEffort(id2, effort);
|
|
14021
|
+
json(200, { ok: true, id: id2, ...result });
|
|
14022
|
+
} catch (err) {
|
|
14023
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14024
|
+
const status = msg.includes("no session") ? 404 : msg.includes("not an agent-cli session") ? 400 : 500;
|
|
14025
|
+
json(status, { error: "set_effort_failed", message: msg });
|
|
14026
|
+
}
|
|
14027
|
+
return true;
|
|
14028
|
+
}
|
|
14029
|
+
const postureMatch = path.match(/^\/sessions\/([^/]+)\/posture$/);
|
|
14030
|
+
if (postureMatch && req.method === "POST") {
|
|
14031
|
+
const id2 = postureMatch[1];
|
|
14032
|
+
if (!id2) return false;
|
|
14033
|
+
const body = await readJsonBody(req);
|
|
14034
|
+
const postureRaw = body && typeof body === "object" && typeof body.posture === "string" ? body.posture : void 0;
|
|
14035
|
+
if (!postureRaw) {
|
|
14036
|
+
json(400, { error: "missing_posture" });
|
|
14037
|
+
return true;
|
|
14038
|
+
}
|
|
14039
|
+
try {
|
|
14040
|
+
const result = await registry.setPosture(id2, parsePostureInput(postureRaw));
|
|
14041
|
+
json(200, { ok: true, id: id2, ...result });
|
|
14042
|
+
} catch (err) {
|
|
14043
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14044
|
+
const status = msg.includes("no session") ? 404 : msg.includes("not an agent-cli session") ? 400 : 500;
|
|
14045
|
+
json(status, { error: "set_posture_failed", message: msg });
|
|
14046
|
+
}
|
|
14047
|
+
return true;
|
|
14048
|
+
}
|
|
12707
14049
|
if (path === "/sessions" && req.method === "POST") {
|
|
12708
14050
|
const body = await readJsonBody(req);
|
|
12709
14051
|
if (!body || typeof body !== "object") {
|
|
@@ -14544,8 +15886,6 @@ function createRoutineRunner(opts) {
|
|
|
14544
15886
|
}
|
|
14545
15887
|
};
|
|
14546
15888
|
}
|
|
14547
|
-
|
|
14548
|
-
// src/sessions-registry-agent-host.ts
|
|
14549
15889
|
init_transcript_export();
|
|
14550
15890
|
var SessionsRegistryAgentHost = class {
|
|
14551
15891
|
constructor(registry, sessionEvents, resolveAgentAdapter, opts) {
|
|
@@ -14560,10 +15900,45 @@ var SessionsRegistryAgentHost = class {
|
|
|
14560
15900
|
opts;
|
|
14561
15901
|
sessionsByLabel = /* @__PURE__ */ new Map();
|
|
14562
15902
|
async spawn(adapter, opts) {
|
|
14563
|
-
const resolved = await this.resolveAgentAdapter(adapter);
|
|
14564
|
-
if (!resolved) throw new Error(`adapter '${adapter}' not found`);
|
|
14565
15903
|
const workspaceSlug = opts.workspaceSlug ?? this.opts?.workspaceSlug ?? "default";
|
|
14566
15904
|
const cwd = opts.cwd ?? this.opts?.cwd ?? process.cwd();
|
|
15905
|
+
if (opts.sandbox !== void 0) {
|
|
15906
|
+
let sandbox;
|
|
15907
|
+
if (typeof opts.sandbox === "string") {
|
|
15908
|
+
sandbox = opts.sandbox;
|
|
15909
|
+
} else {
|
|
15910
|
+
const parsed = SandboxSpecSchema.safeParse({ config: {}, ...opts.sandbox });
|
|
15911
|
+
if (!parsed.success) {
|
|
15912
|
+
throw new Error(
|
|
15913
|
+
`agent step sandbox spec invalid (provider "${opts.sandbox.provider}"): ${parsed.error.message}`
|
|
15914
|
+
);
|
|
15915
|
+
}
|
|
15916
|
+
sandbox = parsed.data;
|
|
15917
|
+
}
|
|
15918
|
+
const result = await spawnAgentSession(
|
|
15919
|
+
{
|
|
15920
|
+
registry: this.registry,
|
|
15921
|
+
resolveAgentAdapter: this.resolveAgentAdapter,
|
|
15922
|
+
...this.opts?.resolveSandboxProvider ? { resolveSandboxProvider: this.opts.resolveSandboxProvider } : {}
|
|
15923
|
+
},
|
|
15924
|
+
{
|
|
15925
|
+
adapter,
|
|
15926
|
+
cwd,
|
|
15927
|
+
workspaceSlug,
|
|
15928
|
+
sandbox,
|
|
15929
|
+
label: `agent-step:${adapter}`
|
|
15930
|
+
}
|
|
15931
|
+
);
|
|
15932
|
+
if (!result.ok) {
|
|
15933
|
+
throw new Error(`agent step sandbox spawn failed (${result.code}): ${result.message}`);
|
|
15934
|
+
}
|
|
15935
|
+
if (opts.stepId) {
|
|
15936
|
+
this.sessionsByLabel.set(opts.stepId, result.descriptor.id);
|
|
15937
|
+
}
|
|
15938
|
+
return result.descriptor.id;
|
|
15939
|
+
}
|
|
15940
|
+
const resolved = await this.resolveAgentAdapter(adapter);
|
|
15941
|
+
if (!resolved) throw new Error(`adapter '${adapter}' not found`);
|
|
14567
15942
|
const agentSession = await resolved.startSession({ cwd });
|
|
14568
15943
|
const desc = this.registry.spawnAgent({
|
|
14569
15944
|
workspaceSlug,
|
|
@@ -14658,7 +16033,14 @@ var SessionsRegistryAgentHost = class {
|
|
|
14658
16033
|
};
|
|
14659
16034
|
unsubs.push(
|
|
14660
16035
|
this.sessionEvents.on("session:turn-end", (ev) => {
|
|
14661
|
-
if (ev.sessionId
|
|
16036
|
+
if (ev.sessionId !== sessionId) return;
|
|
16037
|
+
if (ev.empty === true) {
|
|
16038
|
+
fail(
|
|
16039
|
+
`session ${sessionId} produced an empty turn \u2014 no assistant output or tool call (commonly an auth failure or an invalid model id)`
|
|
16040
|
+
);
|
|
16041
|
+
} else {
|
|
16042
|
+
done();
|
|
16043
|
+
}
|
|
14662
16044
|
})
|
|
14663
16045
|
);
|
|
14664
16046
|
unsubs.push(
|
|
@@ -14739,6 +16121,7 @@ function translateStages(stages, workflowId) {
|
|
|
14739
16121
|
id: step.label,
|
|
14740
16122
|
...step.adapter !== void 0 ? { adapter: step.adapter } : {},
|
|
14741
16123
|
...step.sessionRef !== void 0 ? { sessionRef: step.sessionRef } : {},
|
|
16124
|
+
...step.sandbox !== void 0 ? { sandbox: step.sandbox } : {},
|
|
14742
16125
|
...step.cacheable ? { cacheable: true } : {},
|
|
14743
16126
|
prompt: () => step.prompt ?? "",
|
|
14744
16127
|
policy: step.policy ?? { awaiting: "fail" }
|
|
@@ -14756,6 +16139,45 @@ function translateStages(stages, workflowId) {
|
|
|
14756
16139
|
steps
|
|
14757
16140
|
};
|
|
14758
16141
|
}
|
|
16142
|
+
function collectAgentSteps(steps) {
|
|
16143
|
+
const collected = [];
|
|
16144
|
+
for (const step of steps) {
|
|
16145
|
+
if (step.kind === "agent") {
|
|
16146
|
+
const adapter = typeof step.adapter === "string" ? step.adapter : void 0;
|
|
16147
|
+
collected.push({ id: step.id, adapter, sessionRef: step.sessionRef });
|
|
16148
|
+
} else if (step.kind === "parallel") {
|
|
16149
|
+
for (const branch of step.branches) collected.push(...collectAgentSteps(branch.steps));
|
|
16150
|
+
} else if (step.kind === "group") {
|
|
16151
|
+
collected.push(...collectAgentSteps(step.steps));
|
|
16152
|
+
} else if (step.kind === "map") ; else if (step.kind === "pipeline") {
|
|
16153
|
+
for (const stage of step.stages) {
|
|
16154
|
+
}
|
|
16155
|
+
} else if (step.kind === "branch") {
|
|
16156
|
+
collected.push(...collectAgentSteps(step.then));
|
|
16157
|
+
if (step.otherwise) collected.push(...collectAgentSteps(step.otherwise));
|
|
16158
|
+
} else if (step.kind === "loop") {
|
|
16159
|
+
collected.push(...collectAgentSteps(step.body));
|
|
16160
|
+
} else if (step.kind === "subworkflow") {
|
|
16161
|
+
collected.push(...collectAgentSteps(step.workflow.steps));
|
|
16162
|
+
}
|
|
16163
|
+
}
|
|
16164
|
+
return collected;
|
|
16165
|
+
}
|
|
16166
|
+
function runtimeWorkflowToStages(workflow) {
|
|
16167
|
+
const agents = collectAgentSteps(workflow.steps);
|
|
16168
|
+
if (agents.length === 0) {
|
|
16169
|
+
return [{ steps: [{ label: "workflow" }] }];
|
|
16170
|
+
}
|
|
16171
|
+
return [
|
|
16172
|
+
{
|
|
16173
|
+
steps: agents.map((a) => ({
|
|
16174
|
+
label: a.id,
|
|
16175
|
+
...a.adapter !== void 0 ? { adapter: a.adapter } : {},
|
|
16176
|
+
...a.sessionRef !== void 0 ? { sessionRef: a.sessionRef } : {}
|
|
16177
|
+
}))
|
|
16178
|
+
}
|
|
16179
|
+
];
|
|
16180
|
+
}
|
|
14759
16181
|
var DEFAULT_PERSIST_PATH2 = () => join(homedir(), ".agentproto", "workflow-runs.json");
|
|
14760
16182
|
function loadRuns2(persistPath) {
|
|
14761
16183
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -14813,13 +16235,10 @@ function fireNotifyUrl(run) {
|
|
|
14813
16235
|
}).catch(() => void 0);
|
|
14814
16236
|
}
|
|
14815
16237
|
function resolveStepSessionId(step, agents) {
|
|
14816
|
-
if (step.adapter) {
|
|
14817
|
-
return agents.resolveByLabel(step.label);
|
|
14818
|
-
}
|
|
14819
16238
|
if (step.sessionRef) {
|
|
14820
16239
|
return agents.resolveByLabel(step.sessionRef);
|
|
14821
16240
|
}
|
|
14822
|
-
return
|
|
16241
|
+
return agents.resolveByLabel(step.label);
|
|
14823
16242
|
}
|
|
14824
16243
|
function fillStepStates(stages, defs, agents) {
|
|
14825
16244
|
const sessionIds = [];
|
|
@@ -14879,6 +16298,8 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cache
|
|
|
14879
16298
|
}
|
|
14880
16299
|
}
|
|
14881
16300
|
}
|
|
16301
|
+
const sessionIds = fillStepStates(state.run.stages, state.stages, agents);
|
|
16302
|
+
if (sessionIds.length > 0) state.run.result = { sessionIds };
|
|
14882
16303
|
}
|
|
14883
16304
|
}
|
|
14884
16305
|
fireNotifyUrl(state.run);
|
|
@@ -14930,7 +16351,8 @@ function createWorkflowRunner(opts) {
|
|
|
14930
16351
|
{
|
|
14931
16352
|
workspaceSlug: input.workspaceSlug,
|
|
14932
16353
|
cwd: input.cwd,
|
|
14933
|
-
notifyUrl: input.notifyUrl
|
|
16354
|
+
notifyUrl: input.notifyUrl,
|
|
16355
|
+
...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
|
|
14934
16356
|
}
|
|
14935
16357
|
);
|
|
14936
16358
|
const cache = input.cacheKey ? createFileStepCache(input.cacheKey) : void 0;
|
|
@@ -14947,26 +16369,30 @@ function createWorkflowRunner(opts) {
|
|
|
14947
16369
|
}
|
|
14948
16370
|
const handle = await loadWorkflowHandle(args.path);
|
|
14949
16371
|
const workflow = await compileWorkflow2(handle);
|
|
16372
|
+
const fileStages = runtimeWorkflowToStages(workflow);
|
|
14950
16373
|
const runId = `wfrun_${randomUUID()}`;
|
|
14951
16374
|
const run = {
|
|
14952
16375
|
runId,
|
|
14953
16376
|
workflowId: handle.id,
|
|
14954
16377
|
status: "running",
|
|
14955
16378
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14956
|
-
stages:
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
16379
|
+
stages: fileStages.map((stage, si) => ({
|
|
16380
|
+
index: si,
|
|
16381
|
+
...stage.label !== void 0 ? { label: stage.label } : {},
|
|
16382
|
+
status: "pending",
|
|
16383
|
+
steps: stage.steps.map((s, i) => ({
|
|
16384
|
+
index: i,
|
|
16385
|
+
label: s.label,
|
|
16386
|
+
status: "pending"
|
|
16387
|
+
}))
|
|
16388
|
+
}))
|
|
14963
16389
|
};
|
|
14964
16390
|
const abort = new AbortController();
|
|
14965
16391
|
const state = {
|
|
14966
16392
|
run,
|
|
14967
16393
|
cancelled: false,
|
|
14968
16394
|
abort,
|
|
14969
|
-
stages:
|
|
16395
|
+
stages: fileStages,
|
|
14970
16396
|
...args.cwd !== void 0 ? { cwd: args.cwd } : {},
|
|
14971
16397
|
...args.workspaceSlug !== void 0 ? { workspaceSlug: args.workspaceSlug } : {}
|
|
14972
16398
|
};
|
|
@@ -14978,7 +16404,8 @@ function createWorkflowRunner(opts) {
|
|
|
14978
16404
|
resolveAgentAdapter,
|
|
14979
16405
|
{
|
|
14980
16406
|
workspaceSlug: args.workspaceSlug,
|
|
14981
|
-
cwd: args.cwd
|
|
16407
|
+
cwd: args.cwd,
|
|
16408
|
+
...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
|
|
14982
16409
|
}
|
|
14983
16410
|
);
|
|
14984
16411
|
const cache = args.cacheKey ? createFileStepCache(args.cacheKey) : void 0;
|
|
@@ -15743,6 +17170,216 @@ function createOrchestratorInjector(deps2) {
|
|
|
15743
17170
|
return { entry, scope, bindLifecycle };
|
|
15744
17171
|
};
|
|
15745
17172
|
}
|
|
17173
|
+
var WIDENING_ROUTES = ["openrouter", "requesty", "huggingface"];
|
|
17174
|
+
function normalizeRouterPrefixedId(id) {
|
|
17175
|
+
const firstSlash = id.indexOf("/");
|
|
17176
|
+
if (firstSlash === -1) return id;
|
|
17177
|
+
const head = id.slice(0, firstSlash);
|
|
17178
|
+
if (!WIDENING_ROUTES.includes(head)) return id;
|
|
17179
|
+
const remainder = id.slice(firstSlash + 1);
|
|
17180
|
+
if (!remainder.includes("/") || remainder.includes("@")) return id;
|
|
17181
|
+
return `${remainder}@${head}`;
|
|
17182
|
+
}
|
|
17183
|
+
function tryResolveLlmModelRoute(id) {
|
|
17184
|
+
try {
|
|
17185
|
+
return resolveLlmModelRoute(id);
|
|
17186
|
+
} catch {
|
|
17187
|
+
return void 0;
|
|
17188
|
+
}
|
|
17189
|
+
}
|
|
17190
|
+
function vendorFromIdPrefix(bareId) {
|
|
17191
|
+
if (/^claude[-/]/.test(bareId)) return "anthropic";
|
|
17192
|
+
if (/^(gpt[-/]|o[1-9](-|$)|chatgpt)/.test(bareId)) return "openai";
|
|
17193
|
+
if (/^gemini[-/]/.test(bareId)) return "google";
|
|
17194
|
+
if (/^grok[-/]/.test(bareId)) return "x-ai";
|
|
17195
|
+
if (/^deepseek[-/]/.test(bareId)) return "deepseek";
|
|
17196
|
+
return void 0;
|
|
17197
|
+
}
|
|
17198
|
+
function resolveModelId(id) {
|
|
17199
|
+
const normalized = normalizeRouterPrefixedId(id);
|
|
17200
|
+
const resolved = tryResolveLlmModelRoute(normalized);
|
|
17201
|
+
if (resolved) {
|
|
17202
|
+
return {
|
|
17203
|
+
vendor: resolved.vendor,
|
|
17204
|
+
product: resolved.product,
|
|
17205
|
+
directRoute: resolved.route,
|
|
17206
|
+
ref: formatModelRef(resolved.ref),
|
|
17207
|
+
baseUrl: resolved.transport.baseUrl ?? null,
|
|
17208
|
+
pricing: {
|
|
17209
|
+
inPer1M: resolved.pricing.inputPer1M,
|
|
17210
|
+
outPer1M: resolved.pricing.outputPer1M
|
|
17211
|
+
}
|
|
17212
|
+
};
|
|
17213
|
+
}
|
|
17214
|
+
const parsed = tryParseModelRef(normalized);
|
|
17215
|
+
if (parsed) {
|
|
17216
|
+
return {
|
|
17217
|
+
vendor: parsed.vendor,
|
|
17218
|
+
product: parsed.product,
|
|
17219
|
+
directRoute: parsed.route,
|
|
17220
|
+
ref: formatModelRef(parsed),
|
|
17221
|
+
baseUrl: null,
|
|
17222
|
+
pricing: null
|
|
17223
|
+
};
|
|
17224
|
+
}
|
|
17225
|
+
const vendor = vendorFromIdPrefix(id) ?? "unknown";
|
|
17226
|
+
return {
|
|
17227
|
+
vendor,
|
|
17228
|
+
product: id,
|
|
17229
|
+
directRoute: vendor,
|
|
17230
|
+
ref: `${vendor}/${id}`,
|
|
17231
|
+
baseUrl: null,
|
|
17232
|
+
pricing: null
|
|
17233
|
+
};
|
|
17234
|
+
}
|
|
17235
|
+
function methodsForDirect(descriptor) {
|
|
17236
|
+
const methods = [];
|
|
17237
|
+
if (descriptor?.authSubscription) methods.push("oauth-bearer");
|
|
17238
|
+
if (descriptor?.provider) methods.push("api-key");
|
|
17239
|
+
return methods;
|
|
17240
|
+
}
|
|
17241
|
+
function isDirectRoute(mode, resolved) {
|
|
17242
|
+
return mode === void 0 && resolved.directRoute === resolved.vendor;
|
|
17243
|
+
}
|
|
17244
|
+
function curatedContributions(adapters) {
|
|
17245
|
+
const out = [];
|
|
17246
|
+
for (const adapter of adapters) {
|
|
17247
|
+
for (const model of adapter.models) {
|
|
17248
|
+
const resolved = resolveModelId(model.id);
|
|
17249
|
+
const route = model.mode ?? resolved.directRoute;
|
|
17250
|
+
const methods = isDirectRoute(model.mode, resolved) ? methodsForDirect(adapter.authDescriptor) : ["api-key"];
|
|
17251
|
+
out.push({
|
|
17252
|
+
vendor: resolved.vendor,
|
|
17253
|
+
product: resolved.product,
|
|
17254
|
+
route,
|
|
17255
|
+
ref: resolved.ref,
|
|
17256
|
+
baseUrl: resolved.baseUrl,
|
|
17257
|
+
pricing: resolved.pricing,
|
|
17258
|
+
curated: true,
|
|
17259
|
+
adapterSlug: adapter.slug,
|
|
17260
|
+
...model.mode ? { adapterMode: model.mode } : {},
|
|
17261
|
+
methods
|
|
17262
|
+
});
|
|
17263
|
+
}
|
|
17264
|
+
}
|
|
17265
|
+
return out;
|
|
17266
|
+
}
|
|
17267
|
+
function widenedContributions(curated) {
|
|
17268
|
+
const seenProducts = /* @__PURE__ */ new Map();
|
|
17269
|
+
for (const c of curated) {
|
|
17270
|
+
const key = `${c.vendor}/${c.product}`;
|
|
17271
|
+
const routes = seenProducts.get(key) ?? /* @__PURE__ */ new Set();
|
|
17272
|
+
routes.add(c.route);
|
|
17273
|
+
seenProducts.set(key, routes);
|
|
17274
|
+
}
|
|
17275
|
+
const out = [];
|
|
17276
|
+
for (const [key, existingRoutes] of seenProducts) {
|
|
17277
|
+
const [vendor, product] = key.split("/", 2);
|
|
17278
|
+
for (const router of WIDENING_ROUTES) {
|
|
17279
|
+
if (existingRoutes.has(router)) continue;
|
|
17280
|
+
const resolved = resolveLlmModelRoute(`${vendor}/${product}@${router}`);
|
|
17281
|
+
if (!resolved) continue;
|
|
17282
|
+
out.push({
|
|
17283
|
+
vendor,
|
|
17284
|
+
product,
|
|
17285
|
+
route: router,
|
|
17286
|
+
ref: formatModelRef(resolved.ref),
|
|
17287
|
+
baseUrl: resolved.transport.baseUrl ?? null,
|
|
17288
|
+
pricing: {
|
|
17289
|
+
inPer1M: resolved.pricing.inputPer1M,
|
|
17290
|
+
outPer1M: resolved.pricing.outputPer1M
|
|
17291
|
+
},
|
|
17292
|
+
curated: false,
|
|
17293
|
+
methods: ["api-key"]
|
|
17294
|
+
});
|
|
17295
|
+
}
|
|
17296
|
+
}
|
|
17297
|
+
return out;
|
|
17298
|
+
}
|
|
17299
|
+
function mergeContributions(contributions) {
|
|
17300
|
+
const rows = /* @__PURE__ */ new Map();
|
|
17301
|
+
for (const c of contributions) {
|
|
17302
|
+
const key = `${c.vendor}\0${c.product}\0${c.route}`;
|
|
17303
|
+
const existing = rows.get(key);
|
|
17304
|
+
if (!existing) {
|
|
17305
|
+
rows.set(key, {
|
|
17306
|
+
vendor: c.vendor,
|
|
17307
|
+
product: c.product,
|
|
17308
|
+
route: c.route,
|
|
17309
|
+
ref: c.ref,
|
|
17310
|
+
baseUrl: c.baseUrl,
|
|
17311
|
+
pricing: c.pricing,
|
|
17312
|
+
curated: c.curated,
|
|
17313
|
+
adapters: c.adapterSlug ? [c.adapterSlug] : [],
|
|
17314
|
+
adapterModes: c.adapterMode ? [c.adapterMode] : [],
|
|
17315
|
+
methods: [...c.methods]
|
|
17316
|
+
});
|
|
17317
|
+
continue;
|
|
17318
|
+
}
|
|
17319
|
+
existing.curated = existing.curated || c.curated;
|
|
17320
|
+
existing.baseUrl = existing.baseUrl ?? c.baseUrl;
|
|
17321
|
+
existing.pricing = existing.pricing ?? c.pricing;
|
|
17322
|
+
if (c.adapterSlug && !existing.adapters.includes(c.adapterSlug)) {
|
|
17323
|
+
existing.adapters.push(c.adapterSlug);
|
|
17324
|
+
}
|
|
17325
|
+
if (c.adapterMode && !existing.adapterModes.includes(c.adapterMode)) {
|
|
17326
|
+
existing.adapterModes.push(c.adapterMode);
|
|
17327
|
+
}
|
|
17328
|
+
for (const m of c.methods) {
|
|
17329
|
+
if (!existing.methods.includes(m)) existing.methods.push(m);
|
|
17330
|
+
}
|
|
17331
|
+
}
|
|
17332
|
+
return [...rows.values()];
|
|
17333
|
+
}
|
|
17334
|
+
function billedVendor(vendor, route) {
|
|
17335
|
+
return route === vendor ? vendor : route;
|
|
17336
|
+
}
|
|
17337
|
+
function buildCatalogModels(input) {
|
|
17338
|
+
const contributions = [
|
|
17339
|
+
...curatedContributions(input.adapters),
|
|
17340
|
+
...widenedContributions(curatedContributions(input.adapters))
|
|
17341
|
+
];
|
|
17342
|
+
const merged = mergeContributions(contributions);
|
|
17343
|
+
const query = input.query ?? {};
|
|
17344
|
+
const vendors = /* @__PURE__ */ new Map();
|
|
17345
|
+
for (const row of merged) {
|
|
17346
|
+
if (query.vendor && row.vendor !== query.vendor) continue;
|
|
17347
|
+
if (query.route && row.route !== query.route) continue;
|
|
17348
|
+
if (query.adapter && !row.adapters.includes(query.adapter)) continue;
|
|
17349
|
+
const manifest = {
|
|
17350
|
+
id: `${row.vendor}/${row.product}@${row.route}`,
|
|
17351
|
+
vendorByRoute: { [row.route]: billedVendor(row.vendor, row.route) },
|
|
17352
|
+
methodsByRoute: { [row.route]: row.methods }
|
|
17353
|
+
};
|
|
17354
|
+
const eligible = eligibleProfiles(input.profiles, manifest, row.route);
|
|
17355
|
+
const runnable = eligible.length > 0;
|
|
17356
|
+
if (query.runnableOnly && !runnable) continue;
|
|
17357
|
+
const route = {
|
|
17358
|
+
route: row.route,
|
|
17359
|
+
ref: row.ref,
|
|
17360
|
+
baseUrl: row.baseUrl,
|
|
17361
|
+
pricing: row.pricing,
|
|
17362
|
+
runnable,
|
|
17363
|
+
eligibleProfiles: eligible.map((p) => p.id),
|
|
17364
|
+
adapterModes: row.adapterModes,
|
|
17365
|
+
adapters: row.adapters,
|
|
17366
|
+
curated: row.curated
|
|
17367
|
+
};
|
|
17368
|
+
const products = vendors.get(row.vendor) ?? /* @__PURE__ */ new Map();
|
|
17369
|
+
vendors.set(row.vendor, products);
|
|
17370
|
+
const routes = products.get(row.product) ?? [];
|
|
17371
|
+
products.set(row.product, routes);
|
|
17372
|
+
routes.push(route);
|
|
17373
|
+
}
|
|
17374
|
+
const result = [...vendors.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([vendor, products]) => ({
|
|
17375
|
+
vendor,
|
|
17376
|
+
products: [...products.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([product, routes]) => ({
|
|
17377
|
+
product,
|
|
17378
|
+
routes: [...routes].sort((a, b) => a.route.localeCompare(b.route))
|
|
17379
|
+
}))
|
|
17380
|
+
}));
|
|
17381
|
+
return { vendors: result };
|
|
17382
|
+
}
|
|
15746
17383
|
function makeBrowserHandle(entry, resolve14) {
|
|
15747
17384
|
return {
|
|
15748
17385
|
slug: entry.id,
|
|
@@ -17642,7 +19279,7 @@ var WorkspacePathError = class extends Error {
|
|
|
17642
19279
|
};
|
|
17643
19280
|
function createWorkspaceFs(opts) {
|
|
17644
19281
|
const root = resolve(opts.workspace);
|
|
17645
|
-
function
|
|
19282
|
+
function resolvePath5(path) {
|
|
17646
19283
|
if (typeof path !== "string" || path.length === 0) {
|
|
17647
19284
|
throw new WorkspacePathError("path must be a non-empty string");
|
|
17648
19285
|
}
|
|
@@ -17662,18 +19299,18 @@ function createWorkspaceFs(opts) {
|
|
|
17662
19299
|
}
|
|
17663
19300
|
return {
|
|
17664
19301
|
async readFile(path) {
|
|
17665
|
-
const abs =
|
|
19302
|
+
const abs = resolvePath5(path);
|
|
17666
19303
|
const buf = await readFile(abs);
|
|
17667
19304
|
return buf.toString("utf8");
|
|
17668
19305
|
},
|
|
17669
19306
|
async writeFile(path, content) {
|
|
17670
|
-
const abs =
|
|
19307
|
+
const abs = resolvePath5(path);
|
|
17671
19308
|
await mkdir(dirname(abs), { recursive: true });
|
|
17672
19309
|
await writeFile(abs, content);
|
|
17673
19310
|
},
|
|
17674
19311
|
async exists(path) {
|
|
17675
19312
|
try {
|
|
17676
|
-
const abs =
|
|
19313
|
+
const abs = resolvePath5(path);
|
|
17677
19314
|
return existsSync(abs);
|
|
17678
19315
|
} catch {
|
|
17679
19316
|
return false;
|
|
@@ -18031,6 +19668,39 @@ function isEnoent(err) {
|
|
|
18031
19668
|
function errMsg(err) {
|
|
18032
19669
|
return err instanceof Error ? err.message : String(err);
|
|
18033
19670
|
}
|
|
19671
|
+
var POSTURE_MODE_VALUES = {
|
|
19672
|
+
default: "default",
|
|
19673
|
+
plan: "plan",
|
|
19674
|
+
"accept-edits": "accept-edits",
|
|
19675
|
+
"bypass-permissions": "bypass",
|
|
19676
|
+
"read-only": "read-only",
|
|
19677
|
+
"full-access": "bypass",
|
|
19678
|
+
build: "default"
|
|
19679
|
+
};
|
|
19680
|
+
function decomposeMode(modes, modeId) {
|
|
19681
|
+
const declared = modes.find((mode) => mode.id === modeId);
|
|
19682
|
+
const kind = declared?.kind ?? inferLegacyModeKind(modeId);
|
|
19683
|
+
if (kind === "route") return { route: { gateway: modeId } };
|
|
19684
|
+
if (kind === "posture") return { posture: POSTURE_MODE_VALUES[modeId] ?? "default" };
|
|
19685
|
+
return { contextProfile: modeId };
|
|
19686
|
+
}
|
|
19687
|
+
function decomposedAxisMatches(cfg, decomposed) {
|
|
19688
|
+
if (decomposed.route) return cfg.route?.gateway === decomposed.route.gateway;
|
|
19689
|
+
if (decomposed.posture !== void 0) return cfg.posture === decomposed.posture;
|
|
19690
|
+
if (decomposed.contextProfile !== void 0) {
|
|
19691
|
+
return cfg.contextProfile === decomposed.contextProfile;
|
|
19692
|
+
}
|
|
19693
|
+
return false;
|
|
19694
|
+
}
|
|
19695
|
+
function composeMode(cfg, modes) {
|
|
19696
|
+
for (const mode of modes) {
|
|
19697
|
+
if (decomposedAxisMatches(cfg, decomposeMode(modes, mode.id))) return mode.id;
|
|
19698
|
+
}
|
|
19699
|
+
return void 0;
|
|
19700
|
+
}
|
|
19701
|
+
|
|
19702
|
+
// src/index.ts
|
|
19703
|
+
init_conversation_store();
|
|
18034
19704
|
async function isAgentCliAuthConfigured(slug, descriptor, model) {
|
|
18035
19705
|
const config = await loadConfig();
|
|
18036
19706
|
const spawnDefaults = resolveSpawnDefaults(config.defaults, slug, {});
|
|
@@ -18203,6 +19873,10 @@ async function createGateway(opts) {
|
|
|
18203
19873
|
sessionEvents,
|
|
18204
19874
|
resolveAgentAdapter: opts.resolveAgentAdapter,
|
|
18205
19875
|
persist,
|
|
19876
|
+
// Sandbox-capable agent steps (`AgentStep.sandbox` / workflow_start's
|
|
19877
|
+
// step `sandbox`) resolve providers through the same resolver
|
|
19878
|
+
// `agent_start.sandbox` uses.
|
|
19879
|
+
resolveSandboxProvider: resolveSandboxProviderResolved,
|
|
18206
19880
|
// Compile a loaded WORKFLOW.md handle into a runnable RuntimeWorkflow
|
|
18207
19881
|
// for `workflow_run_file` / `startFromFile`. The daemon's workflow
|
|
18208
19882
|
// surface is agent-step based (like the stage primitive), so no tool/
|
|
@@ -18266,7 +19940,8 @@ async function createGateway(opts) {
|
|
|
18266
19940
|
resolveSandboxProvider: resolveSandboxProviderResolved,
|
|
18267
19941
|
...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
|
|
18268
19942
|
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
18269
|
-
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {}
|
|
19943
|
+
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|
|
19944
|
+
...opts.listCatalogModels ? { listCatalogModels: opts.listCatalogModels } : {}
|
|
18270
19945
|
});
|
|
18271
19946
|
registerBrowserTools(server, {
|
|
18272
19947
|
registry: sessions,
|
|
@@ -18422,6 +20097,7 @@ async function createGateway(opts) {
|
|
|
18422
20097
|
daemonMcpUrl,
|
|
18423
20098
|
...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
|
|
18424
20099
|
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|
|
20100
|
+
...opts.listCatalogModels ? { listCatalogModels: opts.listCatalogModels } : {},
|
|
18425
20101
|
...opts.resolveBrowserAdapter ? { resolveBrowserAdapter: opts.resolveBrowserAdapter } : {},
|
|
18426
20102
|
...opts.listBrowserAdapters ? { listBrowserAdapters: opts.listBrowserAdapters } : {},
|
|
18427
20103
|
meta: { workspace, registered, startedAt },
|
|
@@ -18508,6 +20184,6 @@ var export_providersPath = providers_store_exports.providersPath;
|
|
|
18508
20184
|
var export_removeProviderKey = providers_store_exports.removeProviderKey;
|
|
18509
20185
|
var export_setProviderKey = providers_store_exports.setProviderKey;
|
|
18510
20186
|
|
|
18511
|
-
export { AuthResolutionError, BUCKETS_ROOT, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, LEGACY_SESSIONS_FILE, PAIRINGS_VERSION, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, WORKTREE_ISOLATION_ENV, bucketDir, bucketSessionsFile, bucketTranscriptDir, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, deriveSessionUsage, fileConversationStore, formatToolCall, formatToolResult, getMcpCredentialDeps, export_getProviderKey as getProviderKey, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isSafeBucketSlug, listBuckets, listPresets, export_loadProviders as loadProviders, loadWorktreeIsolation, makeBrowserAdapterLister, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeSkillsOption, normalizeWorktreeField, parseDuration, parseWorktreeIsolationMode, policyWatchesSession, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readDaemonRegistry, readRegisteredSlugs, readRuntimeMeta, registerPairingTools, export_removeProviderKey as removeProviderKey, resolveAuthSpec, resolveBucketSlug, resolveSpawnDefaults, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|
|
20187
|
+
export { AuthResolutionError, BUCKETS_ROOT, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, LEGACY_SESSIONS_FILE, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, WORKTREE_ISOLATION_ENV, appendConversationRecord, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, canonicalForModeId, claudeProjectSlug, composeMode, composeSessionObservers, conversationIndexPath, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, deriveSessionUsage, fileConversationStore, findConversationRecord, findNativeMode, formatToolCall, formatToolResult, getMcpCredentialDeps, export_getProviderKey as getProviderKey, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isSafeBucketSlug, listBuckets, listPresets, export_loadProviders as loadProviders, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeModeId, normalizeSkillsOption, normalizeWorktreeField, parseDuration, parseWorktreeIsolationMode, policyWatchesSession, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readRegisteredSlugs, readRuntimeMeta, registerPairingTools, export_removeProviderKey as removeProviderKey, resolveAuthSpec, resolveBucketSlug, resolveNativeLink, resolvePosture, resolveSpawnDefaults, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
|
|
18512
20188
|
//# sourceMappingURL=index.mjs.map
|
|
18513
20189
|
//# sourceMappingURL=index.mjs.map
|