agent.libx.js 0.93.17 → 0.93.19
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/cli/cli.ts +19 -2
- package/dist/cli.js +210 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +97 -12
- package/dist/index.js.map +1 -1
- package/dist/{mcp-DGWuuWJm.d.ts → mcp-C5GuDinb.d.ts} +35 -6
- package/dist/mcp.client.d.ts +70 -7
- package/dist/mcp.client.js +212 -31
- package/dist/mcp.client.js.map +1 -1
- package/package.json +1 -1
package/cli/cli.ts
CHANGED
|
@@ -1035,6 +1035,9 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
1035
1035
|
let dx: DuplexAgent | undefined;
|
|
1036
1036
|
let voiceIO: VoiceIO | undefined; // real voice I/O (--voice + keys): mic→STT in, text_delta→TTS out
|
|
1037
1037
|
let editorRef: LineEditor | undefined; // bound once the line editor exists — async chrome repaints the prompt via it
|
|
1038
|
+
// During a turn the user's type-ahead lives on a "stash ›" line (no active editor to own it). Async
|
|
1039
|
+
// chrome (streamed deltas, task events) lands on top of it — repaint the stash below, so it survives.
|
|
1040
|
+
let repaintStash: () => void = () => {};
|
|
1038
1041
|
let workerOptions: AgentOptions | undefined;
|
|
1039
1042
|
// Worker UI verbosity: 'full' = ⚙ tool chrome per worker step; 'minimal' = task events only
|
|
1040
1043
|
// (started/progress/⦿ done). Voice defaults minimal (chrome is noise next to speech); /workers toggles live.
|
|
@@ -1110,6 +1113,13 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
1110
1113
|
if (e.kind === 'text_delta' && voiceIO) {
|
|
1111
1114
|
voiceIO.speakDelta(e.message);
|
|
1112
1115
|
editorRef?.suspend(); // no-op when already suspended
|
|
1116
|
+
} else if (e.kind === 'text_delta' && stashBuf) {
|
|
1117
|
+
// TEXT mode with type-ahead pending: lift the sacred stash line, let the markdown stream
|
|
1118
|
+
// land, then repaint the stash below it (else the streamed ack overwrites what's being typed).
|
|
1119
|
+
process.stdout.write('\r\x1b[K');
|
|
1120
|
+
base.notify!(e);
|
|
1121
|
+
repaintStash();
|
|
1122
|
+
return;
|
|
1113
1123
|
}
|
|
1114
1124
|
if (e.kind === 'hold_filler' && voiceIO) {
|
|
1115
1125
|
voiceIO.speakFiller(e.message);
|
|
@@ -1135,6 +1145,7 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
1135
1145
|
if (lines.length > shown.length) err(dim(` … (+${lines.length - shown.length} more lines)\n`));
|
|
1136
1146
|
duplexAccount(e.data); // worker tokens/cost are real spend — fold into /cost
|
|
1137
1147
|
editorRef?.redrawNow();
|
|
1148
|
+
repaintStash(); // type-ahead survives the async ⦿ landing
|
|
1138
1149
|
return;
|
|
1139
1150
|
}
|
|
1140
1151
|
// Remaining task_* events (started/progress/cancelled/error) land ASYNC at a live prompt —
|
|
@@ -1145,6 +1156,7 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
1145
1156
|
// asks are decisions, not chatter — make them stand out (the voice also speaks them)
|
|
1146
1157
|
err('\r\x1b[0J' + (e.kind === 'task_ask' ? yellow(` ? ${e.message} — answer by voice or type yes/no\n`) : dim(` · ${e.message}\n`)));
|
|
1147
1158
|
editorRef?.redrawNow();
|
|
1159
|
+
repaintStash(); // type-ahead survives the async task event landing
|
|
1148
1160
|
return;
|
|
1149
1161
|
}
|
|
1150
1162
|
base.notify!(e); // makeHost always provides notify (the HostBridge type just marks it optional)
|
|
@@ -1921,6 +1933,8 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
1921
1933
|
const q = inputStash.length ? dim(` [${inputStash.length} queued]`) : '';
|
|
1922
1934
|
err(`\r\x1b[K${dim(' stash › ')}${stashBuf}${q}`);
|
|
1923
1935
|
};
|
|
1936
|
+
repaintStash = renderStashBuf; // async chrome repaints the type-ahead line through this
|
|
1937
|
+
|
|
1924
1938
|
process.stdin.on('keypress', (_s, key) => {
|
|
1925
1939
|
if (!activeTurn) return;
|
|
1926
1940
|
if (key?.ctrl && key?.name === 'o') { toggleVerbose(); return; }
|
|
@@ -2064,9 +2078,12 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
2064
2078
|
while (true) {
|
|
2065
2079
|
// Double-Esc fired during the just-finished turn → open the jump-back picker now (turn has unwound).
|
|
2066
2080
|
if (pendingRewind) { pendingRewind = false; const t = await rewindToMessage(); if (t !== undefined) prefill = t; }
|
|
2067
|
-
aborting = false;
|
|
2081
|
+
aborting = false;
|
|
2082
|
+
const carry = stashBuf; stashBuf = ''; // type-ahead typed (not Enter'd) during the turn → carry it forward
|
|
2068
2083
|
err('\n'); // blank line before the prompt (the editor renders on one line)
|
|
2069
|
-
|
|
2084
|
+
// Consume the pending jump-back text (once); else fall back to un-submitted type-ahead so a line
|
|
2085
|
+
// typed while the turn ran isn't lost — it lands editable in the fresh prompt (CC parity).
|
|
2086
|
+
const initial = prefill ?? (carry || undefined); prefill = undefined;
|
|
2070
2087
|
// Dim status footer under the prompt (context% · cost). Constant while typing one line (transcript is
|
|
2071
2088
|
// fixed until submit), so compute once per iteration. Hidden on a fresh REPL (no usage yet) to stay clean.
|
|
2072
2089
|
const ctxTok = estimateTranscriptTokens(face.transcript); // expensive: compute once per iteration (not per keystroke)
|
package/dist/cli.js
CHANGED
|
@@ -3575,6 +3575,17 @@ var DuplexAgent = class {
|
|
|
3575
3575
|
seq = 0;
|
|
3576
3576
|
pendingEvents = [];
|
|
3577
3577
|
flushQueued = false;
|
|
3578
|
+
/** Per-voice-turn guards (reset by resetTurn at each turn's start). The reflex is a weak model:
|
|
3579
|
+
* left unguarded it polls TaskStatus after a dispatch and/or dispatches silently (dead air).
|
|
3580
|
+
* Like CC's Task tool, a dispatch is "said my piece, now wait for the push" — these enforce that. */
|
|
3581
|
+
turnDispatched = false;
|
|
3582
|
+
// an Act/Think fired this turn
|
|
3583
|
+
turnBriefs = /* @__PURE__ */ new Set();
|
|
3584
|
+
// briefs dispatched this turn (detect identical re-dispatch)
|
|
3585
|
+
spokeThisTurn = false;
|
|
3586
|
+
// any non-empty text_delta streamed this turn
|
|
3587
|
+
nudging = false;
|
|
3588
|
+
// re-ack pass in flight: block ALL tools, prevent recursion
|
|
3578
3589
|
/** Parked worker questions awaiting a (voice-relayed) user answer, keyed by ask id. */
|
|
3579
3590
|
pendingAsks = /* @__PURE__ */ new Map();
|
|
3580
3591
|
/** Lazily resolved memory tools (async loadMemory runs in initMemory). */
|
|
@@ -3599,12 +3610,21 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
3599
3610
|
this.answerTaskTool(),
|
|
3600
3611
|
this.holdTool()
|
|
3601
3612
|
];
|
|
3613
|
+
const host = o.host;
|
|
3614
|
+
const voiceHost = host && {
|
|
3615
|
+
ask: host.ask ? (q2) => host.ask(q2) : void 0,
|
|
3616
|
+
confirm: host.confirm ? (p, m) => host.confirm(p, m) : void 0,
|
|
3617
|
+
notify: (ev) => {
|
|
3618
|
+
if (ev?.kind === "text_delta" && typeof ev.message === "string" && ev.message.trim()) this.spokeThisTurn = true;
|
|
3619
|
+
host.notify?.(ev);
|
|
3620
|
+
}
|
|
3621
|
+
};
|
|
3602
3622
|
this.voice = new Agent({
|
|
3603
3623
|
ai: o.ai,
|
|
3604
3624
|
fs: new MemFilesystem2(),
|
|
3605
3625
|
model: o.reflexModel,
|
|
3606
3626
|
stream: true,
|
|
3607
|
-
host:
|
|
3627
|
+
host: voiceHost,
|
|
3608
3628
|
// The reflex IS the conversational channel — it confirms ambiguity inline ("did you mean…?"),
|
|
3609
3629
|
// never via the blocking AskUserQuestion tool (Agent auto-adds it whenever a host is set). Left in,
|
|
3610
3630
|
// it stalls a voice turn until the kill-switch. Worker questions still reach the user via parkQuestion.
|
|
@@ -3614,7 +3634,9 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
3614
3634
|
maxSteps: 8,
|
|
3615
3635
|
timeoutMs: 3e4,
|
|
3616
3636
|
...o.reflexOptions,
|
|
3617
|
-
tools
|
|
3637
|
+
tools,
|
|
3638
|
+
// Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
|
|
3639
|
+
hooks: composeHooks(this.dispatchGuard(), o.reflexOptions?.hooks)
|
|
3618
3640
|
});
|
|
3619
3641
|
}
|
|
3620
3642
|
/** Resolve memory tools + inject index into voice system prompt (once). */
|
|
@@ -3625,11 +3647,54 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
3625
3647
|
this.voice.options.tools.push(...mem.tools);
|
|
3626
3648
|
if (mem.index) this.voice.options.systemPrompt += "\n\n" + mem.index;
|
|
3627
3649
|
}
|
|
3650
|
+
/** Clear the per-turn guards. Called at the head of every voice turn (user send + re-voice flush). */
|
|
3651
|
+
resetTurn() {
|
|
3652
|
+
this.turnDispatched = false;
|
|
3653
|
+
this.turnBriefs.clear();
|
|
3654
|
+
this.spokeThisTurn = false;
|
|
3655
|
+
}
|
|
3656
|
+
/** preToolUse guard on the reflex: once it has dispatched this turn, a dispatch is "said my piece,
|
|
3657
|
+
* now wait for the push" (CC's Task model). Block the temptations — TaskStatus polling and identical
|
|
3658
|
+
* re-dispatch — so the only remaining move is to voice a short ack and end. A genuinely NEW Act is
|
|
3659
|
+
* still allowed (parallel independent work). During a re-ack pass, block every tool. */
|
|
3660
|
+
dispatchGuard() {
|
|
3661
|
+
return {
|
|
3662
|
+
preToolUse: (call) => {
|
|
3663
|
+
if (this.nudging) return { block: true, reason: "Just say one short spoken acknowledgement \u2014 no tools this turn." };
|
|
3664
|
+
if (!this.turnDispatched) return;
|
|
3665
|
+
if (call.name === "TaskStatus")
|
|
3666
|
+
return { block: true, reason: "You just dispatched a task this turn \u2014 do NOT poll. Give one short spoken acknowledgement and end your turn; the result arrives later as a [task \u2026] event." };
|
|
3667
|
+
if ((call.name === "Act" || call.name === "Think") && this.turnBriefs.has(String(call.args?.brief ?? "")))
|
|
3668
|
+
return { block: true, reason: "You already dispatched this exact task \u2014 acknowledge briefly and end your turn." };
|
|
3669
|
+
}
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
3672
|
+
/** True when the just-finished turn dispatched a task but voiced nothing — dead air to repair.
|
|
3673
|
+
* Requires a host: without one there's no stream to detect speech on (and no one to speak to). */
|
|
3674
|
+
get silentDispatch() {
|
|
3675
|
+
return !!this.options.host && this.turnDispatched && !this.spokeThisTurn;
|
|
3676
|
+
}
|
|
3677
|
+
/** A dispatch with no spoken text is dead air. Re-prompt the reflex ONCE so the LLM itself voices a
|
|
3678
|
+
* short ack (no template). If it STILL says nothing, fall back to a minimal line so silence never ships. */
|
|
3679
|
+
async ackIfSilent() {
|
|
3680
|
+
this.nudging = true;
|
|
3681
|
+
try {
|
|
3682
|
+
await this.voice.send("[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools.");
|
|
3683
|
+
} catch (e) {
|
|
3684
|
+
log6.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
3685
|
+
} finally {
|
|
3686
|
+
this.nudging = false;
|
|
3687
|
+
}
|
|
3688
|
+
if (!this.spokeThisTurn) this.options.host?.notify?.({ kind: "text_delta", message: "Okay, on it." });
|
|
3689
|
+
}
|
|
3628
3690
|
/** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns. */
|
|
3629
3691
|
send(content) {
|
|
3630
3692
|
return this.enqueue(async () => {
|
|
3631
3693
|
await this.initMemory();
|
|
3632
|
-
|
|
3694
|
+
this.resetTurn();
|
|
3695
|
+
const res = await this.voice.send(content);
|
|
3696
|
+
if (this.silentDispatch) await this.ackIfSilent();
|
|
3697
|
+
return res;
|
|
3633
3698
|
});
|
|
3634
3699
|
}
|
|
3635
3700
|
/** Resolve when all queued voice turns AND all in-flight worker tasks have settled (tests, graceful shutdown). */
|
|
@@ -3662,7 +3727,9 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
3662
3727
|
this.flushQueued = false;
|
|
3663
3728
|
const events = this.pendingEvents.splice(0);
|
|
3664
3729
|
if (!events.length) return;
|
|
3730
|
+
this.resetTurn();
|
|
3665
3731
|
await this.voice.send(events.join("\n"));
|
|
3732
|
+
if (this.silentDispatch) await this.ackIfSilent();
|
|
3666
3733
|
this.notify("revoice_done", "");
|
|
3667
3734
|
});
|
|
3668
3735
|
}
|
|
@@ -3895,6 +3962,8 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
3895
3962
|
}
|
|
3896
3963
|
},
|
|
3897
3964
|
run: async ({ brief, label }) => {
|
|
3965
|
+
this.turnDispatched = true;
|
|
3966
|
+
this.turnBriefs.add(String(brief ?? ""));
|
|
3898
3967
|
const id = await this.dispatch(String(brief ?? ""), "act", label ? String(label) : void 0);
|
|
3899
3968
|
return `Acting on task ${id}. Acknowledge briefly; the result will arrive as a [task ${id} completed] event.`;
|
|
3900
3969
|
}
|
|
@@ -3913,6 +3982,8 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
3913
3982
|
}
|
|
3914
3983
|
},
|
|
3915
3984
|
run: async ({ brief, label }) => {
|
|
3985
|
+
this.turnDispatched = true;
|
|
3986
|
+
this.turnBriefs.add(String(brief ?? ""));
|
|
3916
3987
|
const id = await this.dispatch(String(brief ?? ""), "think", label ? String(label) : void 0);
|
|
3917
3988
|
return `Thinking on task ${id}. Acknowledge briefly; the result will arrive as a [task ${id} completed] event.`;
|
|
3918
3989
|
}
|
|
@@ -4781,6 +4852,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
|
|
|
4781
4852
|
// src/mcp.client.ts
|
|
4782
4853
|
init_logging();
|
|
4783
4854
|
import { spawn } from "child_process";
|
|
4855
|
+
import { createHash } from "crypto";
|
|
4784
4856
|
var log10 = forComponent("mcp");
|
|
4785
4857
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
4786
4858
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -4973,37 +5045,136 @@ var McpClient = class {
|
|
|
4973
5045
|
await this.transport.close();
|
|
4974
5046
|
}
|
|
4975
5047
|
};
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5048
|
+
function buildTransport(cfg) {
|
|
5049
|
+
return cfg.url ? new HttpTransport({ url: cfg.url, headers: cfg.headers, bearerToken: cfg.bearerToken, timeoutMs: cfg.timeoutMs }) : new StdioTransport({ command: cfg.command, args: cfg.args, env: cfg.env, cwd: cfg.cwd, timeoutMs: cfg.timeoutMs });
|
|
5050
|
+
}
|
|
5051
|
+
function withTimeout(p, ms, label) {
|
|
5052
|
+
if (!ms || ms <= 0) return p;
|
|
5053
|
+
return new Promise((resolve4, reject) => {
|
|
5054
|
+
const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
|
|
5055
|
+
timer.unref?.();
|
|
5056
|
+
p.then((v) => {
|
|
5057
|
+
clearTimeout(timer);
|
|
5058
|
+
resolve4(v);
|
|
5059
|
+
}, (e) => {
|
|
5060
|
+
clearTimeout(timer);
|
|
5061
|
+
reject(e);
|
|
5062
|
+
});
|
|
5063
|
+
});
|
|
5064
|
+
}
|
|
5065
|
+
async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
5066
|
+
const client = new McpClient(buildTransport(cfg));
|
|
5067
|
+
try {
|
|
5068
|
+
return await withTimeout((async () => {
|
|
5069
|
+
const init = await client.connect();
|
|
5070
|
+
const specs = await client.listTools();
|
|
5071
|
+
const tools = mcpToolsToAgentTools(specs, (tool, a) => client.callTool(tool, a), `mcp__${name}__`);
|
|
5072
|
+
return { name, client, tools, specs, serverInfo: init?.serverInfo };
|
|
5073
|
+
})(), mountTimeoutMs, name);
|
|
5074
|
+
} catch (e) {
|
|
5075
|
+
await client.close().catch((err2) => log10.debug(`close after failed mount of "${name}": ${err2}`));
|
|
5076
|
+
throw e;
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
function mountMcpServer(name, cfg) {
|
|
5080
|
+
return mountWithDeadline(name, cfg);
|
|
5081
|
+
}
|
|
5082
|
+
function validEntries(servers) {
|
|
5083
|
+
return Object.entries(servers).filter(([name, cfg]) => {
|
|
5084
|
+
if (!cfg || cfg.disabled) return false;
|
|
4988
5085
|
if (!cfg.command && !cfg.url) {
|
|
4989
5086
|
log10.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
4990
|
-
|
|
4991
|
-
}
|
|
4992
|
-
try {
|
|
4993
|
-
const m = await mountMcpServer(name, cfg);
|
|
4994
|
-
out.push(m);
|
|
4995
|
-
log10.info(`MCP "${name}" mounted \u2014 ${m.tools.length} tool(s)${m.serverInfo?.name ? ` from ${m.serverInfo.name}` : ""}`);
|
|
4996
|
-
} catch (e) {
|
|
4997
|
-
if (e instanceof McpAuthError) log10.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
4998
|
-
else log10.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
5087
|
+
return false;
|
|
4999
5088
|
}
|
|
5000
|
-
|
|
5089
|
+
return true;
|
|
5090
|
+
});
|
|
5091
|
+
}
|
|
5092
|
+
function logMountFailure(name, e) {
|
|
5093
|
+
if (e instanceof McpAuthError) log10.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
5094
|
+
else log10.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
5095
|
+
}
|
|
5096
|
+
async function mountMcpServers(servers = {}, opts = {}) {
|
|
5097
|
+
const entries = validEntries(servers);
|
|
5098
|
+
const settled = await Promise.allSettled(entries.map(([name, cfg]) => mountWithDeadline(name, cfg, opts.mountTimeoutMs)));
|
|
5099
|
+
const out = [];
|
|
5100
|
+
settled.forEach((r, i) => {
|
|
5101
|
+
const name = entries[i][0];
|
|
5102
|
+
if (r.status === "fulfilled") {
|
|
5103
|
+
out.push(r.value);
|
|
5104
|
+
log10.info(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
5105
|
+
} else logMountFailure(name, r.reason);
|
|
5106
|
+
});
|
|
5001
5107
|
return out;
|
|
5002
5108
|
}
|
|
5109
|
+
var MemMcpCatalog = class {
|
|
5110
|
+
constructor(ttlMs = 5 * 6e4) {
|
|
5111
|
+
this.ttlMs = ttlMs;
|
|
5112
|
+
}
|
|
5113
|
+
ttlMs;
|
|
5114
|
+
m = /* @__PURE__ */ new Map();
|
|
5115
|
+
get(key) {
|
|
5116
|
+
const e = this.m.get(key);
|
|
5117
|
+
if (!e) return null;
|
|
5118
|
+
if (Date.now() > e.exp) {
|
|
5119
|
+
this.m.delete(key);
|
|
5120
|
+
return null;
|
|
5121
|
+
}
|
|
5122
|
+
return e.specs;
|
|
5123
|
+
}
|
|
5124
|
+
set(key, specs) {
|
|
5125
|
+
this.m.set(key, { specs, exp: Date.now() + this.ttlMs });
|
|
5126
|
+
}
|
|
5127
|
+
};
|
|
5128
|
+
var McpPool = class {
|
|
5129
|
+
constructor(ttlMs = 5 * 6e4) {
|
|
5130
|
+
this.ttlMs = ttlMs;
|
|
5131
|
+
}
|
|
5132
|
+
ttlMs;
|
|
5133
|
+
warm = /* @__PURE__ */ new Map();
|
|
5134
|
+
get(key) {
|
|
5135
|
+
const e = this.warm.get(key);
|
|
5136
|
+
if (!e) return null;
|
|
5137
|
+
this.arm(key, e);
|
|
5138
|
+
return e.client;
|
|
5139
|
+
}
|
|
5140
|
+
put(key, client) {
|
|
5141
|
+
const prev = this.warm.get(key);
|
|
5142
|
+
if (prev) {
|
|
5143
|
+
clearTimeout(prev.timer);
|
|
5144
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log10.debug(`warm-pool replace close failed: ${err2}`));
|
|
5145
|
+
}
|
|
5146
|
+
const e = { client, timer: void 0 };
|
|
5147
|
+
this.warm.set(key, e);
|
|
5148
|
+
this.arm(key, e);
|
|
5149
|
+
}
|
|
5150
|
+
arm(key, e) {
|
|
5151
|
+
clearTimeout(e.timer);
|
|
5152
|
+
e.timer = setTimeout(() => {
|
|
5153
|
+
void this.evict(key);
|
|
5154
|
+
}, this.ttlMs);
|
|
5155
|
+
e.timer.unref?.();
|
|
5156
|
+
}
|
|
5157
|
+
async evict(key) {
|
|
5158
|
+
const e = this.warm.get(key);
|
|
5159
|
+
if (!e) return;
|
|
5160
|
+
this.warm.delete(key);
|
|
5161
|
+
await e.client.close().catch((err2) => log10.debug(`warm-pool evict close failed: ${err2}`));
|
|
5162
|
+
}
|
|
5163
|
+
async closeAll() {
|
|
5164
|
+
for (const e of this.warm.values()) {
|
|
5165
|
+
clearTimeout(e.timer);
|
|
5166
|
+
await e.client.close().catch(() => {
|
|
5167
|
+
});
|
|
5168
|
+
}
|
|
5169
|
+
this.warm.clear();
|
|
5170
|
+
}
|
|
5171
|
+
};
|
|
5172
|
+
var defaultCatalog = new MemMcpCatalog();
|
|
5173
|
+
var defaultPool = new McpPool();
|
|
5003
5174
|
|
|
5004
5175
|
// cli/mcpOAuth.ts
|
|
5005
5176
|
import { createServer } from "http";
|
|
5006
|
-
import { randomBytes, createHash } from "crypto";
|
|
5177
|
+
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
5007
5178
|
import { readFileSync, writeFileSync as writeFileSync2, mkdirSync, existsSync } from "fs";
|
|
5008
5179
|
import { dirname as dirname2 } from "path";
|
|
5009
5180
|
var McpOAuthOptions = class {
|
|
@@ -5047,7 +5218,7 @@ var McpOAuth = class {
|
|
|
5047
5218
|
const meta = await this.discover(serverUrl);
|
|
5048
5219
|
const clientId = this.options.clientId ?? await this.registerClient(meta, serverUrl);
|
|
5049
5220
|
const verifier = b64url(randomBytes(32));
|
|
5050
|
-
const challenge = b64url(
|
|
5221
|
+
const challenge = b64url(createHash2("sha256").update(verifier).digest());
|
|
5051
5222
|
const state = b64url(randomBytes(16));
|
|
5052
5223
|
const { code, redirectUri } = await this.captureCode(meta.authorization_endpoint, clientId, challenge, state);
|
|
5053
5224
|
const tok = await this.exchange(meta.token_endpoint, {
|
|
@@ -8429,6 +8600,8 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
8429
8600
|
let dx;
|
|
8430
8601
|
let voiceIO;
|
|
8431
8602
|
let editorRef;
|
|
8603
|
+
let repaintStash = () => {
|
|
8604
|
+
};
|
|
8432
8605
|
let workerOptions;
|
|
8433
8606
|
let workerChrome = "full";
|
|
8434
8607
|
let duplexPersist = () => {
|
|
@@ -8484,6 +8657,11 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
8484
8657
|
if (e.kind === "text_delta" && voiceIO) {
|
|
8485
8658
|
voiceIO.speakDelta(e.message);
|
|
8486
8659
|
editorRef?.suspend();
|
|
8660
|
+
} else if (e.kind === "text_delta" && stashBuf) {
|
|
8661
|
+
process.stdout.write("\r\x1B[K");
|
|
8662
|
+
base.notify(e);
|
|
8663
|
+
repaintStash();
|
|
8664
|
+
return;
|
|
8487
8665
|
}
|
|
8488
8666
|
if (e.kind === "hold_filler" && voiceIO) {
|
|
8489
8667
|
voiceIO.speakFiller(e.message);
|
|
@@ -8508,6 +8686,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
8508
8686
|
`));
|
|
8509
8687
|
duplexAccount(e.data);
|
|
8510
8688
|
editorRef?.redrawNow();
|
|
8689
|
+
repaintStash();
|
|
8511
8690
|
return;
|
|
8512
8691
|
}
|
|
8513
8692
|
if (typeof e.kind === "string" && e.kind.startsWith("task_")) {
|
|
@@ -8516,6 +8695,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
8516
8695
|
`) : dim(` \xB7 ${e.message}
|
|
8517
8696
|
`)));
|
|
8518
8697
|
editorRef?.redrawNow();
|
|
8698
|
+
repaintStash();
|
|
8519
8699
|
return;
|
|
8520
8700
|
}
|
|
8521
8701
|
base.notify(e);
|
|
@@ -9510,6 +9690,7 @@ ${extra}` : body);
|
|
|
9510
9690
|
const q2 = inputStash.length ? dim(` [${inputStash.length} queued]`) : "";
|
|
9511
9691
|
err(`\r\x1B[K${dim(" stash \u203A ")}${stashBuf}${q2}`);
|
|
9512
9692
|
};
|
|
9693
|
+
repaintStash = renderStashBuf;
|
|
9513
9694
|
process.stdin.on("keypress", (_s, key) => {
|
|
9514
9695
|
if (!activeTurn) return;
|
|
9515
9696
|
if (key?.ctrl && key?.name === "o") {
|
|
@@ -9699,9 +9880,10 @@ ${extra}` : body);
|
|
|
9699
9880
|
if (t !== void 0) prefill = t;
|
|
9700
9881
|
}
|
|
9701
9882
|
aborting = false;
|
|
9883
|
+
const carry = stashBuf;
|
|
9702
9884
|
stashBuf = "";
|
|
9703
9885
|
err("\n");
|
|
9704
|
-
const initial = prefill;
|
|
9886
|
+
const initial = prefill ?? (carry || void 0);
|
|
9705
9887
|
prefill = void 0;
|
|
9706
9888
|
const ctxTok = estimateTranscriptTokens(face.transcript);
|
|
9707
9889
|
const ctxCap = face.options.maxTokens || 2e5;
|