@agentproto/runtime 3.4.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 +1 -1
- package/dist/catalog-models.mjs.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/index.d.ts +45 -6
- package/dist/index.mjs +426 -54
- 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 +10 -10
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) {
|
|
@@ -8261,8 +8340,10 @@ function registerAgentTools(server, opts) {
|
|
|
8261
8340
|
resolveSandboxProvider: resolveSandboxProvider2,
|
|
8262
8341
|
provisionWorktree,
|
|
8263
8342
|
resolveWorktreeIsolation,
|
|
8264
|
-
supervisor
|
|
8343
|
+
supervisor,
|
|
8344
|
+
defaultAgentPromptInterrupt
|
|
8265
8345
|
} = opts;
|
|
8346
|
+
const interruptDefault = defaultAgentPromptInterrupt ?? false;
|
|
8266
8347
|
server.registerTool(
|
|
8267
8348
|
"agent_start",
|
|
8268
8349
|
{
|
|
@@ -8498,14 +8579,20 @@ function registerAgentTools(server, opts) {
|
|
|
8498
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`."
|
|
8499
8580
|
)
|
|
8500
8581
|
},
|
|
8501
|
-
//
|
|
8502
|
-
//
|
|
8503
|
-
// the tool-definition level — same mechanism
|
|
8504
|
-
// mcp-apps-adapter.ts).
|
|
8505
|
-
//
|
|
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.
|
|
8506
8593
|
_meta: {
|
|
8507
8594
|
ui: {
|
|
8508
|
-
resourceUri: "ui://
|
|
8595
|
+
resourceUri: "ui://agentproto_session_chat/view",
|
|
8509
8596
|
visibility: ["model", "app"]
|
|
8510
8597
|
}
|
|
8511
8598
|
}
|
|
@@ -8618,7 +8705,7 @@ function registerAgentTools(server, opts) {
|
|
|
8618
8705
|
id: sessionIdAliasField,
|
|
8619
8706
|
prompt: z.string().min(1).describe("The next user turn (plain text)."),
|
|
8620
8707
|
interrupt: z.boolean().optional().describe(
|
|
8621
|
-
"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."
|
|
8622
8709
|
),
|
|
8623
8710
|
queue: z.boolean().optional().describe(
|
|
8624
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."
|
|
@@ -8629,8 +8716,9 @@ function registerAgentTools(server, opts) {
|
|
|
8629
8716
|
if (!sessionId) return missingSessionIdError("agent_prompt");
|
|
8630
8717
|
try {
|
|
8631
8718
|
const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
|
|
8632
|
-
|
|
8633
|
-
|
|
8719
|
+
const effectiveInterrupt = input.interrupt ?? interruptDefault;
|
|
8720
|
+
const { queued } = await registry.enqueuePrompt(sessionId, input.prompt, {
|
|
8721
|
+
interrupt: effectiveInterrupt,
|
|
8634
8722
|
// Queue by default: a mid-turn session holds the prompt in its
|
|
8635
8723
|
// FIFO queue and dispatches it at turn end, so callers never
|
|
8636
8724
|
// lose a prompt to the busy rejection. Explicit `queue: false`
|
|
@@ -8638,12 +8726,21 @@ function registerAgentTools(server, opts) {
|
|
|
8638
8726
|
queue: input.queue ?? true,
|
|
8639
8727
|
...promptSource ? { source: `agent:${promptSource}` } : {}
|
|
8640
8728
|
});
|
|
8729
|
+
const hintQueued = queued && input.interrupt === void 0;
|
|
8641
8730
|
return {
|
|
8642
8731
|
content: [
|
|
8643
8732
|
{
|
|
8644
8733
|
type: "text",
|
|
8645
8734
|
text: JSON.stringify(
|
|
8646
|
-
{
|
|
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
|
+
},
|
|
8647
8744
|
null,
|
|
8648
8745
|
2
|
|
8649
8746
|
)
|
|
@@ -8672,9 +8769,12 @@ function registerAgentTools(server, opts) {
|
|
|
8672
8769
|
);
|
|
8673
8770
|
server.tool(
|
|
8674
8771
|
"message_parent",
|
|
8675
|
-
"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.",
|
|
8676
8773
|
{
|
|
8677
|
-
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
|
+
)
|
|
8678
8778
|
},
|
|
8679
8779
|
async (input) => {
|
|
8680
8780
|
const fail = (text10) => ({
|
|
@@ -8708,11 +8808,12 @@ function registerAgentTools(server, opts) {
|
|
|
8708
8808
|
}
|
|
8709
8809
|
const who = self.label ?? selfId;
|
|
8710
8810
|
const notice = `[child-message] ${who} (${selfId}): ${input.message}`;
|
|
8711
|
-
const
|
|
8811
|
+
const effectiveInterrupt = input.interrupt ?? interruptDefault;
|
|
8812
|
+
const done = (delivery, extra) => ({
|
|
8712
8813
|
content: [
|
|
8713
8814
|
{
|
|
8714
8815
|
type: "text",
|
|
8715
|
-
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery })
|
|
8816
|
+
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery, ...extra })
|
|
8716
8817
|
}
|
|
8717
8818
|
]
|
|
8718
8819
|
});
|
|
@@ -8723,12 +8824,27 @@ function registerAgentTools(server, opts) {
|
|
|
8723
8824
|
} catch {
|
|
8724
8825
|
}
|
|
8725
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
|
+
}
|
|
8726
8837
|
if (!registry.stampPendingChildCrashNotice(parentId, notice)) {
|
|
8727
8838
|
return fail(
|
|
8728
8839
|
`message_parent: parent session "${parentId}" vanished mid-delivery.`
|
|
8729
8840
|
);
|
|
8730
8841
|
}
|
|
8731
|
-
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
|
+
);
|
|
8732
8848
|
}
|
|
8733
8849
|
);
|
|
8734
8850
|
server.tool(
|
|
@@ -10915,6 +11031,9 @@ function promptOriginLabel(item) {
|
|
|
10915
11031
|
if (code.startsWith("child:")) return `child ${code.slice("child:".length)}`;
|
|
10916
11032
|
return code;
|
|
10917
11033
|
}
|
|
11034
|
+
function isShellOnlyRow(desc) {
|
|
11035
|
+
return desc.kind === "command" || desc.kind === "terminal" && !isConversationTerminal(desc);
|
|
11036
|
+
}
|
|
10918
11037
|
function toSessionSummary(desc) {
|
|
10919
11038
|
return {
|
|
10920
11039
|
id: desc.id,
|
|
@@ -11308,6 +11427,7 @@ function createSessionsRegistry(opts) {
|
|
|
11308
11427
|
}
|
|
11309
11428
|
const emitExited = (rt) => {
|
|
11310
11429
|
if (rt.exitedEmitted) return;
|
|
11430
|
+
rt.linkProbeStop?.();
|
|
11311
11431
|
cancelPendingPermissionsForSession(rt);
|
|
11312
11432
|
if (runWorktreeAutoReclaim && rt.desc.worktreeAutoProvisioned && rt.desc.worktreePath) {
|
|
11313
11433
|
const worktreePath = rt.desc.worktreePath;
|
|
@@ -11489,8 +11609,9 @@ function createSessionsRegistry(opts) {
|
|
|
11489
11609
|
const recordConversationLink = (rt, adapterSessionIdOverride) => {
|
|
11490
11610
|
if (!persist || !partitioned) return;
|
|
11491
11611
|
const desc = rt.desc;
|
|
11492
|
-
|
|
11493
|
-
|
|
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);
|
|
11494
11615
|
const adapterSessionId = adapterSessionIdOverride ?? desc.adapterSessionId;
|
|
11495
11616
|
const cwd = desc.cwd;
|
|
11496
11617
|
if (!adapterSlug || !adapterSessionId || !cwd) return;
|
|
@@ -11534,6 +11655,81 @@ function createSessionsRegistry(opts) {
|
|
|
11534
11655
|
void persistSnapshot();
|
|
11535
11656
|
}, PERSIST_DEBOUNCE_MS2);
|
|
11536
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
|
+
};
|
|
11537
11733
|
const snapshotRows = () => Array.from(sessions.values()).map((s) => {
|
|
11538
11734
|
const {
|
|
11539
11735
|
processAlive: _processAlive,
|
|
@@ -11617,8 +11813,9 @@ function createSessionsRegistry(opts) {
|
|
|
11617
11813
|
rt.emitter.emit("line", { line, stream });
|
|
11618
11814
|
};
|
|
11619
11815
|
const sniffResumeHints = (rt, line) => {
|
|
11620
|
-
if (rt.desc.kind !== "agent-cli") return;
|
|
11621
|
-
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;
|
|
11622
11819
|
if (!strategy?.outputHint) return;
|
|
11623
11820
|
const plain = stripAnsiCodes(line);
|
|
11624
11821
|
const m = plain.match(strategy.outputHint);
|
|
@@ -11631,6 +11828,14 @@ function createSessionsRegistry(opts) {
|
|
|
11631
11828
|
recordConversationLink(rt, m[1]);
|
|
11632
11829
|
}
|
|
11633
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
|
+
};
|
|
11634
11839
|
const appendBytes = (rt, chunk) => {
|
|
11635
11840
|
terminalTranscriptWriter.appendChunk(rt.desc.id, chunk);
|
|
11636
11841
|
rt.recentBytes.push(chunk);
|
|
@@ -11640,6 +11845,7 @@ function createSessionsRegistry(opts) {
|
|
|
11640
11845
|
if (dropped) rt.recentBytesSize -= dropped.byteLength;
|
|
11641
11846
|
}
|
|
11642
11847
|
rt.desc.lastOutputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11848
|
+
if (rt.conversationTerminal === true) sniffConversationTerminalChunk(rt, chunk);
|
|
11643
11849
|
rt.emitter.emit("data", chunk);
|
|
11644
11850
|
};
|
|
11645
11851
|
const reconcilePtySize = (rt) => {
|
|
@@ -13010,6 +13216,19 @@ ${message}`;
|
|
|
13010
13216
|
rows: input.rows
|
|
13011
13217
|
});
|
|
13012
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;
|
|
13013
13232
|
const desc = {
|
|
13014
13233
|
id,
|
|
13015
13234
|
kind: "terminal",
|
|
@@ -13024,6 +13243,16 @@ ${message}`;
|
|
|
13024
13243
|
...worktreeFields(input.cwd),
|
|
13025
13244
|
...input.name ? { name: input.name } : {},
|
|
13026
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
|
+
} : {},
|
|
13027
13256
|
...priorCommandSessionId ? { priorCommandSessionId } : {},
|
|
13028
13257
|
// Parent attribution + depth (orchestrator WP4) — same recording
|
|
13029
13258
|
// rule as spawnAgent above: depth always set so subtree/depth
|
|
@@ -13050,7 +13279,8 @@ ${message}`;
|
|
|
13050
13279
|
emitter: new EventEmitter(),
|
|
13051
13280
|
busy: false,
|
|
13052
13281
|
textBuf: "",
|
|
13053
|
-
thoughtBuf: ""
|
|
13282
|
+
thoughtBuf: "",
|
|
13283
|
+
...isConversationTerminal(desc) ? { conversationTerminal: true } : {}
|
|
13054
13284
|
};
|
|
13055
13285
|
rt.emitter.setMaxListeners(50);
|
|
13056
13286
|
sessions.set(id, rt);
|
|
@@ -13062,6 +13292,10 @@ ${message}`;
|
|
|
13062
13292
|
depth: desc.depth ?? 0,
|
|
13063
13293
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
13064
13294
|
});
|
|
13295
|
+
if (rt.conversationTerminal) {
|
|
13296
|
+
if (desc.adapterSessionId) recordConversationLink(rt);
|
|
13297
|
+
else startConversationTerminalLinkProbe(rt);
|
|
13298
|
+
}
|
|
13065
13299
|
pty.onData((chunk) => {
|
|
13066
13300
|
const buf = Buffer.from(chunk, "utf8");
|
|
13067
13301
|
rt.bracketedPaste = scanBracketedPaste(
|
|
@@ -13272,14 +13506,14 @@ ${message}`;
|
|
|
13272
13506
|
};
|
|
13273
13507
|
rtPre.desc.promptQueue = opts2.force ? [item, ...rtPre.desc.promptQueue ?? []] : [...rtPre.desc.promptQueue ?? [], item];
|
|
13274
13508
|
schedulePersist();
|
|
13275
|
-
return;
|
|
13509
|
+
return { queued: true };
|
|
13276
13510
|
}
|
|
13277
13511
|
await maybeResumeAgent(rtPre);
|
|
13278
13512
|
const rt = validateAgentTurn(id, "enqueuePrompt");
|
|
13279
13513
|
const structuredAnswer = matchStructuredQuestionAnswer(rt, message);
|
|
13280
13514
|
if (structuredAnswer) {
|
|
13281
13515
|
await answerStructuredQuestion(rt, structuredAnswer);
|
|
13282
|
-
return;
|
|
13516
|
+
return { queued: false };
|
|
13283
13517
|
}
|
|
13284
13518
|
void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
|
|
13285
13519
|
appendLine(
|
|
@@ -13288,6 +13522,7 @@ ${message}`;
|
|
|
13288
13522
|
"stderr"
|
|
13289
13523
|
);
|
|
13290
13524
|
});
|
|
13525
|
+
return { queued: false };
|
|
13291
13526
|
},
|
|
13292
13527
|
removeQueuedPrompt(id, queueId) {
|
|
13293
13528
|
const rt = sessions.get(id);
|
|
@@ -13575,7 +13810,7 @@ ${message}`;
|
|
|
13575
13810
|
};
|
|
13576
13811
|
const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).filter((rt) => {
|
|
13577
13812
|
if (!lane) return true;
|
|
13578
|
-
if (rt.desc
|
|
13813
|
+
if (isShellOnlyRow(rt.desc)) return false;
|
|
13579
13814
|
const machine = hasMachineLineage(rt);
|
|
13580
13815
|
return lane === "auto" ? machine : !machine;
|
|
13581
13816
|
}).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
|
|
@@ -13588,7 +13823,11 @@ ${message}`;
|
|
|
13588
13823
|
stampWatchers(desc);
|
|
13589
13824
|
desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
|
|
13590
13825
|
desc.queuedPrompts = desc.promptQueue?.length ?? 0;
|
|
13591
|
-
|
|
13826
|
+
const summary = toSessionSummary(desc);
|
|
13827
|
+
if (!isShellOnlyRow(desc)) {
|
|
13828
|
+
summary.lane = hasMachineLineage(rt) ? "auto" : "agents";
|
|
13829
|
+
}
|
|
13830
|
+
return summary;
|
|
13592
13831
|
});
|
|
13593
13832
|
return { summaries, total: all.length };
|
|
13594
13833
|
},
|
|
@@ -17899,9 +18138,27 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17899
18138
|
try {
|
|
17900
18139
|
if (strategy.kind === "pty-native" || strategy.kind === "pty-plain") {
|
|
17901
18140
|
if (!ptyEnabled) return ptyNotConfigured("session_restart");
|
|
17902
|
-
|
|
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
|
+
}
|
|
17903
18154
|
const envVarName = strategy.kind === "pty-native" && prev.adapterSlug ? RESUME_STRATEGIES[prev.adapterSlug]?.configDirEnvVar : void 0;
|
|
17904
|
-
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;
|
|
17905
18162
|
if (strategy.kind === "pty-native") {
|
|
17906
18163
|
console.log(
|
|
17907
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`)
|
|
@@ -17983,6 +18240,15 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17983
18240
|
isError: true
|
|
17984
18241
|
};
|
|
17985
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
|
+
}
|
|
17986
18252
|
const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
|
|
17987
18253
|
...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
|
|
17988
18254
|
});
|
|
@@ -17995,7 +18261,8 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17995
18261
|
...restarted.desc,
|
|
17996
18262
|
resumedFrom: restarted.resumedFrom,
|
|
17997
18263
|
resumeVia: restarted.resumeVia,
|
|
17998
|
-
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
18264
|
+
...restarted.resumeFallback ? { resumeFallback: true } : {},
|
|
18265
|
+
...nativeResumeDecline ? { nativeResumeDecline } : {}
|
|
17999
18266
|
},
|
|
18000
18267
|
null,
|
|
18001
18268
|
2
|
|
@@ -20289,13 +20556,34 @@ function registerWebSearchTools(server, opts = {}) {
|
|
|
20289
20556
|
transformers: [catchErrors(), enrichResolverRejections()]
|
|
20290
20557
|
});
|
|
20291
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
|
+
}
|
|
20292
20576
|
|
|
20293
20577
|
// src/mcp-apps-adapter.ts
|
|
20294
20578
|
var MIME_TYPE = "text/html;profile=mcp-app";
|
|
20579
|
+
var EMBED_TOKEN_PLACEHOLDER = '"__AGENPROTO_EMBED_TOKEN__"';
|
|
20295
20580
|
function registerMcpApps(server, apps) {
|
|
20296
20581
|
for (const app of apps) {
|
|
20297
20582
|
const resourceUri = `ui://${app.id}/view`;
|
|
20298
|
-
|
|
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
|
+
}
|
|
20299
20587
|
const resourceMeta = {
|
|
20300
20588
|
ui: {
|
|
20301
20589
|
prefersBorder: true,
|
|
@@ -20366,7 +20654,11 @@ function makeBuiltinPanelApps(ops) {
|
|
|
20366
20654
|
// installed, install notice otherwise; see apps/src/session-chat).
|
|
20367
20655
|
makeSessionChatApp({
|
|
20368
20656
|
httpBaseUrl: ops.httpBaseUrl,
|
|
20369
|
-
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")
|
|
20370
20662
|
}),
|
|
20371
20663
|
// Work-board widget — kanban over the Task ledger (see apps/src/
|
|
20372
20664
|
// work-board). Read path only; writes go through task_claim/
|
|
@@ -23241,6 +23533,22 @@ async function sendOutbound(provider, input, deps2) {
|
|
|
23241
23533
|
return { ok: false, error: "unsupported_provider" };
|
|
23242
23534
|
}
|
|
23243
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
|
+
}
|
|
23244
23552
|
async function sendAgentpush(input, deps2) {
|
|
23245
23553
|
const alias = input.alias;
|
|
23246
23554
|
if (!alias) {
|
|
@@ -23270,7 +23578,7 @@ async function sendAgentpush(input, deps2) {
|
|
|
23270
23578
|
if (!upload.ok) {
|
|
23271
23579
|
return { ok: false, error: `upload_media_failed: ${upload.error}` };
|
|
23272
23580
|
}
|
|
23273
|
-
const result =
|
|
23581
|
+
const result = unwrapMcpToolResult(upload.result);
|
|
23274
23582
|
const providerMediaId = typeof result.media_id === "string" ? result.media_id : typeof result.id === "string" ? result.id : void 0;
|
|
23275
23583
|
if (!providerMediaId) {
|
|
23276
23584
|
return { ok: false, error: "upload_media_missing_media_id" };
|
|
@@ -23295,7 +23603,18 @@ async function sendAgentpush(input, deps2) {
|
|
|
23295
23603
|
if (!out.ok) {
|
|
23296
23604
|
return { ok: false, error: out.error };
|
|
23297
23605
|
}
|
|
23298
|
-
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);
|
|
23299
23618
|
return { ok: true, providerMessageId };
|
|
23300
23619
|
}
|
|
23301
23620
|
async function sendTelegram(input, deps2) {
|
|
@@ -23320,12 +23639,7 @@ async function sendTelegram(input, deps2) {
|
|
|
23320
23639
|
parse_mode: "MarkdownV2"
|
|
23321
23640
|
})
|
|
23322
23641
|
});
|
|
23323
|
-
|
|
23324
|
-
const body2 = await res2.text().catch(() => "");
|
|
23325
|
-
return { ok: false, error: `telegram_http_${res2.status}: ${body2}` };
|
|
23326
|
-
}
|
|
23327
|
-
const json2 = await res2.json().catch(() => ({}));
|
|
23328
|
-
return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
|
|
23642
|
+
return parseTelegramSendResponse(res2);
|
|
23329
23643
|
}
|
|
23330
23644
|
const files = [];
|
|
23331
23645
|
for (const att of input.attachments) {
|
|
@@ -23359,12 +23673,7 @@ async function sendTelegram(input, deps2) {
|
|
|
23359
23673
|
headers: { "Content-Type": contentType2 },
|
|
23360
23674
|
body: toFetchBody(body2)
|
|
23361
23675
|
});
|
|
23362
|
-
|
|
23363
|
-
const bodyText = await res2.text().catch(() => "");
|
|
23364
|
-
return { ok: false, error: `telegram_http_${res2.status}: ${bodyText}` };
|
|
23365
|
-
}
|
|
23366
|
-
const json2 = await res2.json().catch(() => ({}));
|
|
23367
|
-
return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
|
|
23676
|
+
return parseTelegramSendResponse(res2);
|
|
23368
23677
|
}
|
|
23369
23678
|
const mediaJson = input.attachments.map((att, index) => {
|
|
23370
23679
|
const item = {
|
|
@@ -23397,11 +23706,18 @@ async function sendTelegram(input, deps2) {
|
|
|
23397
23706
|
headers: { "Content-Type": contentType },
|
|
23398
23707
|
body: toFetchBody(body)
|
|
23399
23708
|
});
|
|
23709
|
+
return parseTelegramSendResponse(res);
|
|
23710
|
+
}
|
|
23711
|
+
async function parseTelegramSendResponse(res) {
|
|
23400
23712
|
if (!res.ok) {
|
|
23401
|
-
const
|
|
23402
|
-
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}` };
|
|
23403
23715
|
}
|
|
23404
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
|
+
}
|
|
23405
23721
|
return { ok: true, providerMessageId: extractTelegramMessageId(json) };
|
|
23406
23722
|
}
|
|
23407
23723
|
function extractProviderMessageId(result) {
|
|
@@ -26348,7 +26664,16 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
26348
26664
|
if (!out.ok) {
|
|
26349
26665
|
return {
|
|
26350
26666
|
content: [
|
|
26351
|
-
{
|
|
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
|
+
}
|
|
26352
26677
|
],
|
|
26353
26678
|
isError: true
|
|
26354
26679
|
};
|
|
@@ -26364,7 +26689,18 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
26364
26689
|
provider
|
|
26365
26690
|
});
|
|
26366
26691
|
}
|
|
26367
|
-
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
|
+
};
|
|
26368
26704
|
}
|
|
26369
26705
|
);
|
|
26370
26706
|
}
|
|
@@ -26798,7 +27134,7 @@ async function startHttpServer(opts) {
|
|
|
26798
27134
|
}
|
|
26799
27135
|
function authorizeMcp(req, res) {
|
|
26800
27136
|
const origin = req.headers.origin;
|
|
26801
|
-
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
|
|
27137
|
+
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
|
|
26802
27138
|
const auth = readAuth();
|
|
26803
27139
|
const header = req.headers.authorization;
|
|
26804
27140
|
if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
|
|
@@ -26828,8 +27164,12 @@ async function startHttpServer(opts) {
|
|
|
26828
27164
|
}
|
|
26829
27165
|
const origin = req.headers.origin;
|
|
26830
27166
|
if (typeof origin === "string" && originAllowed(origin)) return "ok";
|
|
27167
|
+
if (embedTokenTrusted(req)) return "ok";
|
|
26831
27168
|
return header ? "bad" : "missing";
|
|
26832
27169
|
}
|
|
27170
|
+
function embedTokenTrusted(req) {
|
|
27171
|
+
return isValidAppEmbedToken(requestEmbedToken(req));
|
|
27172
|
+
}
|
|
26833
27173
|
function originAllowed(origin) {
|
|
26834
27174
|
const list = opts.strictOrigins ? opts.allowedOrigins ?? [] : [...DEFAULT_ALLOWED_ORIGINS, ...opts.allowedOrigins ?? []];
|
|
26835
27175
|
for (const pattern of list) {
|
|
@@ -27115,7 +27455,7 @@ async function startHttpServer(opts) {
|
|
|
27115
27455
|
}
|
|
27116
27456
|
function guardBrowserOrigin(req, res) {
|
|
27117
27457
|
const origin = req.headers.origin;
|
|
27118
|
-
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
|
|
27458
|
+
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
|
|
27119
27459
|
const auth = readAuth();
|
|
27120
27460
|
const header = req.headers.authorization;
|
|
27121
27461
|
if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
|
|
@@ -27134,10 +27474,14 @@ async function startHttpServer(opts) {
|
|
|
27134
27474
|
}
|
|
27135
27475
|
function applyCors(req, res) {
|
|
27136
27476
|
const origin = req.headers.origin;
|
|
27137
|
-
const
|
|
27138
|
-
|
|
27477
|
+
const tokenTrusted = embedTokenTrusted(req);
|
|
27478
|
+
const originTrusted = typeof origin === "string" && origin.length > 0 && originAllowed(origin);
|
|
27479
|
+
const trusted = originTrusted || tokenTrusted;
|
|
27480
|
+
if (originTrusted) {
|
|
27139
27481
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
27140
27482
|
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
27483
|
+
} else if (tokenTrusted && origin && origin !== "null") {
|
|
27484
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
27141
27485
|
} else {
|
|
27142
27486
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
27143
27487
|
}
|
|
@@ -28105,7 +28449,9 @@ async function startHttpServer(opts) {
|
|
|
28105
28449
|
const uiMatch = path.match(/^\/apps\/(.+)\/ui$/);
|
|
28106
28450
|
if (uiMatch && req.method === "GET") {
|
|
28107
28451
|
const uiApp = opts.appRegistry.getApp(decodeURIComponent(uiMatch[1]));
|
|
28108
|
-
if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && guardBrowserOrigin(req, res))
|
|
28452
|
+
if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && !isValidAppEmbedToken(requestEmbedToken(req)) && guardBrowserOrigin(req, res)) {
|
|
28453
|
+
return;
|
|
28454
|
+
}
|
|
28109
28455
|
if (!authorize(req, res)) return;
|
|
28110
28456
|
await handleAppUiPage(
|
|
28111
28457
|
req,
|
|
@@ -30853,6 +31199,16 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
30853
31199
|
if (frameAncestors.length === 0) {
|
|
30854
31200
|
headers["x-frame-options"] = "SAMEORIGIN";
|
|
30855
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
|
+
}
|
|
30856
31212
|
}
|
|
30857
31213
|
res.writeHead(200, headers);
|
|
30858
31214
|
res.end(injectStandaloneAppBridge(raw));
|
|
@@ -30860,6 +31216,7 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
30860
31216
|
function iframeEmbedAllowed(req, app) {
|
|
30861
31217
|
const secFetchDest = req.headers["sec-fetch-dest"];
|
|
30862
31218
|
if (secFetchDest !== void 0 && secFetchDest !== "iframe") return false;
|
|
31219
|
+
if (isValidAppEmbedToken(requestEmbedToken(req))) return true;
|
|
30863
31220
|
const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
|
|
30864
31221
|
const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
|
|
30865
31222
|
if (!origin && !referer) return false;
|
|
@@ -30880,6 +31237,13 @@ function iframeEmbedAllowed(req, app) {
|
|
|
30880
31237
|
}
|
|
30881
31238
|
return false;
|
|
30882
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
|
+
}
|
|
30883
31247
|
function iframeEmbedOriginAllowed(req, app) {
|
|
30884
31248
|
const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
|
|
30885
31249
|
const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
|
|
@@ -34838,6 +35202,7 @@ function createOrchestratorMcpServerFactory(deps2) {
|
|
|
34838
35202
|
...deps2.listHarnessCapabilities ? { listHarnessCapabilities: deps2.listHarnessCapabilities } : {},
|
|
34839
35203
|
...deps2.webhookNotifier ? { webhookNotifier: deps2.webhookNotifier } : {},
|
|
34840
35204
|
...deps2.resolveSandboxProvider ? { resolveSandboxProvider: deps2.resolveSandboxProvider } : {},
|
|
35205
|
+
...deps2.defaultAgentPromptInterrupt != null ? { defaultAgentPromptInterrupt: deps2.defaultAgentPromptInterrupt } : {},
|
|
34841
35206
|
daemonMcpUrl: deps2.daemonMcpUrl
|
|
34842
35207
|
});
|
|
34843
35208
|
registerOrchestrationTools(server, {
|
|
@@ -38278,6 +38643,10 @@ async function createGateway(opts) {
|
|
|
38278
38643
|
// can declare `sandbox` and resolve a provider, instead of throwing
|
|
38279
38644
|
// `sandbox_provider_not_found`.
|
|
38280
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 } : {},
|
|
38281
38650
|
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
38282
38651
|
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|
|
38283
38652
|
...opts.listHarnessCapabilities ? { listHarnessCapabilities: opts.listHarnessCapabilities } : {}
|
|
@@ -38338,6 +38707,9 @@ async function createGateway(opts) {
|
|
|
38338
38707
|
webhookNotifier,
|
|
38339
38708
|
daemonMcpUrl,
|
|
38340
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 } : {},
|
|
38341
38713
|
...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
|
|
38342
38714
|
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
38343
38715
|
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|