@dudko.dev/agent-web 0.0.1 → 0.0.2
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/README.md +35 -1
- package/dist/agent-web.js +177 -0
- package/dist/agent-web.js.map +1 -0
- package/dist/index.cjs +186 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -4
- package/dist/index.d.ts +31 -4
- package/dist/index.js +186 -95
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +71 -44
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts +5 -1
- package/dist/mcp.d.ts +5 -1
- package/dist/mcp.js +71 -44
- package/dist/mcp.js.map +1 -1
- package/package.json +7 -1
package/dist/index.d.cts
CHANGED
|
@@ -105,6 +105,11 @@ interface PlannerPromptContext {
|
|
|
105
105
|
state?: string;
|
|
106
106
|
toolCatalog: string;
|
|
107
107
|
mode: ToolCallMode;
|
|
108
|
+
/** Prior session messages (oldest first), for resolving references to earlier turns. */
|
|
109
|
+
history?: {
|
|
110
|
+
role: string;
|
|
111
|
+
content: string;
|
|
112
|
+
}[];
|
|
108
113
|
}
|
|
109
114
|
interface ExecutorPromptContext {
|
|
110
115
|
goal: string;
|
|
@@ -242,7 +247,9 @@ interface BrowserAgentConfig {
|
|
|
242
247
|
clientName?: string;
|
|
243
248
|
/** Host tools the executor may call (an AI SDK ToolSet; use defineTool). */
|
|
244
249
|
tools?: AgentToolSet;
|
|
250
|
+
/** Whitelist: only these tool names from `tools` are mounted (default: all). */
|
|
245
251
|
availableTools?: string[];
|
|
252
|
+
/** Blacklist: these tool names are removed after the whitelist is applied. */
|
|
246
253
|
excludedTools?: string[];
|
|
247
254
|
/** Force native/prompted tool-calling; 'auto' picks per model (default 'auto'). */
|
|
248
255
|
toolMode?: ToolMode;
|
|
@@ -253,7 +260,11 @@ interface BrowserAgentConfig {
|
|
|
253
260
|
prompts?: Partial<Prompts>;
|
|
254
261
|
/** Serialize the host's current world state into prompt context (grounding). */
|
|
255
262
|
describeState?: () => string | Promise<string>;
|
|
256
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* Persist the transcript (e.g. new IndexedDBStore()). The last few messages
|
|
265
|
+
* are also read back into the planner prompt, so follow-up goals can refer
|
|
266
|
+
* to earlier turns.
|
|
267
|
+
*/
|
|
257
268
|
memory?: ContextStore;
|
|
258
269
|
sessionId?: string;
|
|
259
270
|
/** Hard cap on executed steps incl. replans (default 8). */
|
|
@@ -486,6 +497,8 @@ interface AgentContext {
|
|
|
486
497
|
signal?: AbortSignal;
|
|
487
498
|
/** Current world state for grounding, or undefined when no describeState is configured. */
|
|
488
499
|
state: () => Promise<string | undefined>;
|
|
500
|
+
/** Session transcript loaded from memory BEFORE this run's goal was appended. */
|
|
501
|
+
history?: StoredMessage[];
|
|
489
502
|
}
|
|
490
503
|
|
|
491
504
|
/**
|
|
@@ -570,8 +583,10 @@ interface ReplanOutcome {
|
|
|
570
583
|
declare const decideReplan: (ctx: AgentContext, goal: string, done: string[], remaining: string[]) => Promise<ReplanOutcome>;
|
|
571
584
|
|
|
572
585
|
/**
|
|
573
|
-
* Write the final natural-language summary of what the run accomplished
|
|
574
|
-
*
|
|
586
|
+
* Write the final natural-language summary of what the run accomplished,
|
|
587
|
+
* streaming it as `final.text-delta` events. Plain text only (parsePlainText
|
|
588
|
+
* strips any stray JSON; deltas are suppressed entirely when the model drifts
|
|
589
|
+
* into JSON, so raw structure never reaches a UI). Falls back to a default
|
|
575
590
|
* sentence if the model returns nothing usable.
|
|
576
591
|
*/
|
|
577
592
|
declare const synthesizeAnswer: (ctx: AgentContext, goal: string, done: string[]) => Promise<{
|
|
@@ -646,6 +661,10 @@ declare const directBrowserOk: (p: ProviderType) => boolean;
|
|
|
646
661
|
* ever being exposed to JavaScript. Not even in-page script can read it out; it
|
|
647
662
|
* can only be used to encrypt / decrypt. That is what makes at-rest storage of
|
|
648
663
|
* the secrets meaningful (see IndexedDBVault's threat model).
|
|
664
|
+
*
|
|
665
|
+
* Concurrent callers share one in-flight promise per DB, and creation is
|
|
666
|
+
* double-checked in a single IDB transaction, so two racing writers can never
|
|
667
|
+
* end up encrypting under different keys.
|
|
649
668
|
*/
|
|
650
669
|
declare const getOrCreateVaultKey: (dbName?: string) => Promise<CryptoKey>;
|
|
651
670
|
interface EncryptedBlob {
|
|
@@ -716,7 +735,10 @@ interface ToolLoopOptions {
|
|
|
716
735
|
messages?: ModelMessage[];
|
|
717
736
|
/** In native mode: the callable tools. In prompted mode: used to dispatch salvaged calls. */
|
|
718
737
|
tools: ToolSet;
|
|
719
|
-
/**
|
|
738
|
+
/**
|
|
739
|
+
* Restrict callable tools to these names (plan-narrowed). Native mode passes
|
|
740
|
+
* them to the SDK's `activeTools`; prompted mode bounds `dispatch` to them.
|
|
741
|
+
*/
|
|
720
742
|
activeTools?: string[];
|
|
721
743
|
/** Native mode: cap on internal tool-calling steps (default 4). */
|
|
722
744
|
maxSteps?: number;
|
|
@@ -795,6 +817,8 @@ declare const dispatch: (action: RawAction, tools: ToolSet, ctx?: DispatchContex
|
|
|
795
817
|
* Decide the tool-calling strategy for a resolved model. An explicit
|
|
796
818
|
* config.toolMode of 'native'/'prompted' always wins; 'auto' (default) inspects
|
|
797
819
|
* the model's provider id — local/on-device runtimes → 'prompted', cloud → 'native'.
|
|
820
|
+
* A string model is a gateway model id ("openai/gpt-4o") — always cloud; the
|
|
821
|
+
* LOCAL_RE must not see it, or model names like "gpt-5-nano" would match `nano`.
|
|
798
822
|
*/
|
|
799
823
|
declare const selectToolMode: (model: LanguageModel, mode?: ToolMode) => "native" | "prompted";
|
|
800
824
|
|
|
@@ -823,6 +847,9 @@ interface CompressOptions {
|
|
|
823
847
|
maxChars?: number;
|
|
824
848
|
/** How many most-recent messages to keep verbatim. */
|
|
825
849
|
keepRecent?: number;
|
|
850
|
+
/** Time-box the summarisation call so it can never hang the caller. */
|
|
851
|
+
timeoutMs?: number;
|
|
852
|
+
abortSignal?: AbortSignal;
|
|
826
853
|
}
|
|
827
854
|
/**
|
|
828
855
|
* When the transcript grows too long, summarise everything except the last
|
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,11 @@ interface PlannerPromptContext {
|
|
|
105
105
|
state?: string;
|
|
106
106
|
toolCatalog: string;
|
|
107
107
|
mode: ToolCallMode;
|
|
108
|
+
/** Prior session messages (oldest first), for resolving references to earlier turns. */
|
|
109
|
+
history?: {
|
|
110
|
+
role: string;
|
|
111
|
+
content: string;
|
|
112
|
+
}[];
|
|
108
113
|
}
|
|
109
114
|
interface ExecutorPromptContext {
|
|
110
115
|
goal: string;
|
|
@@ -242,7 +247,9 @@ interface BrowserAgentConfig {
|
|
|
242
247
|
clientName?: string;
|
|
243
248
|
/** Host tools the executor may call (an AI SDK ToolSet; use defineTool). */
|
|
244
249
|
tools?: AgentToolSet;
|
|
250
|
+
/** Whitelist: only these tool names from `tools` are mounted (default: all). */
|
|
245
251
|
availableTools?: string[];
|
|
252
|
+
/** Blacklist: these tool names are removed after the whitelist is applied. */
|
|
246
253
|
excludedTools?: string[];
|
|
247
254
|
/** Force native/prompted tool-calling; 'auto' picks per model (default 'auto'). */
|
|
248
255
|
toolMode?: ToolMode;
|
|
@@ -253,7 +260,11 @@ interface BrowserAgentConfig {
|
|
|
253
260
|
prompts?: Partial<Prompts>;
|
|
254
261
|
/** Serialize the host's current world state into prompt context (grounding). */
|
|
255
262
|
describeState?: () => string | Promise<string>;
|
|
256
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* Persist the transcript (e.g. new IndexedDBStore()). The last few messages
|
|
265
|
+
* are also read back into the planner prompt, so follow-up goals can refer
|
|
266
|
+
* to earlier turns.
|
|
267
|
+
*/
|
|
257
268
|
memory?: ContextStore;
|
|
258
269
|
sessionId?: string;
|
|
259
270
|
/** Hard cap on executed steps incl. replans (default 8). */
|
|
@@ -486,6 +497,8 @@ interface AgentContext {
|
|
|
486
497
|
signal?: AbortSignal;
|
|
487
498
|
/** Current world state for grounding, or undefined when no describeState is configured. */
|
|
488
499
|
state: () => Promise<string | undefined>;
|
|
500
|
+
/** Session transcript loaded from memory BEFORE this run's goal was appended. */
|
|
501
|
+
history?: StoredMessage[];
|
|
489
502
|
}
|
|
490
503
|
|
|
491
504
|
/**
|
|
@@ -570,8 +583,10 @@ interface ReplanOutcome {
|
|
|
570
583
|
declare const decideReplan: (ctx: AgentContext, goal: string, done: string[], remaining: string[]) => Promise<ReplanOutcome>;
|
|
571
584
|
|
|
572
585
|
/**
|
|
573
|
-
* Write the final natural-language summary of what the run accomplished
|
|
574
|
-
*
|
|
586
|
+
* Write the final natural-language summary of what the run accomplished,
|
|
587
|
+
* streaming it as `final.text-delta` events. Plain text only (parsePlainText
|
|
588
|
+
* strips any stray JSON; deltas are suppressed entirely when the model drifts
|
|
589
|
+
* into JSON, so raw structure never reaches a UI). Falls back to a default
|
|
575
590
|
* sentence if the model returns nothing usable.
|
|
576
591
|
*/
|
|
577
592
|
declare const synthesizeAnswer: (ctx: AgentContext, goal: string, done: string[]) => Promise<{
|
|
@@ -646,6 +661,10 @@ declare const directBrowserOk: (p: ProviderType) => boolean;
|
|
|
646
661
|
* ever being exposed to JavaScript. Not even in-page script can read it out; it
|
|
647
662
|
* can only be used to encrypt / decrypt. That is what makes at-rest storage of
|
|
648
663
|
* the secrets meaningful (see IndexedDBVault's threat model).
|
|
664
|
+
*
|
|
665
|
+
* Concurrent callers share one in-flight promise per DB, and creation is
|
|
666
|
+
* double-checked in a single IDB transaction, so two racing writers can never
|
|
667
|
+
* end up encrypting under different keys.
|
|
649
668
|
*/
|
|
650
669
|
declare const getOrCreateVaultKey: (dbName?: string) => Promise<CryptoKey>;
|
|
651
670
|
interface EncryptedBlob {
|
|
@@ -716,7 +735,10 @@ interface ToolLoopOptions {
|
|
|
716
735
|
messages?: ModelMessage[];
|
|
717
736
|
/** In native mode: the callable tools. In prompted mode: used to dispatch salvaged calls. */
|
|
718
737
|
tools: ToolSet;
|
|
719
|
-
/**
|
|
738
|
+
/**
|
|
739
|
+
* Restrict callable tools to these names (plan-narrowed). Native mode passes
|
|
740
|
+
* them to the SDK's `activeTools`; prompted mode bounds `dispatch` to them.
|
|
741
|
+
*/
|
|
720
742
|
activeTools?: string[];
|
|
721
743
|
/** Native mode: cap on internal tool-calling steps (default 4). */
|
|
722
744
|
maxSteps?: number;
|
|
@@ -795,6 +817,8 @@ declare const dispatch: (action: RawAction, tools: ToolSet, ctx?: DispatchContex
|
|
|
795
817
|
* Decide the tool-calling strategy for a resolved model. An explicit
|
|
796
818
|
* config.toolMode of 'native'/'prompted' always wins; 'auto' (default) inspects
|
|
797
819
|
* the model's provider id — local/on-device runtimes → 'prompted', cloud → 'native'.
|
|
820
|
+
* A string model is a gateway model id ("openai/gpt-4o") — always cloud; the
|
|
821
|
+
* LOCAL_RE must not see it, or model names like "gpt-5-nano" would match `nano`.
|
|
798
822
|
*/
|
|
799
823
|
declare const selectToolMode: (model: LanguageModel, mode?: ToolMode) => "native" | "prompted";
|
|
800
824
|
|
|
@@ -823,6 +847,9 @@ interface CompressOptions {
|
|
|
823
847
|
maxChars?: number;
|
|
824
848
|
/** How many most-recent messages to keep verbatim. */
|
|
825
849
|
keepRecent?: number;
|
|
850
|
+
/** Time-box the summarisation call so it can never hang the caller. */
|
|
851
|
+
timeoutMs?: number;
|
|
852
|
+
abortSignal?: AbortSignal;
|
|
826
853
|
}
|
|
827
854
|
/**
|
|
828
855
|
* When the transcript grows too long, summarise everything except the last
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,9 @@ var compressHistory = async (messages, model, opts = {}) => {
|
|
|
94
94
|
const result = await generate(model, {
|
|
95
95
|
system: "Summarize the following conversation into a compact set of durable facts and decisions. Output plain text only.",
|
|
96
96
|
prompt: transcript,
|
|
97
|
-
maxOutputTokens: 400
|
|
97
|
+
maxOutputTokens: 400,
|
|
98
|
+
timeoutMs: opts.timeoutMs,
|
|
99
|
+
abortSignal: opts.abortSignal
|
|
98
100
|
});
|
|
99
101
|
const clean = (result.text || "").trim();
|
|
100
102
|
if (!clean) return messages;
|
|
@@ -302,9 +304,19 @@ var stateBlock = (state) => state && state.trim() ? `
|
|
|
302
304
|
|
|
303
305
|
STATE:
|
|
304
306
|
${state}` : "";
|
|
307
|
+
var clip = (s, max) => s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
308
|
+
var historyBlock = (history) => {
|
|
309
|
+
if (!history || history.length === 0) return "";
|
|
310
|
+
const lines = history.slice(-8).map((m) => `${m.role}: ${clip(m.content, 400)}`);
|
|
311
|
+
return `
|
|
312
|
+
|
|
313
|
+
CONVERSATION SO FAR:
|
|
314
|
+
${lines.join("\n")}`;
|
|
315
|
+
};
|
|
305
316
|
var PLANNER_NATIVE = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
|
|
306
317
|
Produce a brief "thought" and an ordered "steps" list (1\u20136 DISTINCT, self-contained steps) grounded in the current STATE and the available TOOLS.
|
|
307
318
|
If the user only greets, thanks, makes small talk, asks a question, or is unclear: return an EMPTY steps list and put a short, friendly answer in "thought".
|
|
319
|
+
If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.
|
|
308
320
|
Never repeat or pad steps.`;
|
|
309
321
|
var PLANNER_PROMPTED = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
|
|
310
322
|
|
|
@@ -312,7 +324,8 @@ Reply with a single JSON object, nothing else:
|
|
|
312
324
|
{ "reply": string, "plan": string[] }
|
|
313
325
|
- Only produce a "plan" when the user CLEARLY asks to build, do, or change something. For a greeting, small talk, thanks, a question, or an unclear/empty request: set "plan": [] and put a short, friendly "reply".
|
|
314
326
|
- For a real goal: "reply" is one short sentence; "plan" is 1\u20136 DISTINCT, self-contained steps. Never repeat or pad steps.
|
|
315
|
-
- Plan realistic steps the available TOOLS can perform, grounded in the current STATE
|
|
327
|
+
- Plan realistic steps the available TOOLS can perform, grounded in the current STATE.
|
|
328
|
+
- If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.`;
|
|
316
329
|
var EXECUTOR_NATIVE = `You are the EXECUTOR of a tool-using agent. Carry out ONLY the current step by calling the provided tools.
|
|
317
330
|
Build on the current STATE \u2014 do not repeat work that is already there.
|
|
318
331
|
When the step is done, reply with one short human sentence describing what you did (no JSON).
|
|
@@ -340,7 +353,7 @@ var SYNTHESIZER_SYSTEM = `You are the SYNTHESIZER. In 1\u20133 short, friendly s
|
|
|
340
353
|
var defaultPrompts = {
|
|
341
354
|
planner: (ctx) => ({
|
|
342
355
|
system: ctx.mode === "prompted" ? PLANNER_PROMPTED : PLANNER_NATIVE,
|
|
343
|
-
prompt: `GOAL: ${ctx.goal}${stateBlock(ctx.state)}
|
|
356
|
+
prompt: `GOAL: ${ctx.goal}${historyBlock(ctx.history)}${stateBlock(ctx.state)}
|
|
344
357
|
|
|
345
358
|
TOOLS:
|
|
346
359
|
${ctx.toolCatalog}`
|
|
@@ -385,11 +398,12 @@ var withSystem = (base, extra) => extra && extra.trim() ? `${extra.trim()}
|
|
|
385
398
|
${base}` : base;
|
|
386
399
|
|
|
387
400
|
// src/tools/mode.ts
|
|
388
|
-
var modelProviderId = (model) => typeof model === "string" ? model : model.provider ?? "";
|
|
389
401
|
var LOCAL_RE = /web-?llm|mlc|browser-ai|transformers|nano|built-?in/i;
|
|
390
402
|
var selectToolMode = (model, mode = "auto") => {
|
|
391
403
|
if (mode === "native" || mode === "prompted") return mode;
|
|
392
|
-
|
|
404
|
+
if (typeof model === "string") return "native";
|
|
405
|
+
const provider = model.provider ?? "";
|
|
406
|
+
return LOCAL_RE.test(provider) ? "prompted" : "native";
|
|
393
407
|
};
|
|
394
408
|
|
|
395
409
|
// src/tools/types.ts
|
|
@@ -514,24 +528,35 @@ var normalizeSteps = (arr, cap = 12) => {
|
|
|
514
528
|
}
|
|
515
529
|
return out;
|
|
516
530
|
};
|
|
531
|
+
var decodeJsonString = (body) => {
|
|
532
|
+
const v = tryParse(`"${body}"`);
|
|
533
|
+
return typeof v === "string" ? v : body;
|
|
534
|
+
};
|
|
517
535
|
var salvageStringArray = (text, field) => {
|
|
518
|
-
const
|
|
519
|
-
if (
|
|
520
|
-
const region = text.slice(at);
|
|
536
|
+
const m = new RegExp(`"${field}"\\s*:\\s*\\[`).exec(text);
|
|
537
|
+
if (!m) return [];
|
|
521
538
|
const out = [];
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
539
|
+
let i = m.index + m[0].length;
|
|
540
|
+
while (i < text.length && text[i] !== "]") {
|
|
541
|
+
if (text[i] !== '"') {
|
|
542
|
+
i += 1;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
let j = i + 1;
|
|
546
|
+
let esc = false;
|
|
547
|
+
while (j < text.length && (esc || text[j] !== '"')) {
|
|
548
|
+
esc = !esc && text[j] === "\\";
|
|
549
|
+
j += 1;
|
|
550
|
+
}
|
|
551
|
+
if (j >= text.length) break;
|
|
552
|
+
out.push(decodeJsonString(text.slice(i + 1, j)));
|
|
553
|
+
i = j + 1;
|
|
529
554
|
}
|
|
530
555
|
return out;
|
|
531
556
|
};
|
|
532
557
|
var salvageString = (text, field) => {
|
|
533
558
|
const m = text.match(new RegExp(`"${field}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
|
|
534
|
-
return m ? m[1].trim() : "";
|
|
559
|
+
return m ? decodeJsonString(m[1]).trim() : "";
|
|
535
560
|
};
|
|
536
561
|
var parsePlannerResponse = (raw) => {
|
|
537
562
|
const text = stripFences(raw);
|
|
@@ -654,12 +679,14 @@ var runPrompted = async (model, opts) => {
|
|
|
654
679
|
temperature: opts.temperature,
|
|
655
680
|
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
656
681
|
});
|
|
657
|
-
if (result.text) opts.callbacks?.onTextDelta?.(result.text);
|
|
658
682
|
const parsed = parseExecutorResponse(result.text);
|
|
683
|
+
if (parsed.reply) opts.callbacks?.onTextDelta?.(parsed.reply);
|
|
684
|
+
const active = opts.activeTools;
|
|
685
|
+
const tools = active ? Object.fromEntries(Object.entries(opts.tools).filter(([name]) => active.includes(name))) : opts.tools;
|
|
659
686
|
const toolCalls = [];
|
|
660
687
|
for (const action of parsed.actions) {
|
|
661
688
|
opts.callbacks?.onToolCall?.(action.tool, action.args);
|
|
662
|
-
const rec = await dispatch(action,
|
|
689
|
+
const rec = await dispatch(action, tools, { abortSignal: opts.abortSignal });
|
|
663
690
|
toolCalls.push(rec);
|
|
664
691
|
opts.callbacks?.onToolResult?.(rec.name, rec.output, rec.ok);
|
|
665
692
|
}
|
|
@@ -688,13 +715,15 @@ var summaryOf = (text, toolCallCount) => {
|
|
|
688
715
|
};
|
|
689
716
|
var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
690
717
|
const state = await ctx.state();
|
|
718
|
+
const active = activeToolNames(ctx, step);
|
|
719
|
+
const toolCatalog = active === void 0 ? ctx.toolCatalog : renderCatalog(Object.fromEntries(active.map((name) => [name, ctx.tools[name]])));
|
|
691
720
|
const parts = ctx.prompts.executor({
|
|
692
721
|
goal,
|
|
693
722
|
state,
|
|
694
723
|
step: step.description,
|
|
695
724
|
index,
|
|
696
725
|
total,
|
|
697
|
-
toolCatalog
|
|
726
|
+
toolCatalog,
|
|
698
727
|
done,
|
|
699
728
|
mode: ctx.executorMode
|
|
700
729
|
});
|
|
@@ -703,7 +732,7 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
|
703
732
|
system: systemFor(ctx, parts.system),
|
|
704
733
|
prompt: parts.prompt,
|
|
705
734
|
tools: ctx.tools,
|
|
706
|
-
activeTools:
|
|
735
|
+
activeTools: active,
|
|
707
736
|
maxSteps: ctx.config.maxStepsPerTask,
|
|
708
737
|
maxOutputTokens: ctx.config.budgets.executor,
|
|
709
738
|
temperature: ctx.config.temperature,
|
|
@@ -764,24 +793,26 @@ var toSteps = (raw) => raw.filter((s) => s.description && s.description.trim()).
|
|
|
764
793
|
}));
|
|
765
794
|
var createPlan = async (ctx, goal) => {
|
|
766
795
|
const state = await ctx.state();
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
796
|
+
const commonFor = (mode) => {
|
|
797
|
+
const parts = ctx.prompts.planner({
|
|
798
|
+
goal,
|
|
799
|
+
state,
|
|
800
|
+
toolCatalog: ctx.toolCatalog,
|
|
801
|
+
mode,
|
|
802
|
+
history: ctx.history
|
|
803
|
+
});
|
|
804
|
+
return {
|
|
805
|
+
system: systemFor(ctx, parts.system),
|
|
806
|
+
prompt: parts.prompt,
|
|
807
|
+
maxOutputTokens: ctx.config.budgets.planner,
|
|
808
|
+
temperature: ctx.config.temperature,
|
|
809
|
+
abortSignal: ctx.signal,
|
|
810
|
+
timeoutMs: ctx.config.chatTimeoutMs
|
|
811
|
+
};
|
|
781
812
|
};
|
|
782
813
|
if (ctx.plannerMode === "native") {
|
|
783
814
|
try {
|
|
784
|
-
const result2 = await generateStructured(ctx.plannerModel, PlanSchema,
|
|
815
|
+
const result2 = await generateStructured(ctx.plannerModel, PlanSchema, commonFor("native"));
|
|
785
816
|
return {
|
|
786
817
|
plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps) },
|
|
787
818
|
usage: normalizeUsage(result2.usage)
|
|
@@ -790,14 +821,14 @@ var createPlan = async (ctx, goal) => {
|
|
|
790
821
|
ctx.emit({ type: "retry", phase: "plan", attempt: 1, error: asMessage(err) });
|
|
791
822
|
}
|
|
792
823
|
}
|
|
793
|
-
const result = await generate(ctx.plannerModel,
|
|
824
|
+
const result = await generate(ctx.plannerModel, commonFor("prompted"));
|
|
794
825
|
const parsed = parsePlannerResponse(result.text);
|
|
795
826
|
return {
|
|
796
827
|
plan: {
|
|
797
828
|
thought: parsed.reply,
|
|
798
829
|
steps: toSteps(parsed.plan.map((description) => ({ description })))
|
|
799
830
|
},
|
|
800
|
-
usage:
|
|
831
|
+
usage: normalizeUsage(result.usage)
|
|
801
832
|
};
|
|
802
833
|
};
|
|
803
834
|
var asMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
@@ -805,26 +836,31 @@ var asMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
|
805
836
|
// src/agent/replanner.ts
|
|
806
837
|
var decideReplan = async (ctx, goal, done, remaining) => {
|
|
807
838
|
const state = await ctx.state();
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
839
|
+
const commonFor = (mode) => {
|
|
840
|
+
const parts = ctx.prompts.replanner({ goal, state, done, remaining, mode });
|
|
841
|
+
return {
|
|
842
|
+
system: systemFor(ctx, parts.system),
|
|
843
|
+
prompt: parts.prompt,
|
|
844
|
+
maxOutputTokens: ctx.config.budgets.replanner,
|
|
845
|
+
temperature: ctx.config.temperature,
|
|
846
|
+
abortSignal: ctx.signal,
|
|
847
|
+
timeoutMs: ctx.config.chatTimeoutMs
|
|
848
|
+
};
|
|
816
849
|
};
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const
|
|
850
|
+
if (ctx.plannerMode === "native") {
|
|
851
|
+
try {
|
|
852
|
+
const result = await generateStructured(ctx.plannerModel, ReplanSchema, commonFor("native"));
|
|
820
853
|
return {
|
|
821
|
-
decision:
|
|
822
|
-
reason:
|
|
823
|
-
plan:
|
|
824
|
-
usage: normalizeUsage(
|
|
854
|
+
decision: result.object.decision,
|
|
855
|
+
reason: result.object.reason,
|
|
856
|
+
plan: result.object.plan ?? [],
|
|
857
|
+
usage: normalizeUsage(result.usage)
|
|
825
858
|
};
|
|
859
|
+
} catch {
|
|
826
860
|
}
|
|
827
|
-
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
const result = await generate(ctx.plannerModel, commonFor("prompted"));
|
|
828
864
|
const parsed = parseReplannerResponse(result.text);
|
|
829
865
|
return {
|
|
830
866
|
decision: parsed.decision,
|
|
@@ -841,7 +877,7 @@ var decideReplan = async (ctx, goal, done, remaining) => {
|
|
|
841
877
|
var synthesizeAnswer = async (ctx, goal, done) => {
|
|
842
878
|
const state = await ctx.state();
|
|
843
879
|
const parts = ctx.prompts.synthesizer({ goal, state, done });
|
|
844
|
-
const result =
|
|
880
|
+
const result = stream(ctx.synthesizerModel, {
|
|
845
881
|
system: systemFor(ctx, parts.system),
|
|
846
882
|
prompt: parts.prompt,
|
|
847
883
|
maxOutputTokens: ctx.config.budgets.synthesizer,
|
|
@@ -849,37 +885,54 @@ var synthesizeAnswer = async (ctx, goal, done) => {
|
|
|
849
885
|
abortSignal: ctx.signal,
|
|
850
886
|
timeoutMs: ctx.config.chatTimeoutMs
|
|
851
887
|
});
|
|
852
|
-
|
|
888
|
+
let text = "";
|
|
889
|
+
let verdict = "unknown";
|
|
890
|
+
for await (const delta of result.textStream) {
|
|
891
|
+
text += delta;
|
|
892
|
+
if (verdict === "unknown") {
|
|
893
|
+
const lead = text.trimStart();
|
|
894
|
+
if (!lead) continue;
|
|
895
|
+
verdict = looksLikeJson(lead) ? "suppress" : "emit";
|
|
896
|
+
if (verdict === "emit") ctx.emit({ type: "final.text-delta", delta: text });
|
|
897
|
+
} else if (verdict === "emit") {
|
|
898
|
+
ctx.emit({ type: "final.text-delta", delta });
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const clean = parsePlainText(text);
|
|
853
902
|
return {
|
|
854
903
|
text: clean || "Done \u2014 the changes have been applied.",
|
|
855
|
-
usage: normalizeUsage(result.usage)
|
|
904
|
+
usage: normalizeUsage(await result.usage)
|
|
856
905
|
};
|
|
857
906
|
};
|
|
858
907
|
|
|
859
908
|
// src/agent/runner.ts
|
|
860
909
|
var errMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
910
|
+
var filterTools = (all, available, excluded) => {
|
|
911
|
+
let entries = Object.entries(all);
|
|
912
|
+
if (available && available.length > 0) entries = entries.filter(([n]) => available.includes(n));
|
|
913
|
+
if (excluded && excluded.length > 0) entries = entries.filter(([n]) => !excluded.includes(n));
|
|
914
|
+
return Object.fromEntries(entries);
|
|
915
|
+
};
|
|
861
916
|
var createAgent = async (config) => {
|
|
862
917
|
const cfg = resolveConfig(config);
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
),
|
|
874
|
-
buildModelFromStage(
|
|
875
|
-
resolveStage(config.model, config.synthesizer, "synthesizer"),
|
|
876
|
-
config.credentials,
|
|
877
|
-
cfg.clientName
|
|
878
|
-
)
|
|
918
|
+
const buildStage = (override, stageName) => buildModelFromStage(
|
|
919
|
+
resolveStage(config.model, override, stageName),
|
|
920
|
+
config.credentials,
|
|
921
|
+
cfg.clientName
|
|
922
|
+
);
|
|
923
|
+
const executorP = buildStage(void 0, "executor");
|
|
924
|
+
const [executorModel, plannerModel, synthesizerModel] = await Promise.all([
|
|
925
|
+
executorP,
|
|
926
|
+
config.planner === void 0 ? executorP : buildStage(config.planner, "planner"),
|
|
927
|
+
config.synthesizer === void 0 ? executorP : buildStage(config.synthesizer, "synthesizer")
|
|
879
928
|
]);
|
|
880
929
|
const plannerMode = selectToolMode(plannerModel, cfg.toolMode);
|
|
881
930
|
const executorMode = selectToolMode(executorModel, cfg.toolMode);
|
|
882
|
-
const tools =
|
|
931
|
+
const tools = filterTools(
|
|
932
|
+
config.tools ?? {},
|
|
933
|
+
config.availableTools,
|
|
934
|
+
config.excludedTools
|
|
935
|
+
);
|
|
883
936
|
const toolCatalog = renderCatalog(tools);
|
|
884
937
|
const prompts = { ...defaultPrompts, ...config.prompts };
|
|
885
938
|
const run = async (goal, opts = {}) => {
|
|
@@ -904,21 +957,6 @@ var createAgent = async (config) => {
|
|
|
904
957
|
};
|
|
905
958
|
if (!text) return result;
|
|
906
959
|
const state = async () => config.describeState ? config.describeState() : void 0;
|
|
907
|
-
const ctx = {
|
|
908
|
-
config: cfg,
|
|
909
|
-
raw: config,
|
|
910
|
-
plannerModel,
|
|
911
|
-
executorModel,
|
|
912
|
-
synthesizerModel,
|
|
913
|
-
plannerMode,
|
|
914
|
-
executorMode,
|
|
915
|
-
tools,
|
|
916
|
-
toolCatalog,
|
|
917
|
-
prompts,
|
|
918
|
-
emit,
|
|
919
|
-
signal: opts.signal,
|
|
920
|
-
state
|
|
921
|
-
};
|
|
922
960
|
const isAborted = () => opts.signal?.aborted === true;
|
|
923
961
|
const bumpUsage = (u, phase) => {
|
|
924
962
|
usage = addUsage(usage, u);
|
|
@@ -933,6 +971,29 @@ var createAgent = async (config) => {
|
|
|
933
971
|
}
|
|
934
972
|
};
|
|
935
973
|
emit({ type: "run.start", goal: text });
|
|
974
|
+
let history = [];
|
|
975
|
+
if (config.memory) {
|
|
976
|
+
try {
|
|
977
|
+
history = await config.memory.load(sessionId);
|
|
978
|
+
} catch {
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const ctx = {
|
|
982
|
+
config: cfg,
|
|
983
|
+
raw: config,
|
|
984
|
+
plannerModel,
|
|
985
|
+
executorModel,
|
|
986
|
+
synthesizerModel,
|
|
987
|
+
plannerMode,
|
|
988
|
+
executorMode,
|
|
989
|
+
tools,
|
|
990
|
+
toolCatalog,
|
|
991
|
+
prompts,
|
|
992
|
+
emit,
|
|
993
|
+
signal: opts.signal,
|
|
994
|
+
state,
|
|
995
|
+
history
|
|
996
|
+
};
|
|
936
997
|
await remember({ role: "user", content: text });
|
|
937
998
|
try {
|
|
938
999
|
const planned = await createPlan(ctx, text);
|
|
@@ -973,6 +1034,11 @@ var createAgent = async (config) => {
|
|
|
973
1034
|
bumpUsage(out.usage, "execute");
|
|
974
1035
|
stepResult = out.result;
|
|
975
1036
|
} catch (err) {
|
|
1037
|
+
if (isAborted()) {
|
|
1038
|
+
emit({ type: "stopped" });
|
|
1039
|
+
result.stopped = true;
|
|
1040
|
+
return result;
|
|
1041
|
+
}
|
|
976
1042
|
emit({ type: "error", phase: "execute", error: errMessage(err) });
|
|
977
1043
|
stepResult = { step, summary: errMessage(err), toolCalls: [], blocked: true };
|
|
978
1044
|
}
|
|
@@ -985,7 +1051,7 @@ var createAgent = async (config) => {
|
|
|
985
1051
|
done.push(
|
|
986
1052
|
`${step.description} \u2014 ${applied} applied${failed ? `, ${failed} failed` : ""}${stepResult.blocked ? ", blocked" : ""}`
|
|
987
1053
|
);
|
|
988
|
-
if (cfg.replan &&
|
|
1054
|
+
if (cfg.replan && iter < cfg.maxIterations && revisions < cfg.maxRevisions && !isAborted() && shouldReplan(stepResult)) {
|
|
989
1055
|
const decision = await decideReplan(
|
|
990
1056
|
ctx,
|
|
991
1057
|
text,
|
|
@@ -1025,11 +1091,13 @@ var createAgent = async (config) => {
|
|
|
1025
1091
|
result.final = summary;
|
|
1026
1092
|
if (config.memory && cfg.compressAfterChars > 0) {
|
|
1027
1093
|
try {
|
|
1028
|
-
const
|
|
1029
|
-
const compacted = await compressHistory(
|
|
1030
|
-
maxChars: cfg.compressAfterChars
|
|
1094
|
+
const transcript = await config.memory.load(sessionId);
|
|
1095
|
+
const compacted = await compressHistory(transcript, synthesizerModel, {
|
|
1096
|
+
maxChars: cfg.compressAfterChars,
|
|
1097
|
+
timeoutMs: cfg.chatTimeoutMs,
|
|
1098
|
+
abortSignal: opts.signal
|
|
1031
1099
|
});
|
|
1032
|
-
if (compacted !==
|
|
1100
|
+
if (compacted !== transcript) await config.memory.replace(sessionId, compacted);
|
|
1033
1101
|
} catch {
|
|
1034
1102
|
}
|
|
1035
1103
|
}
|
|
@@ -1078,6 +1146,9 @@ var openAgentWebDB = (opts = {}) => {
|
|
|
1078
1146
|
}
|
|
1079
1147
|
});
|
|
1080
1148
|
cache.set(name, p);
|
|
1149
|
+
p.catch(() => {
|
|
1150
|
+
if (cache.get(name) === p) cache.delete(name);
|
|
1151
|
+
});
|
|
1081
1152
|
}
|
|
1082
1153
|
return p;
|
|
1083
1154
|
};
|
|
@@ -1091,7 +1162,7 @@ var webcrypto = () => {
|
|
|
1091
1162
|
}
|
|
1092
1163
|
return c;
|
|
1093
1164
|
};
|
|
1094
|
-
var
|
|
1165
|
+
var loadOrCreateKey = async (dbName) => {
|
|
1095
1166
|
const db = await openAgentWebDB({ dbName });
|
|
1096
1167
|
const existing = await db.get(KEYS_STORE, KEY_ID);
|
|
1097
1168
|
if (existing) return existing;
|
|
@@ -1101,9 +1172,29 @@ var getOrCreateVaultKey = async (dbName) => {
|
|
|
1101
1172
|
false,
|
|
1102
1173
|
["encrypt", "decrypt"]
|
|
1103
1174
|
);
|
|
1104
|
-
|
|
1175
|
+
const tx = db.transaction(KEYS_STORE, "readwrite");
|
|
1176
|
+
const winner = await tx.store.get(KEY_ID);
|
|
1177
|
+
if (winner) {
|
|
1178
|
+
await tx.done;
|
|
1179
|
+
return winner;
|
|
1180
|
+
}
|
|
1181
|
+
await tx.store.put(key, KEY_ID);
|
|
1182
|
+
await tx.done;
|
|
1105
1183
|
return key;
|
|
1106
1184
|
};
|
|
1185
|
+
var keyCache = /* @__PURE__ */ new Map();
|
|
1186
|
+
var getOrCreateVaultKey = (dbName) => {
|
|
1187
|
+
const cacheKey = dbName ?? "agent-web";
|
|
1188
|
+
let p = keyCache.get(cacheKey);
|
|
1189
|
+
if (!p) {
|
|
1190
|
+
p = loadOrCreateKey(dbName);
|
|
1191
|
+
keyCache.set(cacheKey, p);
|
|
1192
|
+
p.catch(() => {
|
|
1193
|
+
if (keyCache.get(cacheKey) === p) keyCache.delete(cacheKey);
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
return p;
|
|
1197
|
+
};
|
|
1107
1198
|
var textEncoder = new TextEncoder();
|
|
1108
1199
|
var textDecoder = new TextDecoder();
|
|
1109
1200
|
var encryptJSON = async (key, value) => {
|