@dudko.dev/agent-web 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
- return LOCAL_RE.test(modelProviderId(model)) ? "prompted" : "native";
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 at = text.search(new RegExp(`"${field}"\\s*:`));
519
- if (at < 0) return [];
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
- const re = /"((?:[^"\\]|\\.)*)"/g;
523
- let skippedKey = false;
524
- let m = re.exec(region);
525
- while (m) {
526
- if (skippedKey) out.push(m[1]);
527
- skippedKey = true;
528
- m = re.exec(region);
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, opts.tools, { abortSignal: opts.abortSignal });
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: ctx.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: activeToolNames(ctx, step),
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 parts = ctx.prompts.planner({
768
- goal,
769
- state,
770
- toolCatalog: ctx.toolCatalog,
771
- mode: ctx.plannerMode
772
- });
773
- const system = systemFor(ctx, parts.system);
774
- const common = {
775
- system,
776
- prompt: parts.prompt,
777
- maxOutputTokens: ctx.config.budgets.planner,
778
- temperature: ctx.config.temperature,
779
- abortSignal: ctx.signal,
780
- timeoutMs: ctx.config.chatTimeoutMs
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, common);
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, common);
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: ctx.plannerMode === "native" ? emptyUsage() : normalizeUsage(result.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 parts = ctx.prompts.replanner({ goal, state, done, remaining, mode: ctx.plannerMode });
809
- const common = {
810
- system: systemFor(ctx, parts.system),
811
- prompt: parts.prompt,
812
- maxOutputTokens: ctx.config.budgets.replanner,
813
- temperature: ctx.config.temperature,
814
- abortSignal: ctx.signal,
815
- timeoutMs: ctx.config.chatTimeoutMs
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
- try {
818
- if (ctx.plannerMode === "native") {
819
- const result2 = await generateStructured(ctx.plannerModel, ReplanSchema, common);
850
+ if (ctx.plannerMode === "native") {
851
+ try {
852
+ const result = await generateStructured(ctx.plannerModel, ReplanSchema, commonFor("native"));
820
853
  return {
821
- decision: result2.object.decision,
822
- reason: result2.object.reason,
823
- plan: result2.object.plan ?? [],
824
- usage: normalizeUsage(result2.usage)
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
- const result = await generate(ctx.plannerModel, common);
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 = await generate(ctx.synthesizerModel, {
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
- const clean = parsePlainText(result.text);
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 [plannerModel, executorModel, synthesizerModel] = await Promise.all([
864
- buildModelFromStage(
865
- resolveStage(config.model, config.planner, "planner"),
866
- config.credentials,
867
- cfg.clientName
868
- ),
869
- buildModelFromStage(
870
- resolveStage(config.model, void 0, "executor"),
871
- config.credentials,
872
- cfg.clientName
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 = config.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 && remaining.length > 0 && iter < cfg.maxIterations && revisions < cfg.maxRevisions && !isAborted() && shouldReplan(stepResult)) {
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 history = await config.memory.load(sessionId);
1029
- const compacted = await compressHistory(history, synthesizerModel, {
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 !== history) await config.memory.replace(sessionId, 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 getOrCreateVaultKey = async (dbName) => {
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
- await db.put(KEYS_STORE, key, KEY_ID);
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) => {