@agentproto/runtime 3.3.0 → 3.5.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/catalog-models.d.ts +16 -6
- package/dist/catalog-models.mjs +12 -3
- package/dist/catalog-models.mjs.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/index.d.ts +66 -14
- package/dist/index.mjs +473 -58
- package/dist/index.mjs.map +1 -1
- package/dist/resume-strategies.d.ts +29 -1
- package/dist/resume-strategies.mjs +22 -2
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/{spawn-defaults-D4W661ui.d.ts → spawn-defaults-CwI9Puwy.d.ts} +8 -0
- package/package.json +27 -27
package/dist/index.mjs
CHANGED
|
@@ -2232,7 +2232,40 @@ async function readPi(conversationId) {
|
|
|
2232
2232
|
const { exportPiSession: exportPiSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
2233
2233
|
return exportPiSession2(conversationId);
|
|
2234
2234
|
}
|
|
2235
|
-
|
|
2235
|
+
function argvBasename(path) {
|
|
2236
|
+
const idx = path.lastIndexOf("/");
|
|
2237
|
+
return idx === -1 ? path : path.slice(idx + 1);
|
|
2238
|
+
}
|
|
2239
|
+
function conversationTerminalSlugFor(subject) {
|
|
2240
|
+
if (subject.adapterSlug && (CONVERSATION_STORES[subject.adapterSlug] || NATIVE_LAUNCH_ARGV[subject.adapterSlug])) {
|
|
2241
|
+
return subject.adapterSlug;
|
|
2242
|
+
}
|
|
2243
|
+
const argv = subject.argv;
|
|
2244
|
+
if (!argv || argv.length === 0 || !argv[0]) return void 0;
|
|
2245
|
+
const bin = argvBasename(argv[0]);
|
|
2246
|
+
for (const [slug, launch] of Object.entries(NATIVE_LAUNCH_ARGV)) {
|
|
2247
|
+
const launchBin = launch[0];
|
|
2248
|
+
if (!launchBin || argvBasename(launchBin) !== bin) continue;
|
|
2249
|
+
if (launchBin === "npx") {
|
|
2250
|
+
if (launch.every((arg, i) => argv[i] === arg)) return slug;
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
2253
|
+
return slug;
|
|
2254
|
+
}
|
|
2255
|
+
return void 0;
|
|
2256
|
+
}
|
|
2257
|
+
function isConversationTerminal(subject) {
|
|
2258
|
+
if (subject.kind !== "terminal") return false;
|
|
2259
|
+
if (conversationTerminalSlugFor(subject) !== void 0) return true;
|
|
2260
|
+
const meta = subject.resumeMetadata;
|
|
2261
|
+
if (meta) {
|
|
2262
|
+
for (const key of CONVERSATION_RESUME_KEYS) {
|
|
2263
|
+
if (meta[key]) return true;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
return false;
|
|
2267
|
+
}
|
|
2268
|
+
var CONVERSATION_STORES, NATIVE_LAUNCH_ARGV, CONVERSATION_RESUME_KEYS;
|
|
2236
2269
|
var init_conversation_store = __esm({
|
|
2237
2270
|
"src/conversation-store.ts"() {
|
|
2238
2271
|
CONVERSATION_STORES = {
|
|
@@ -2242,6 +2275,7 @@ var init_conversation_store = __esm({
|
|
|
2242
2275
|
// (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
|
|
2243
2276
|
outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
|
|
2244
2277
|
attachArgv: (conversationId) => ["claude", "--resume", conversationId],
|
|
2278
|
+
transcriptPath: (cwd, conversationId, configDir) => join(claudeCodeProjectDir(cwd, configDir), `${conversationId}.jsonl`),
|
|
2245
2279
|
configDirEnvVar: "CLAUDE_CONFIG_DIR",
|
|
2246
2280
|
discover: discoverClaudeCode,
|
|
2247
2281
|
read: readClaudeCode
|
|
@@ -2280,6 +2314,32 @@ var init_conversation_store = __esm({
|
|
|
2280
2314
|
read: readPi
|
|
2281
2315
|
}
|
|
2282
2316
|
};
|
|
2317
|
+
NATIVE_LAUNCH_ARGV = {
|
|
2318
|
+
"claude-code": ["claude"],
|
|
2319
|
+
hermes: ["hermes", "--tui"],
|
|
2320
|
+
// Bare arm of the same npx package the adapter spawns IS the TUI.
|
|
2321
|
+
opencode: ["npx", "-y", "opencode-ai"],
|
|
2322
|
+
mastracode: ["npx", "-y", "mastracode"],
|
|
2323
|
+
// The adapter bundles the codex-acp wrapper (ACP-only); the interactive
|
|
2324
|
+
// Codex TUI lives in @openai/codex, fetched by npx on first use.
|
|
2325
|
+
codex: ["npx", "-y", "@openai/codex"],
|
|
2326
|
+
// ACP arms add flags/subcommands to these binaries; bare is interactive.
|
|
2327
|
+
gemini: ["gemini"],
|
|
2328
|
+
"grok-cli": ["grok"],
|
|
2329
|
+
pi: ["pi"],
|
|
2330
|
+
jcode: ["jcode"],
|
|
2331
|
+
// `openclaw chat` = "Open a local terminal UI (alias for tui --local)".
|
|
2332
|
+
openclaw: ["openclaw", "chat"],
|
|
2333
|
+
// Generic ACP catalog CLIs whose ACP arm is a separate binary/subcommand;
|
|
2334
|
+
// the bare bin is the interactive TUI.
|
|
2335
|
+
"gemini-cli": ["gemini"],
|
|
2336
|
+
"qwen-code": ["qwen"],
|
|
2337
|
+
"mistral-vibe": ["vibe"],
|
|
2338
|
+
"kimi-cli": ["kimi"]
|
|
2339
|
+
};
|
|
2340
|
+
CONVERSATION_RESUME_KEYS = new Set(
|
|
2341
|
+
Object.values(CONVERSATION_STORES).map((s) => s.storeAs)
|
|
2342
|
+
);
|
|
2283
2343
|
}
|
|
2284
2344
|
});
|
|
2285
2345
|
|
|
@@ -2512,6 +2572,7 @@ var RESUME_STRATEGIES = Object.fromEntries(
|
|
|
2512
2572
|
outputHint: store.outputHint,
|
|
2513
2573
|
storeAs: store.storeAs,
|
|
2514
2574
|
...store.configDirEnvVar ? { configDirEnvVar: store.configDirEnvVar } : {},
|
|
2575
|
+
...store.transcriptPath ? { transcriptPath: store.transcriptPath } : {},
|
|
2515
2576
|
fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {
|
|
2516
2577
|
const candidates = await s.discover({
|
|
2517
2578
|
cwd,
|
|
@@ -2583,6 +2644,21 @@ async function augmentWithFsResume(prev) {
|
|
|
2583
2644
|
}
|
|
2584
2645
|
};
|
|
2585
2646
|
}
|
|
2647
|
+
async function probeNativeTranscript(prev) {
|
|
2648
|
+
if (!prev.adapterSlug || !prev.cwd) return void 0;
|
|
2649
|
+
const strategy = RESUME_STRATEGIES[prev.adapterSlug];
|
|
2650
|
+
if (!strategy?.transcriptPath) return void 0;
|
|
2651
|
+
const id = prev.resumeMetadata?.[strategy.storeAs] ?? prev.adapterSessionId;
|
|
2652
|
+
if (!id) return void 0;
|
|
2653
|
+
const path = strategy.transcriptPath(prev.cwd, id, prev.adapterConfigDir);
|
|
2654
|
+
let exists = false;
|
|
2655
|
+
try {
|
|
2656
|
+
exists = (await promises.stat(path)).isFile();
|
|
2657
|
+
} catch {
|
|
2658
|
+
exists = false;
|
|
2659
|
+
}
|
|
2660
|
+
return { dir: dirname(path), path, exists };
|
|
2661
|
+
}
|
|
2586
2662
|
function describeResumePath(prev, opts = {}) {
|
|
2587
2663
|
const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true;
|
|
2588
2664
|
if (prev.adapterSlug && mayPreferNative) {
|
|
@@ -2628,6 +2704,9 @@ function tokenizeCommand(s) {
|
|
|
2628
2704
|
return out;
|
|
2629
2705
|
}
|
|
2630
2706
|
|
|
2707
|
+
// src/sessions.ts
|
|
2708
|
+
init_conversation_store();
|
|
2709
|
+
|
|
2631
2710
|
// src/command-log.ts
|
|
2632
2711
|
init_transcript_writer();
|
|
2633
2712
|
function writeCommandLogEntry(sessionId, entry, baseDir) {
|
|
@@ -4036,7 +4115,13 @@ function resolveWorktreeIdentity(cwd) {
|
|
|
4036
4115
|
dir = parent;
|
|
4037
4116
|
}
|
|
4038
4117
|
}
|
|
4039
|
-
var WIDENING_ROUTES = [
|
|
4118
|
+
var WIDENING_ROUTES = [
|
|
4119
|
+
"openrouter",
|
|
4120
|
+
"requesty",
|
|
4121
|
+
"huggingface",
|
|
4122
|
+
"opencode-go",
|
|
4123
|
+
"opencode"
|
|
4124
|
+
];
|
|
4040
4125
|
var VENDOR_COMPATIBILITY_ROUTES = {
|
|
4041
4126
|
xai: ["xai", "xai-anthropic"]
|
|
4042
4127
|
};
|
|
@@ -4375,9 +4460,12 @@ function reconcileModelRoute(input) {
|
|
|
4375
4460
|
}
|
|
4376
4461
|
function serviceableModelRoutes(model) {
|
|
4377
4462
|
const routes = /* @__PURE__ */ new Set();
|
|
4378
|
-
const provider = getModelProvider(model);
|
|
4379
|
-
if (provider) routes.add(provider);
|
|
4380
4463
|
const parsed = tryParseModelRef(normalizeRouterPrefixedId(model));
|
|
4464
|
+
const selfRouted = parsed !== void 0 && parsed.route === parsed.vendor && WIDENING_ROUTES.includes(parsed.vendor) && tryResolveLlmModelRoute(`${parsed.vendor}/${parsed.product}`)?.pricing.provider === parsed.vendor;
|
|
4465
|
+
if (!selfRouted) {
|
|
4466
|
+
const provider = getModelProvider(model);
|
|
4467
|
+
if (provider) routes.add(provider);
|
|
4468
|
+
}
|
|
4381
4469
|
if (parsed) {
|
|
4382
4470
|
if (parsed.route !== parsed.vendor) routes.add(parsed.route);
|
|
4383
4471
|
if (resolvePricingExact(parsed.product)?.provider === parsed.vendor) {
|
|
@@ -8252,8 +8340,10 @@ function registerAgentTools(server, opts) {
|
|
|
8252
8340
|
resolveSandboxProvider: resolveSandboxProvider2,
|
|
8253
8341
|
provisionWorktree,
|
|
8254
8342
|
resolveWorktreeIsolation,
|
|
8255
|
-
supervisor
|
|
8343
|
+
supervisor,
|
|
8344
|
+
defaultAgentPromptInterrupt
|
|
8256
8345
|
} = opts;
|
|
8346
|
+
const interruptDefault = defaultAgentPromptInterrupt ?? false;
|
|
8257
8347
|
server.registerTool(
|
|
8258
8348
|
"agent_start",
|
|
8259
8349
|
{
|
|
@@ -8489,14 +8579,20 @@ function registerAgentTools(server, opts) {
|
|
|
8489
8579
|
"Isolate this session in its OWN git worktree instead of spawning directly in `cwd` \u2014 so a parallel agent can't collide on the working tree. `true` provisions a worktree on a fresh branch `wt/<slug>` cut from origin/main (slug auto-minted from `label`); pass `{ slug, base, async }` to pin either, or opt into an early return (`async`, see that field's own description). The daemon boots the worktree (git worktree add + the repo's agentproto.json setup hooks) and spawns `adapter` THERE; the session's cwd, and every path it edits, live inside the worktree. Honoured only for a ROOT spawn (a spawn made THROUGH an orchestrator inherits its parent's tree \u2014 no second worktree; an EXPLICIT `worktree` on such a nested spawn is REJECTED, not silently ignored \u2014 use `sandbox` to isolate a child) and only when `cwd` is inside a git repo (nothing to isolate otherwise \u21D2 spawns plain, no error). The daemon's `worktrees.isolation` policy may force this ON for every root spawn (`always`) or OFF (`never`, which REJECTS an explicit `worktree`). Ignored for a `sandbox` spawn (the box already isolates). The worktree is NOT auto-removed on session close \u2014 it holds the agent's work; tear it down with `agentproto worktree rm|archive|gc`."
|
|
8490
8580
|
)
|
|
8491
8581
|
},
|
|
8492
|
-
//
|
|
8493
|
-
//
|
|
8494
|
-
// the tool-definition level — same mechanism
|
|
8495
|
-
// mcp-apps-adapter.ts).
|
|
8496
|
-
//
|
|
8582
|
+
// Session-chat widget: rendering agent_start's result auto-mounts the
|
|
8583
|
+
// session-chat launcher for the new session (ext-apps
|
|
8584
|
+
// `_meta.ui.resourceUri` at the tool-definition level — same mechanism
|
|
8585
|
+
// as the panel apps in mcp-apps-adapter.ts). The widget reads the
|
|
8586
|
+
// spawned `{ id: "sess_…" }` off the host's tool-result notification
|
|
8587
|
+
// and deep-links the installed `@agentik/session-chat` app straight
|
|
8588
|
+
// into that session (apps/src/session-chat/panel.ts). The older
|
|
8589
|
+
// `ui://live_session/view` resource stays registered for its own
|
|
8590
|
+
// `live_session` tool and other consumers — only this binding moved.
|
|
8591
|
+
// `visibility:["model","app"]` keeps agent_start fully usable by the
|
|
8592
|
+
// model AND lets the widget re-call it if needed.
|
|
8497
8593
|
_meta: {
|
|
8498
8594
|
ui: {
|
|
8499
|
-
resourceUri: "ui://
|
|
8595
|
+
resourceUri: "ui://agentproto_session_chat/view",
|
|
8500
8596
|
visibility: ["model", "app"]
|
|
8501
8597
|
}
|
|
8502
8598
|
}
|
|
@@ -8609,7 +8705,7 @@ function registerAgentTools(server, opts) {
|
|
|
8609
8705
|
id: sessionIdAliasField,
|
|
8610
8706
|
prompt: z.string().min(1).describe("The next user turn (plain text)."),
|
|
8611
8707
|
interrupt: z.boolean().optional().describe(
|
|
8612
|
-
"When true and the session is mid-turn, cancel the in-flight turn and deliver this prompt on the same session instead of
|
|
8708
|
+
"When true and the session is mid-turn, cancel the in-flight turn and deliver this prompt on the same session immediately instead of queueing it behind the current turn. No-op when the session is already idle. UNSET falls back to the daemon default `defaults.agentPromptInterrupt` in config.json (false unless an operator changed it) \u2014 so a mid-turn target queues by default; pass `interrupt: true` explicitly to cut now."
|
|
8613
8709
|
),
|
|
8614
8710
|
queue: z.boolean().optional().describe(
|
|
8615
8711
|
"When the session is mid-turn, queue this prompt (FIFO) and dispatch it automatically once the current turn ends instead of rejecting. Default true. Explicit false restores the old reject-when-busy behavior."
|
|
@@ -8620,8 +8716,9 @@ function registerAgentTools(server, opts) {
|
|
|
8620
8716
|
if (!sessionId) return missingSessionIdError("agent_prompt");
|
|
8621
8717
|
try {
|
|
8622
8718
|
const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
|
|
8623
|
-
|
|
8624
|
-
|
|
8719
|
+
const effectiveInterrupt = input.interrupt ?? interruptDefault;
|
|
8720
|
+
const { queued } = await registry.enqueuePrompt(sessionId, input.prompt, {
|
|
8721
|
+
interrupt: effectiveInterrupt,
|
|
8625
8722
|
// Queue by default: a mid-turn session holds the prompt in its
|
|
8626
8723
|
// FIFO queue and dispatches it at turn end, so callers never
|
|
8627
8724
|
// lose a prompt to the busy rejection. Explicit `queue: false`
|
|
@@ -8629,12 +8726,21 @@ function registerAgentTools(server, opts) {
|
|
|
8629
8726
|
queue: input.queue ?? true,
|
|
8630
8727
|
...promptSource ? { source: `agent:${promptSource}` } : {}
|
|
8631
8728
|
});
|
|
8729
|
+
const hintQueued = queued && input.interrupt === void 0;
|
|
8632
8730
|
return {
|
|
8633
8731
|
content: [
|
|
8634
8732
|
{
|
|
8635
8733
|
type: "text",
|
|
8636
8734
|
text: JSON.stringify(
|
|
8637
|
-
{
|
|
8735
|
+
{
|
|
8736
|
+
ok: true,
|
|
8737
|
+
sessionId,
|
|
8738
|
+
queued: true,
|
|
8739
|
+
...hintQueued ? {
|
|
8740
|
+
delivery: "queued-mid-turn",
|
|
8741
|
+
hint: "Message queued \u2014 it will only be delivered when the target's CURRENT turn ends. If it's urgent, re-send with interrupt: true (cancels the in-flight turn and redirects the session onto this prompt now)."
|
|
8742
|
+
} : {}
|
|
8743
|
+
},
|
|
8638
8744
|
null,
|
|
8639
8745
|
2
|
|
8640
8746
|
)
|
|
@@ -8663,9 +8769,12 @@ function registerAgentTools(server, opts) {
|
|
|
8663
8769
|
);
|
|
8664
8770
|
server.tool(
|
|
8665
8771
|
"message_parent",
|
|
8666
|
-
"Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts). Errors if this session has no recorded parent or the parent is gone.",
|
|
8772
|
+
"Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts, by default). Pass `interrupt: true` to CUT a mid-turn parent immediately \u2014 cancel its in-flight turn and redirect it onto this message now (same-context cancel, like `agent_prompt`'s `interrupt`) \u2014 for a genuinely urgent report the parent must act on before it finishes what it's doing. Errors if this session has no recorded parent or the parent is gone.",
|
|
8667
8773
|
{
|
|
8668
|
-
message: z.string().min(1).describe("The message to deliver to your parent session (plain text).")
|
|
8774
|
+
message: z.string().min(1).describe("The message to deliver to your parent session (plain text)."),
|
|
8775
|
+
interrupt: z.boolean().optional().describe(
|
|
8776
|
+
"When true and the parent is mid-turn, cancel its in-flight turn and deliver this message immediately instead of queueing it onto the parent's next turn. No-op when the parent is idle (delivered as a normal prompt either way). UNSET falls back to the daemon default `defaults.agentPromptInterrupt` in config.json (false unless an operator changed it) \u2014 so a report never interrupts by default; pass `interrupt: true` explicitly to cut."
|
|
8777
|
+
)
|
|
8669
8778
|
},
|
|
8670
8779
|
async (input) => {
|
|
8671
8780
|
const fail = (text10) => ({
|
|
@@ -8699,11 +8808,12 @@ function registerAgentTools(server, opts) {
|
|
|
8699
8808
|
}
|
|
8700
8809
|
const who = self.label ?? selfId;
|
|
8701
8810
|
const notice = `[child-message] ${who} (${selfId}): ${input.message}`;
|
|
8702
|
-
const
|
|
8811
|
+
const effectiveInterrupt = input.interrupt ?? interruptDefault;
|
|
8812
|
+
const done = (delivery, extra) => ({
|
|
8703
8813
|
content: [
|
|
8704
8814
|
{
|
|
8705
8815
|
type: "text",
|
|
8706
|
-
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery })
|
|
8816
|
+
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery, ...extra })
|
|
8707
8817
|
}
|
|
8708
8818
|
]
|
|
8709
8819
|
});
|
|
@@ -8714,12 +8824,27 @@ function registerAgentTools(server, opts) {
|
|
|
8714
8824
|
} catch {
|
|
8715
8825
|
}
|
|
8716
8826
|
}
|
|
8827
|
+
if (effectiveInterrupt) {
|
|
8828
|
+
try {
|
|
8829
|
+
await registry.enqueuePrompt(parentId, notice, {
|
|
8830
|
+
interrupt: true,
|
|
8831
|
+
origin: `child:${who}`
|
|
8832
|
+
});
|
|
8833
|
+
return done("interrupted");
|
|
8834
|
+
} catch {
|
|
8835
|
+
}
|
|
8836
|
+
}
|
|
8717
8837
|
if (!registry.stampPendingChildCrashNotice(parentId, notice)) {
|
|
8718
8838
|
return fail(
|
|
8719
8839
|
`message_parent: parent session "${parentId}" vanished mid-delivery.`
|
|
8720
8840
|
);
|
|
8721
8841
|
}
|
|
8722
|
-
return done(
|
|
8842
|
+
return done(
|
|
8843
|
+
"queued-next-turn",
|
|
8844
|
+
input.interrupt === void 0 ? {
|
|
8845
|
+
hint: "Message queued \u2014 it will only reach the parent when its CURRENT turn ends. If it's urgent, re-send with interrupt: true (cancels the parent's in-flight turn and redirects it onto this message now)."
|
|
8846
|
+
} : void 0
|
|
8847
|
+
);
|
|
8723
8848
|
}
|
|
8724
8849
|
);
|
|
8725
8850
|
server.tool(
|
|
@@ -10906,6 +11031,9 @@ function promptOriginLabel(item) {
|
|
|
10906
11031
|
if (code.startsWith("child:")) return `child ${code.slice("child:".length)}`;
|
|
10907
11032
|
return code;
|
|
10908
11033
|
}
|
|
11034
|
+
function isShellOnlyRow(desc) {
|
|
11035
|
+
return desc.kind === "command" || desc.kind === "terminal" && !isConversationTerminal(desc);
|
|
11036
|
+
}
|
|
10909
11037
|
function toSessionSummary(desc) {
|
|
10910
11038
|
return {
|
|
10911
11039
|
id: desc.id,
|
|
@@ -11091,6 +11219,14 @@ function stampInterrupted(desc) {
|
|
|
11091
11219
|
delete desc.interrupted;
|
|
11092
11220
|
}
|
|
11093
11221
|
}
|
|
11222
|
+
function stampLiveModes(desc, rt) {
|
|
11223
|
+
const modes = rt.agentSession?.availableModes;
|
|
11224
|
+
if (modes && modes.length > 0) {
|
|
11225
|
+
desc.availableModes = [...modes];
|
|
11226
|
+
} else {
|
|
11227
|
+
delete desc.availableModes;
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11094
11230
|
function isResumable(desc) {
|
|
11095
11231
|
return desc.kind === "agent-cli" && !!desc.adapterSlug && !!desc.adapterSessionId && !!desc.cwd && !desc.archived;
|
|
11096
11232
|
}
|
|
@@ -11291,6 +11427,7 @@ function createSessionsRegistry(opts) {
|
|
|
11291
11427
|
}
|
|
11292
11428
|
const emitExited = (rt) => {
|
|
11293
11429
|
if (rt.exitedEmitted) return;
|
|
11430
|
+
rt.linkProbeStop?.();
|
|
11294
11431
|
cancelPendingPermissionsForSession(rt);
|
|
11295
11432
|
if (runWorktreeAutoReclaim && rt.desc.worktreeAutoProvisioned && rt.desc.worktreePath) {
|
|
11296
11433
|
const worktreePath = rt.desc.worktreePath;
|
|
@@ -11472,8 +11609,9 @@ function createSessionsRegistry(opts) {
|
|
|
11472
11609
|
const recordConversationLink = (rt, adapterSessionIdOverride) => {
|
|
11473
11610
|
if (!persist || !partitioned) return;
|
|
11474
11611
|
const desc = rt.desc;
|
|
11475
|
-
|
|
11476
|
-
|
|
11612
|
+
const isConvTerminal = desc.kind === "terminal" && isConversationTerminal(desc);
|
|
11613
|
+
if (desc.kind !== "agent-cli" && !isConvTerminal) return;
|
|
11614
|
+
const adapterSlug = desc.adapterSlug ?? (isConvTerminal ? conversationTerminalSlugFor(desc) : void 0);
|
|
11477
11615
|
const adapterSessionId = adapterSessionIdOverride ?? desc.adapterSessionId;
|
|
11478
11616
|
const cwd = desc.cwd;
|
|
11479
11617
|
if (!adapterSlug || !adapterSessionId || !cwd) return;
|
|
@@ -11517,6 +11655,81 @@ function createSessionsRegistry(opts) {
|
|
|
11517
11655
|
void persistSnapshot();
|
|
11518
11656
|
}, PERSIST_DEBOUNCE_MS2);
|
|
11519
11657
|
};
|
|
11658
|
+
const conversationLinkProbeMs = opts?.conversationLinkProbeMs ?? {
|
|
11659
|
+
initialMs: 3e3,
|
|
11660
|
+
intervalMs: 15e3
|
|
11661
|
+
};
|
|
11662
|
+
const startConversationTerminalLinkProbe = (rt) => {
|
|
11663
|
+
const desc = rt.desc;
|
|
11664
|
+
const slug = desc.adapterSlug ?? conversationTerminalSlugFor(desc);
|
|
11665
|
+
const store = slug ? CONVERSATION_STORES[slug] : void 0;
|
|
11666
|
+
const cwd = desc.cwd;
|
|
11667
|
+
if (!store || !cwd) return;
|
|
11668
|
+
let inFlight = false;
|
|
11669
|
+
let stopped = false;
|
|
11670
|
+
const timers = [];
|
|
11671
|
+
const stop = () => {
|
|
11672
|
+
stopped = true;
|
|
11673
|
+
for (const t of timers) clearTimeout(t);
|
|
11674
|
+
timers.length = 0;
|
|
11675
|
+
};
|
|
11676
|
+
rt.linkProbeStop = stop;
|
|
11677
|
+
const tick = async () => {
|
|
11678
|
+
if (stopped || inFlight) return;
|
|
11679
|
+
if (desc.status !== "running") {
|
|
11680
|
+
stop();
|
|
11681
|
+
return;
|
|
11682
|
+
}
|
|
11683
|
+
if (!desc.lastOutputAt) return;
|
|
11684
|
+
if (desc.adapterSessionId) {
|
|
11685
|
+
stop();
|
|
11686
|
+
return;
|
|
11687
|
+
}
|
|
11688
|
+
inFlight = true;
|
|
11689
|
+
try {
|
|
11690
|
+
const candidates = await store.discover({
|
|
11691
|
+
cwd,
|
|
11692
|
+
since: desc.startedAt,
|
|
11693
|
+
// A PTY writes through the provider's native arm, never ACP.
|
|
11694
|
+
attachmentMode: "native"
|
|
11695
|
+
// No configDir on purpose: nothing threaded CLAUDE_CONFIG_DIR
|
|
11696
|
+
// into this PTY, so the global store is where the file is.
|
|
11697
|
+
});
|
|
11698
|
+
if (candidates.length === 1) {
|
|
11699
|
+
const candidate = candidates[0];
|
|
11700
|
+
desc.adapterSessionId = candidate.conversationId;
|
|
11701
|
+
desc.resumeMetadata = {
|
|
11702
|
+
...desc.resumeMetadata ?? {},
|
|
11703
|
+
[store.storeAs]: candidate.conversationId
|
|
11704
|
+
};
|
|
11705
|
+
delete desc.linkStatus;
|
|
11706
|
+
if (!desc.title && candidate.preview) {
|
|
11707
|
+
const derived = deriveSessionTitle(candidate.preview);
|
|
11708
|
+
if (derived) desc.title = derived;
|
|
11709
|
+
}
|
|
11710
|
+
schedulePersist();
|
|
11711
|
+
recordConversationLink(rt, candidate.conversationId);
|
|
11712
|
+
stop();
|
|
11713
|
+
} else if (candidates.length > 1) {
|
|
11714
|
+
desc.linkStatus = "ambiguous";
|
|
11715
|
+
schedulePersist();
|
|
11716
|
+
}
|
|
11717
|
+
} catch {
|
|
11718
|
+
} finally {
|
|
11719
|
+
inFlight = false;
|
|
11720
|
+
}
|
|
11721
|
+
};
|
|
11722
|
+
const schedule = (delayMs) => {
|
|
11723
|
+
if (stopped) return;
|
|
11724
|
+
const t = setTimeout(() => {
|
|
11725
|
+
void tick().finally(() => schedule(conversationLinkProbeMs.intervalMs));
|
|
11726
|
+
}, delayMs);
|
|
11727
|
+
t.unref?.();
|
|
11728
|
+
timers.length = 0;
|
|
11729
|
+
timers.push(t);
|
|
11730
|
+
};
|
|
11731
|
+
schedule(conversationLinkProbeMs.initialMs);
|
|
11732
|
+
};
|
|
11520
11733
|
const snapshotRows = () => Array.from(sessions.values()).map((s) => {
|
|
11521
11734
|
const {
|
|
11522
11735
|
processAlive: _processAlive,
|
|
@@ -11525,6 +11738,7 @@ function createSessionsRegistry(opts) {
|
|
|
11525
11738
|
secondsSinceLastActivity: _secondsSinceLastActivity,
|
|
11526
11739
|
toolCallsThisTurn: _toolCallsThisTurn,
|
|
11527
11740
|
eventsPath: _eventsPath,
|
|
11741
|
+
availableModes: _availableModes,
|
|
11528
11742
|
...rest
|
|
11529
11743
|
} = s.desc;
|
|
11530
11744
|
return rest;
|
|
@@ -11599,8 +11813,9 @@ function createSessionsRegistry(opts) {
|
|
|
11599
11813
|
rt.emitter.emit("line", { line, stream });
|
|
11600
11814
|
};
|
|
11601
11815
|
const sniffResumeHints = (rt, line) => {
|
|
11602
|
-
if (rt.desc.kind !== "agent-cli") return;
|
|
11603
|
-
const
|
|
11816
|
+
if (rt.desc.kind !== "agent-cli" && rt.conversationTerminal !== true) return;
|
|
11817
|
+
const strategySlug = rt.desc.adapterSlug ?? conversationTerminalSlugFor(rt.desc);
|
|
11818
|
+
const strategy = strategySlug ? RESUME_STRATEGIES[strategySlug] : void 0;
|
|
11604
11819
|
if (!strategy?.outputHint) return;
|
|
11605
11820
|
const plain = stripAnsiCodes(line);
|
|
11606
11821
|
const m = plain.match(strategy.outputHint);
|
|
@@ -11613,6 +11828,14 @@ function createSessionsRegistry(opts) {
|
|
|
11613
11828
|
recordConversationLink(rt, m[1]);
|
|
11614
11829
|
}
|
|
11615
11830
|
};
|
|
11831
|
+
const sniffConversationTerminalChunk = (rt, chunk) => {
|
|
11832
|
+
const text10 = (rt.sniffCarry ?? "") + chunk.toString("utf8");
|
|
11833
|
+
const lines = text10.split(/\r?\n|\r/);
|
|
11834
|
+
rt.sniffCarry = (lines.pop() ?? "").slice(-4096);
|
|
11835
|
+
for (const line of lines) {
|
|
11836
|
+
if (line) sniffResumeHints(rt, line);
|
|
11837
|
+
}
|
|
11838
|
+
};
|
|
11616
11839
|
const appendBytes = (rt, chunk) => {
|
|
11617
11840
|
terminalTranscriptWriter.appendChunk(rt.desc.id, chunk);
|
|
11618
11841
|
rt.recentBytes.push(chunk);
|
|
@@ -11622,6 +11845,7 @@ function createSessionsRegistry(opts) {
|
|
|
11622
11845
|
if (dropped) rt.recentBytesSize -= dropped.byteLength;
|
|
11623
11846
|
}
|
|
11624
11847
|
rt.desc.lastOutputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11848
|
+
if (rt.conversationTerminal === true) sniffConversationTerminalChunk(rt, chunk);
|
|
11625
11849
|
rt.emitter.emit("data", chunk);
|
|
11626
11850
|
};
|
|
11627
11851
|
const reconcilePtySize = (rt) => {
|
|
@@ -12992,6 +13216,19 @@ ${message}`;
|
|
|
12992
13216
|
rows: input.rows
|
|
12993
13217
|
});
|
|
12994
13218
|
const priorCommandSessionId = findPriorCommandSessionId(sessions, input.cwd);
|
|
13219
|
+
const conversationSlug = conversationTerminalSlugFor({ argv: input.argv });
|
|
13220
|
+
const conversationStore = conversationSlug ? CONVERSATION_STORES[conversationSlug] : void 0;
|
|
13221
|
+
const resumeArgvId = (() => {
|
|
13222
|
+
if (!conversationStore) return void 0;
|
|
13223
|
+
for (let i = 0; i < input.argv.length - 1; i++) {
|
|
13224
|
+
if (input.argv[i] !== "--resume" && input.argv[i] !== "-r") continue;
|
|
13225
|
+
const raw = input.argv[i + 1];
|
|
13226
|
+
const base = raw.slice(raw.lastIndexOf("/") + 1);
|
|
13227
|
+
return base.endsWith(".jsonl") ? base.slice(0, -".jsonl".length) : raw;
|
|
13228
|
+
}
|
|
13229
|
+
return void 0;
|
|
13230
|
+
})();
|
|
13231
|
+
const conversationConfigDir = conversationStore?.configDirEnvVar && input.env ? input.env[conversationStore.configDirEnvVar] : void 0;
|
|
12995
13232
|
const desc = {
|
|
12996
13233
|
id,
|
|
12997
13234
|
kind: "terminal",
|
|
@@ -13006,6 +13243,16 @@ ${message}`;
|
|
|
13006
13243
|
...worktreeFields(input.cwd),
|
|
13007
13244
|
...input.name ? { name: input.name } : {},
|
|
13008
13245
|
...input.label ? { label: input.label } : {},
|
|
13246
|
+
// Same rule as spawnAgent: a NEW spawn's label is a slug, not a
|
|
13247
|
+
// human rename — let a later derived title outrank it.
|
|
13248
|
+
...input.label ? { renamedByUser: false } : {},
|
|
13249
|
+
...conversationSlug ? { adapterSlug: conversationSlug } : {},
|
|
13250
|
+
...conversationSlug && RESUME_STRATEGIES[conversationSlug]?.spawnArgs ? { nativeTerminalResume: true } : {},
|
|
13251
|
+
...conversationConfigDir ? { adapterConfigDir: conversationConfigDir } : {},
|
|
13252
|
+
...resumeArgvId && conversationStore ? {
|
|
13253
|
+
adapterSessionId: resumeArgvId,
|
|
13254
|
+
resumeMetadata: { [conversationStore.storeAs]: resumeArgvId }
|
|
13255
|
+
} : {},
|
|
13009
13256
|
...priorCommandSessionId ? { priorCommandSessionId } : {},
|
|
13010
13257
|
// Parent attribution + depth (orchestrator WP4) — same recording
|
|
13011
13258
|
// rule as spawnAgent above: depth always set so subtree/depth
|
|
@@ -13032,7 +13279,8 @@ ${message}`;
|
|
|
13032
13279
|
emitter: new EventEmitter(),
|
|
13033
13280
|
busy: false,
|
|
13034
13281
|
textBuf: "",
|
|
13035
|
-
thoughtBuf: ""
|
|
13282
|
+
thoughtBuf: "",
|
|
13283
|
+
...isConversationTerminal(desc) ? { conversationTerminal: true } : {}
|
|
13036
13284
|
};
|
|
13037
13285
|
rt.emitter.setMaxListeners(50);
|
|
13038
13286
|
sessions.set(id, rt);
|
|
@@ -13044,6 +13292,10 @@ ${message}`;
|
|
|
13044
13292
|
depth: desc.depth ?? 0,
|
|
13045
13293
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
13046
13294
|
});
|
|
13295
|
+
if (rt.conversationTerminal) {
|
|
13296
|
+
if (desc.adapterSessionId) recordConversationLink(rt);
|
|
13297
|
+
else startConversationTerminalLinkProbe(rt);
|
|
13298
|
+
}
|
|
13047
13299
|
pty.onData((chunk) => {
|
|
13048
13300
|
const buf = Buffer.from(chunk, "utf8");
|
|
13049
13301
|
rt.bracketedPaste = scanBracketedPaste(
|
|
@@ -13254,14 +13506,14 @@ ${message}`;
|
|
|
13254
13506
|
};
|
|
13255
13507
|
rtPre.desc.promptQueue = opts2.force ? [item, ...rtPre.desc.promptQueue ?? []] : [...rtPre.desc.promptQueue ?? [], item];
|
|
13256
13508
|
schedulePersist();
|
|
13257
|
-
return;
|
|
13509
|
+
return { queued: true };
|
|
13258
13510
|
}
|
|
13259
13511
|
await maybeResumeAgent(rtPre);
|
|
13260
13512
|
const rt = validateAgentTurn(id, "enqueuePrompt");
|
|
13261
13513
|
const structuredAnswer = matchStructuredQuestionAnswer(rt, message);
|
|
13262
13514
|
if (structuredAnswer) {
|
|
13263
13515
|
await answerStructuredQuestion(rt, structuredAnswer);
|
|
13264
|
-
return;
|
|
13516
|
+
return { queued: false };
|
|
13265
13517
|
}
|
|
13266
13518
|
void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
|
|
13267
13519
|
appendLine(
|
|
@@ -13270,6 +13522,7 @@ ${message}`;
|
|
|
13270
13522
|
"stderr"
|
|
13271
13523
|
);
|
|
13272
13524
|
});
|
|
13525
|
+
return { queued: false };
|
|
13273
13526
|
},
|
|
13274
13527
|
removeQueuedPrompt(id, queueId) {
|
|
13275
13528
|
const rt = sessions.get(id);
|
|
@@ -13529,6 +13782,7 @@ ${message}`;
|
|
|
13529
13782
|
stampInterrupted(desc);
|
|
13530
13783
|
stampCurrentStatus(rt);
|
|
13531
13784
|
stampWatchers(desc);
|
|
13785
|
+
stampLiveModes(desc, rt);
|
|
13532
13786
|
desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
|
|
13533
13787
|
desc.queuedPrompts = desc.promptQueue?.length ?? 0;
|
|
13534
13788
|
return desc;
|
|
@@ -13536,10 +13790,30 @@ ${message}`;
|
|
|
13536
13790
|
},
|
|
13537
13791
|
listSummaries(opts2) {
|
|
13538
13792
|
const includeArchived = opts2?.includeArchived ?? false;
|
|
13793
|
+
const lane = opts2?.lane;
|
|
13539
13794
|
const limit = Math.max(1, Math.min(200, opts2?.limit ?? 50));
|
|
13540
13795
|
const offset = Math.max(0, opts2?.offset ?? 0);
|
|
13541
13796
|
const childrenBusy = childrenBusyCounts();
|
|
13542
|
-
const
|
|
13797
|
+
const hasMachineLineage = (rt) => {
|
|
13798
|
+
const ownMachine = (desc) => desc.origin === "cron" || desc.origin === "gate";
|
|
13799
|
+
if (ownMachine(rt.desc)) return true;
|
|
13800
|
+
const seen = /* @__PURE__ */ new Set([rt.desc.id]);
|
|
13801
|
+
let current = rt.desc;
|
|
13802
|
+
while (current.parentSessionId) {
|
|
13803
|
+
const parent = sessions.get(current.parentSessionId)?.desc;
|
|
13804
|
+
if (!parent || seen.has(parent.id)) return true;
|
|
13805
|
+
if (parent.kind === "terminal" || parent.kind === "command") return true;
|
|
13806
|
+
seen.add(parent.id);
|
|
13807
|
+
current = parent;
|
|
13808
|
+
}
|
|
13809
|
+
return ownMachine(current);
|
|
13810
|
+
};
|
|
13811
|
+
const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).filter((rt) => {
|
|
13812
|
+
if (!lane) return true;
|
|
13813
|
+
if (isShellOnlyRow(rt.desc)) return false;
|
|
13814
|
+
const machine = hasMachineLineage(rt);
|
|
13815
|
+
return lane === "auto" ? machine : !machine;
|
|
13816
|
+
}).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
|
|
13543
13817
|
const slice = all.slice(offset, offset + limit);
|
|
13544
13818
|
const summaries = slice.map((rt) => {
|
|
13545
13819
|
const desc = rt.desc;
|
|
@@ -13549,7 +13823,11 @@ ${message}`;
|
|
|
13549
13823
|
stampWatchers(desc);
|
|
13550
13824
|
desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
|
|
13551
13825
|
desc.queuedPrompts = desc.promptQueue?.length ?? 0;
|
|
13552
|
-
|
|
13826
|
+
const summary = toSessionSummary(desc);
|
|
13827
|
+
if (!isShellOnlyRow(desc)) {
|
|
13828
|
+
summary.lane = hasMachineLineage(rt) ? "auto" : "agents";
|
|
13829
|
+
}
|
|
13830
|
+
return summary;
|
|
13553
13831
|
});
|
|
13554
13832
|
return { summaries, total: all.length };
|
|
13555
13833
|
},
|
|
@@ -13561,6 +13839,7 @@ ${message}`;
|
|
|
13561
13839
|
stampInterrupted(desc);
|
|
13562
13840
|
stampCurrentStatus(rt);
|
|
13563
13841
|
stampWatchers(desc);
|
|
13842
|
+
stampLiveModes(desc, rt);
|
|
13564
13843
|
desc.childrenBusy = childrenBusyCounts().get(desc.id) ?? 0;
|
|
13565
13844
|
desc.queuedPrompts = desc.promptQueue?.length ?? 0;
|
|
13566
13845
|
}
|
|
@@ -17859,9 +18138,27 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17859
18138
|
try {
|
|
17860
18139
|
if (strategy.kind === "pty-native" || strategy.kind === "pty-plain") {
|
|
17861
18140
|
if (!ptyEnabled) return ptyNotConfigured("session_restart");
|
|
17862
|
-
|
|
18141
|
+
let argv = strategy.kind === "pty-native" ? strategy.argv : Array.isArray(prev.argv) && prev.argv.length > 0 ? [...prev.argv] : tokenizeCommand(prev.command);
|
|
18142
|
+
if (strategy.kind === "pty-native" && prev.adapterSlug) {
|
|
18143
|
+
const probe = await probeNativeTranscript(augmented);
|
|
18144
|
+
if (probe?.exists) {
|
|
18145
|
+
const upgraded = RESUME_STRATEGIES[prev.adapterSlug]?.spawnArgs?.(probe.path);
|
|
18146
|
+
if (upgraded) {
|
|
18147
|
+
argv = upgraded;
|
|
18148
|
+
console.log(
|
|
18149
|
+
`[session_restart] ${prev.id} pty-native resume upgraded to absolute transcript path ${probe.path}`
|
|
18150
|
+
);
|
|
18151
|
+
}
|
|
18152
|
+
}
|
|
18153
|
+
}
|
|
17863
18154
|
const envVarName = strategy.kind === "pty-native" && prev.adapterSlug ? RESUME_STRATEGIES[prev.adapterSlug]?.configDirEnvVar : void 0;
|
|
17864
|
-
const ptyEnv = strategy.kind === "pty-native" ? envVarName && augmented.adapterConfigDir ? { [envVarName]: augmented.adapterConfigDir } :
|
|
18155
|
+
const ptyEnv = strategy.kind === "pty-native" ? envVarName && augmented.adapterConfigDir ? { [envVarName]: augmented.adapterConfigDir } : (
|
|
18156
|
+
// A pty-native restart of a row that is ITSELF a restarted
|
|
18157
|
+
// PTY (conversation terminal): no adapterConfigDir, but the
|
|
18158
|
+
// env the earlier hop recorded still names the isolated
|
|
18159
|
+
// store — replay it rather than dropping to the global one.
|
|
18160
|
+
prev.ptyResumeEnv
|
|
18161
|
+
) : prev.ptyResumeEnv;
|
|
17865
18162
|
if (strategy.kind === "pty-native") {
|
|
17866
18163
|
console.log(
|
|
17867
18164
|
`[session_restart] ${prev.id} pty-native env: ` + (ptyEnv ? `${Object.keys(ptyEnv).join(",")} threaded from adapterConfigDir` : envVarName ? "no adapterConfigDir on descriptor \u2014 resume may miss the isolated store" : `adapter "${prev.adapterSlug}" declares no configDirEnvVar \u2014 resume uses the global store`)
|
|
@@ -17943,6 +18240,15 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17943
18240
|
isError: true
|
|
17944
18241
|
};
|
|
17945
18242
|
}
|
|
18243
|
+
let nativeResumeDecline;
|
|
18244
|
+
if (input.preferNativeTerminal === true && prev.adapterSlug && RESUME_STRATEGIES[prev.adapterSlug]?.spawnArgs) {
|
|
18245
|
+
if (prev.nativeTerminalResume !== true) {
|
|
18246
|
+
nativeResumeDecline = { reason: "capability-missing" };
|
|
18247
|
+
} else {
|
|
18248
|
+
const probe = await probeNativeTranscript(augmented);
|
|
18249
|
+
nativeResumeDecline = probe ? { reason: "transcript-not-found", probedDir: probe.dir } : { reason: "no-resume-id" };
|
|
18250
|
+
}
|
|
18251
|
+
}
|
|
17946
18252
|
const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
|
|
17947
18253
|
...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
|
|
17948
18254
|
});
|
|
@@ -17955,7 +18261,8 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17955
18261
|
...restarted.desc,
|
|
17956
18262
|
resumedFrom: restarted.resumedFrom,
|
|
17957
18263
|
resumeVia: restarted.resumeVia,
|
|
17958
|
-
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
18264
|
+
...restarted.resumeFallback ? { resumeFallback: true } : {},
|
|
18265
|
+
...nativeResumeDecline ? { nativeResumeDecline } : {}
|
|
17959
18266
|
},
|
|
17960
18267
|
null,
|
|
17961
18268
|
2
|
|
@@ -20249,13 +20556,34 @@ function registerWebSearchTools(server, opts = {}) {
|
|
|
20249
20556
|
transformers: [catchErrors(), enrichResolverRejections()]
|
|
20250
20557
|
});
|
|
20251
20558
|
}
|
|
20559
|
+
var embedTokens = /* @__PURE__ */ new Map();
|
|
20560
|
+
function mintAppEmbedToken(appId) {
|
|
20561
|
+
const token = randomBytes(24).toString("base64url");
|
|
20562
|
+
embedTokens.set(token, appId);
|
|
20563
|
+
return token;
|
|
20564
|
+
}
|
|
20565
|
+
var stableTokens = /* @__PURE__ */ new Map();
|
|
20566
|
+
function stableAppEmbedToken(appId) {
|
|
20567
|
+
const existing = stableTokens.get(appId);
|
|
20568
|
+
if (existing) return existing;
|
|
20569
|
+
const token = mintAppEmbedToken(appId);
|
|
20570
|
+
stableTokens.set(appId, token);
|
|
20571
|
+
return token;
|
|
20572
|
+
}
|
|
20573
|
+
function isValidAppEmbedToken(token) {
|
|
20574
|
+
return typeof token === "string" && token.length > 0 && embedTokens.has(token);
|
|
20575
|
+
}
|
|
20252
20576
|
|
|
20253
20577
|
// src/mcp-apps-adapter.ts
|
|
20254
20578
|
var MIME_TYPE = "text/html;profile=mcp-app";
|
|
20579
|
+
var EMBED_TOKEN_PLACEHOLDER = '"__AGENPROTO_EMBED_TOKEN__"';
|
|
20255
20580
|
function registerMcpApps(server, apps) {
|
|
20256
20581
|
for (const app of apps) {
|
|
20257
20582
|
const resourceUri = `ui://${app.id}/view`;
|
|
20258
|
-
|
|
20583
|
+
let html = typeof app.html === "string" ? app.html : app.html({});
|
|
20584
|
+
if (html.includes(EMBED_TOKEN_PLACEHOLDER)) {
|
|
20585
|
+
html = html.split(EMBED_TOKEN_PLACEHOLDER).join(JSON.stringify(mintAppEmbedToken(app.id)));
|
|
20586
|
+
}
|
|
20259
20587
|
const resourceMeta = {
|
|
20260
20588
|
ui: {
|
|
20261
20589
|
prefersBorder: true,
|
|
@@ -20326,7 +20654,11 @@ function makeBuiltinPanelApps(ops) {
|
|
|
20326
20654
|
// installed, install notice otherwise; see apps/src/session-chat).
|
|
20327
20655
|
makeSessionChatApp({
|
|
20328
20656
|
httpBaseUrl: ops.httpBaseUrl,
|
|
20329
|
-
isSessionChatInstalled: ops.isSessionChatInstalled
|
|
20657
|
+
isSessionChatInstalled: ops.isSessionChatInstalled,
|
|
20658
|
+
// Hand every result a live embed token so a host-cached widget
|
|
20659
|
+
// (Claude Desktop's per-conversation srcdoc) re-arms itself after a
|
|
20660
|
+
// daemon restart instead of 403ing on its dead baked token.
|
|
20661
|
+
mintEmbedToken: () => stableAppEmbedToken("agentproto_session_chat")
|
|
20330
20662
|
}),
|
|
20331
20663
|
// Work-board widget — kanban over the Task ledger (see apps/src/
|
|
20332
20664
|
// work-board). Read path only; writes go through task_claim/
|
|
@@ -22891,7 +23223,7 @@ var STANDALONE_REST_BRIDGE_SCRIPT = `<script>
|
|
|
22891
23223
|
return res.json().catch(function () {
|
|
22892
23224
|
throw new Error("tool-call failed: HTTP " + res.status);
|
|
22893
23225
|
}).then(function (body) {
|
|
22894
|
-
if (!res.ok) throw new Error((body && body.error) || ("tool-call failed: HTTP " + res.status));
|
|
23226
|
+
if (!res.ok) throw new Error((body && (body.message || body.error)) || ("tool-call failed: HTTP " + res.status));
|
|
22895
23227
|
return body;
|
|
22896
23228
|
});
|
|
22897
23229
|
});
|
|
@@ -23201,6 +23533,22 @@ async function sendOutbound(provider, input, deps2) {
|
|
|
23201
23533
|
return { ok: false, error: "unsupported_provider" };
|
|
23202
23534
|
}
|
|
23203
23535
|
}
|
|
23536
|
+
function unwrapMcpToolResult(result) {
|
|
23537
|
+
if (!result || typeof result !== "object") return {};
|
|
23538
|
+
const envelope = result;
|
|
23539
|
+
if (envelope.structuredContent && typeof envelope.structuredContent === "object") {
|
|
23540
|
+
return envelope.structuredContent;
|
|
23541
|
+
}
|
|
23542
|
+
const text10 = envelope.content?.find((c) => c.type === "text")?.text;
|
|
23543
|
+
if (typeof text10 === "string") {
|
|
23544
|
+
try {
|
|
23545
|
+
const parsed = JSON.parse(text10);
|
|
23546
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
23547
|
+
} catch {
|
|
23548
|
+
}
|
|
23549
|
+
}
|
|
23550
|
+
return result;
|
|
23551
|
+
}
|
|
23204
23552
|
async function sendAgentpush(input, deps2) {
|
|
23205
23553
|
const alias = input.alias;
|
|
23206
23554
|
if (!alias) {
|
|
@@ -23230,7 +23578,7 @@ async function sendAgentpush(input, deps2) {
|
|
|
23230
23578
|
if (!upload.ok) {
|
|
23231
23579
|
return { ok: false, error: `upload_media_failed: ${upload.error}` };
|
|
23232
23580
|
}
|
|
23233
|
-
const result =
|
|
23581
|
+
const result = unwrapMcpToolResult(upload.result);
|
|
23234
23582
|
const providerMediaId = typeof result.media_id === "string" ? result.media_id : typeof result.id === "string" ? result.id : void 0;
|
|
23235
23583
|
if (!providerMediaId) {
|
|
23236
23584
|
return { ok: false, error: "upload_media_missing_media_id" };
|
|
@@ -23255,7 +23603,18 @@ async function sendAgentpush(input, deps2) {
|
|
|
23255
23603
|
if (!out.ok) {
|
|
23256
23604
|
return { ok: false, error: out.error };
|
|
23257
23605
|
}
|
|
23258
|
-
const
|
|
23606
|
+
const payload = unwrapMcpToolResult(out.result);
|
|
23607
|
+
const status = typeof payload.status === "string" ? payload.status : void 0;
|
|
23608
|
+
if (status === "blocked") {
|
|
23609
|
+
const blockedReason = typeof payload.blocked_reason === "string" ? payload.blocked_reason : "blocked";
|
|
23610
|
+
const suggestion = typeof payload.suggestion === "string" ? payload.suggestion : void 0;
|
|
23611
|
+
return { ok: false, error: blockedReason, blockedReason, ...suggestion ? { suggestion } : {} };
|
|
23612
|
+
}
|
|
23613
|
+
if (status === "failed") {
|
|
23614
|
+
const error = typeof payload.error === "string" ? payload.error : "failed";
|
|
23615
|
+
return { ok: false, error };
|
|
23616
|
+
}
|
|
23617
|
+
const providerMessageId = extractProviderMessageId(payload);
|
|
23259
23618
|
return { ok: true, providerMessageId };
|
|
23260
23619
|
}
|
|
23261
23620
|
async function sendTelegram(input, deps2) {
|
|
@@ -23280,12 +23639,7 @@ async function sendTelegram(input, deps2) {
|
|
|
23280
23639
|
parse_mode: "MarkdownV2"
|
|
23281
23640
|
})
|
|
23282
23641
|
});
|
|
23283
|
-
|
|
23284
|
-
const body2 = await res2.text().catch(() => "");
|
|
23285
|
-
return { ok: false, error: `telegram_http_${res2.status}: ${body2}` };
|
|
23286
|
-
}
|
|
23287
|
-
const json2 = await res2.json().catch(() => ({}));
|
|
23288
|
-
return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
|
|
23642
|
+
return parseTelegramSendResponse(res2);
|
|
23289
23643
|
}
|
|
23290
23644
|
const files = [];
|
|
23291
23645
|
for (const att of input.attachments) {
|
|
@@ -23319,12 +23673,7 @@ async function sendTelegram(input, deps2) {
|
|
|
23319
23673
|
headers: { "Content-Type": contentType2 },
|
|
23320
23674
|
body: toFetchBody(body2)
|
|
23321
23675
|
});
|
|
23322
|
-
|
|
23323
|
-
const bodyText = await res2.text().catch(() => "");
|
|
23324
|
-
return { ok: false, error: `telegram_http_${res2.status}: ${bodyText}` };
|
|
23325
|
-
}
|
|
23326
|
-
const json2 = await res2.json().catch(() => ({}));
|
|
23327
|
-
return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
|
|
23676
|
+
return parseTelegramSendResponse(res2);
|
|
23328
23677
|
}
|
|
23329
23678
|
const mediaJson = input.attachments.map((att, index) => {
|
|
23330
23679
|
const item = {
|
|
@@ -23357,11 +23706,18 @@ async function sendTelegram(input, deps2) {
|
|
|
23357
23706
|
headers: { "Content-Type": contentType },
|
|
23358
23707
|
body: toFetchBody(body)
|
|
23359
23708
|
});
|
|
23709
|
+
return parseTelegramSendResponse(res);
|
|
23710
|
+
}
|
|
23711
|
+
async function parseTelegramSendResponse(res) {
|
|
23360
23712
|
if (!res.ok) {
|
|
23361
|
-
const
|
|
23362
|
-
return { ok: false, error: `telegram_http_${res.status}: ${
|
|
23713
|
+
const body = await res.text().catch(() => "");
|
|
23714
|
+
return { ok: false, error: `telegram_http_${res.status}: ${body}` };
|
|
23363
23715
|
}
|
|
23364
23716
|
const json = await res.json().catch(() => ({}));
|
|
23717
|
+
if (json.ok !== true) {
|
|
23718
|
+
const description = typeof json.description === "string" ? json.description : "unknown_error";
|
|
23719
|
+
return { ok: false, error: `telegram_error: ${description}` };
|
|
23720
|
+
}
|
|
23365
23721
|
return { ok: true, providerMessageId: extractTelegramMessageId(json) };
|
|
23366
23722
|
}
|
|
23367
23723
|
function extractProviderMessageId(result) {
|
|
@@ -26308,7 +26664,16 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
26308
26664
|
if (!out.ok) {
|
|
26309
26665
|
return {
|
|
26310
26666
|
content: [
|
|
26311
|
-
{
|
|
26667
|
+
{
|
|
26668
|
+
type: "text",
|
|
26669
|
+
text: JSON.stringify({
|
|
26670
|
+
sent: false,
|
|
26671
|
+
bound: false,
|
|
26672
|
+
error: out.error,
|
|
26673
|
+
...out.blockedReason ? { blocked_reason: out.blockedReason } : {},
|
|
26674
|
+
...out.suggestion ? { suggestion: out.suggestion } : {}
|
|
26675
|
+
})
|
|
26676
|
+
}
|
|
26312
26677
|
],
|
|
26313
26678
|
isError: true
|
|
26314
26679
|
};
|
|
@@ -26324,7 +26689,18 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
26324
26689
|
provider
|
|
26325
26690
|
});
|
|
26326
26691
|
}
|
|
26327
|
-
return {
|
|
26692
|
+
return {
|
|
26693
|
+
content: [
|
|
26694
|
+
{
|
|
26695
|
+
type: "text",
|
|
26696
|
+
text: JSON.stringify({
|
|
26697
|
+
sent: true,
|
|
26698
|
+
bound,
|
|
26699
|
+
...out.providerMessageId ? { message_id: out.providerMessageId } : {}
|
|
26700
|
+
})
|
|
26701
|
+
}
|
|
26702
|
+
]
|
|
26703
|
+
};
|
|
26328
26704
|
}
|
|
26329
26705
|
);
|
|
26330
26706
|
}
|
|
@@ -26758,7 +27134,7 @@ async function startHttpServer(opts) {
|
|
|
26758
27134
|
}
|
|
26759
27135
|
function authorizeMcp(req, res) {
|
|
26760
27136
|
const origin = req.headers.origin;
|
|
26761
|
-
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
|
|
27137
|
+
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
|
|
26762
27138
|
const auth = readAuth();
|
|
26763
27139
|
const header = req.headers.authorization;
|
|
26764
27140
|
if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
|
|
@@ -26788,8 +27164,12 @@ async function startHttpServer(opts) {
|
|
|
26788
27164
|
}
|
|
26789
27165
|
const origin = req.headers.origin;
|
|
26790
27166
|
if (typeof origin === "string" && originAllowed(origin)) return "ok";
|
|
27167
|
+
if (embedTokenTrusted(req)) return "ok";
|
|
26791
27168
|
return header ? "bad" : "missing";
|
|
26792
27169
|
}
|
|
27170
|
+
function embedTokenTrusted(req) {
|
|
27171
|
+
return isValidAppEmbedToken(requestEmbedToken(req));
|
|
27172
|
+
}
|
|
26793
27173
|
function originAllowed(origin) {
|
|
26794
27174
|
const list = opts.strictOrigins ? opts.allowedOrigins ?? [] : [...DEFAULT_ALLOWED_ORIGINS, ...opts.allowedOrigins ?? []];
|
|
26795
27175
|
for (const pattern of list) {
|
|
@@ -27075,7 +27455,7 @@ async function startHttpServer(opts) {
|
|
|
27075
27455
|
}
|
|
27076
27456
|
function guardBrowserOrigin(req, res) {
|
|
27077
27457
|
const origin = req.headers.origin;
|
|
27078
|
-
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
|
|
27458
|
+
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
|
|
27079
27459
|
const auth = readAuth();
|
|
27080
27460
|
const header = req.headers.authorization;
|
|
27081
27461
|
if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
|
|
@@ -27094,10 +27474,14 @@ async function startHttpServer(opts) {
|
|
|
27094
27474
|
}
|
|
27095
27475
|
function applyCors(req, res) {
|
|
27096
27476
|
const origin = req.headers.origin;
|
|
27097
|
-
const
|
|
27098
|
-
|
|
27477
|
+
const tokenTrusted = embedTokenTrusted(req);
|
|
27478
|
+
const originTrusted = typeof origin === "string" && origin.length > 0 && originAllowed(origin);
|
|
27479
|
+
const trusted = originTrusted || tokenTrusted;
|
|
27480
|
+
if (originTrusted) {
|
|
27099
27481
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
27100
27482
|
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
27483
|
+
} else if (tokenTrusted && origin && origin !== "null") {
|
|
27484
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
27101
27485
|
} else {
|
|
27102
27486
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
27103
27487
|
}
|
|
@@ -28065,7 +28449,9 @@ async function startHttpServer(opts) {
|
|
|
28065
28449
|
const uiMatch = path.match(/^\/apps\/(.+)\/ui$/);
|
|
28066
28450
|
if (uiMatch && req.method === "GET") {
|
|
28067
28451
|
const uiApp = opts.appRegistry.getApp(decodeURIComponent(uiMatch[1]));
|
|
28068
|
-
if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && guardBrowserOrigin(req, res))
|
|
28452
|
+
if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && !isValidAppEmbedToken(requestEmbedToken(req)) && guardBrowserOrigin(req, res)) {
|
|
28453
|
+
return;
|
|
28454
|
+
}
|
|
28069
28455
|
if (!authorize(req, res)) return;
|
|
28070
28456
|
await handleAppUiPage(
|
|
28071
28457
|
req,
|
|
@@ -28819,10 +29205,13 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
28819
29205
|
const queryString = reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : "";
|
|
28820
29206
|
const params = new URLSearchParams(queryString);
|
|
28821
29207
|
const includeArchived = params.get("includeArchived") === "true";
|
|
29208
|
+
const laneParam = params.get("lane");
|
|
29209
|
+
const lane = laneParam === "agents" || laneParam === "auto" ? laneParam : void 0;
|
|
28822
29210
|
const limit = Number.parseInt(params.get("limit") ?? "", 10);
|
|
28823
29211
|
const offset = Number.parseInt(params.get("offset") ?? "", 10);
|
|
28824
29212
|
const result = registry.listSummaries({
|
|
28825
29213
|
includeArchived,
|
|
29214
|
+
lane,
|
|
28826
29215
|
limit: Number.isNaN(limit) ? void 0 : limit,
|
|
28827
29216
|
offset: Number.isNaN(offset) ? void 0 : offset
|
|
28828
29217
|
});
|
|
@@ -30810,6 +31199,16 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
30810
31199
|
if (frameAncestors.length === 0) {
|
|
30811
31200
|
headers["x-frame-options"] = "SAMEORIGIN";
|
|
30812
31201
|
}
|
|
31202
|
+
if (embedRequested && !isValidAppEmbedToken(requestEmbedToken(req))) {
|
|
31203
|
+
console.warn(
|
|
31204
|
+
`[app-ui] embed refused for "${appId}": ` + JSON.stringify({
|
|
31205
|
+
secFetchDest: req.headers["sec-fetch-dest"] ?? null,
|
|
31206
|
+
origin: req.headers.origin ?? null,
|
|
31207
|
+
referer: req.headers.referer ?? null,
|
|
31208
|
+
embedToken: requestEmbedToken(req) != null
|
|
31209
|
+
})
|
|
31210
|
+
);
|
|
31211
|
+
}
|
|
30813
31212
|
}
|
|
30814
31213
|
res.writeHead(200, headers);
|
|
30815
31214
|
res.end(injectStandaloneAppBridge(raw));
|
|
@@ -30817,6 +31216,7 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
30817
31216
|
function iframeEmbedAllowed(req, app) {
|
|
30818
31217
|
const secFetchDest = req.headers["sec-fetch-dest"];
|
|
30819
31218
|
if (secFetchDest !== void 0 && secFetchDest !== "iframe") return false;
|
|
31219
|
+
if (isValidAppEmbedToken(requestEmbedToken(req))) return true;
|
|
30820
31220
|
const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
|
|
30821
31221
|
const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
|
|
30822
31222
|
if (!origin && !referer) return false;
|
|
@@ -30837,6 +31237,13 @@ function iframeEmbedAllowed(req, app) {
|
|
|
30837
31237
|
}
|
|
30838
31238
|
return false;
|
|
30839
31239
|
}
|
|
31240
|
+
function requestEmbedToken(req) {
|
|
31241
|
+
try {
|
|
31242
|
+
return new URL(req.url ?? "/", "http://localhost").searchParams.get("et");
|
|
31243
|
+
} catch {
|
|
31244
|
+
return null;
|
|
31245
|
+
}
|
|
31246
|
+
}
|
|
30840
31247
|
function iframeEmbedOriginAllowed(req, app) {
|
|
30841
31248
|
const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
|
|
30842
31249
|
const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
|
|
@@ -34795,6 +35202,7 @@ function createOrchestratorMcpServerFactory(deps2) {
|
|
|
34795
35202
|
...deps2.listHarnessCapabilities ? { listHarnessCapabilities: deps2.listHarnessCapabilities } : {},
|
|
34796
35203
|
...deps2.webhookNotifier ? { webhookNotifier: deps2.webhookNotifier } : {},
|
|
34797
35204
|
...deps2.resolveSandboxProvider ? { resolveSandboxProvider: deps2.resolveSandboxProvider } : {},
|
|
35205
|
+
...deps2.defaultAgentPromptInterrupt != null ? { defaultAgentPromptInterrupt: deps2.defaultAgentPromptInterrupt } : {},
|
|
34798
35206
|
daemonMcpUrl: deps2.daemonMcpUrl
|
|
34799
35207
|
});
|
|
34800
35208
|
registerOrchestrationTools(server, {
|
|
@@ -38235,6 +38643,10 @@ async function createGateway(opts) {
|
|
|
38235
38643
|
// can declare `sandbox` and resolve a provider, instead of throwing
|
|
38236
38644
|
// `sandbox_provider_not_found`.
|
|
38237
38645
|
resolveSandboxProvider: resolveSandboxProviderResolved,
|
|
38646
|
+
// config.json `defaults.agentPromptInterrupt` — same unset-default the
|
|
38647
|
+
// root /mcp surface uses, so a child driving/reporting through the scoped
|
|
38648
|
+
// orchestrator gateway honours it too.
|
|
38649
|
+
...configDefaults?.agentPromptInterrupt != null ? { defaultAgentPromptInterrupt: configDefaults.agentPromptInterrupt } : {},
|
|
38238
38650
|
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
38239
38651
|
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|
|
38240
38652
|
...opts.listHarnessCapabilities ? { listHarnessCapabilities: opts.listHarnessCapabilities } : {}
|
|
@@ -38295,6 +38707,9 @@ async function createGateway(opts) {
|
|
|
38295
38707
|
webhookNotifier,
|
|
38296
38708
|
daemonMcpUrl,
|
|
38297
38709
|
resolveSandboxProvider: resolveSandboxProviderResolved,
|
|
38710
|
+
// config.json `defaults.agentPromptInterrupt` — the unset-default for
|
|
38711
|
+
// `interrupt` on agent_prompt / message_parent. Omitted ⇒ false.
|
|
38712
|
+
...configDefaults?.agentPromptInterrupt != null ? { defaultAgentPromptInterrupt: configDefaults.agentPromptInterrupt } : {},
|
|
38298
38713
|
...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
|
|
38299
38714
|
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
38300
38715
|
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|