@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/dist/index.cjs CHANGED
@@ -203,7 +203,9 @@ var compressHistory = async (messages, model, opts = {}) => {
203
203
  const result = await generate(model, {
204
204
  system: "Summarize the following conversation into a compact set of durable facts and decisions. Output plain text only.",
205
205
  prompt: transcript,
206
- maxOutputTokens: 400
206
+ maxOutputTokens: 400,
207
+ timeoutMs: opts.timeoutMs,
208
+ abortSignal: opts.abortSignal
207
209
  });
208
210
  const clean = (result.text || "").trim();
209
211
  if (!clean) return messages;
@@ -411,9 +413,19 @@ var stateBlock = (state) => state && state.trim() ? `
411
413
 
412
414
  STATE:
413
415
  ${state}` : "";
416
+ var clip = (s, max) => s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
417
+ var historyBlock = (history) => {
418
+ if (!history || history.length === 0) return "";
419
+ const lines = history.slice(-8).map((m) => `${m.role}: ${clip(m.content, 400)}`);
420
+ return `
421
+
422
+ CONVERSATION SO FAR:
423
+ ${lines.join("\n")}`;
424
+ };
414
425
  var PLANNER_NATIVE = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
415
426
  Produce a brief "thought" and an ordered "steps" list (1\u20136 DISTINCT, self-contained steps) grounded in the current STATE and the available TOOLS.
416
427
  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".
428
+ If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.
417
429
  Never repeat or pad steps.`;
418
430
  var PLANNER_PROMPTED = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
419
431
 
@@ -421,7 +433,8 @@ Reply with a single JSON object, nothing else:
421
433
  { "reply": string, "plan": string[] }
422
434
  - 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".
423
435
  - For a real goal: "reply" is one short sentence; "plan" is 1\u20136 DISTINCT, self-contained steps. Never repeat or pad steps.
424
- - Plan realistic steps the available TOOLS can perform, grounded in the current STATE.`;
436
+ - Plan realistic steps the available TOOLS can perform, grounded in the current STATE.
437
+ - If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.`;
425
438
  var EXECUTOR_NATIVE = `You are the EXECUTOR of a tool-using agent. Carry out ONLY the current step by calling the provided tools.
426
439
  Build on the current STATE \u2014 do not repeat work that is already there.
427
440
  When the step is done, reply with one short human sentence describing what you did (no JSON).
@@ -449,7 +462,7 @@ var SYNTHESIZER_SYSTEM = `You are the SYNTHESIZER. In 1\u20133 short, friendly s
449
462
  var defaultPrompts = {
450
463
  planner: (ctx) => ({
451
464
  system: ctx.mode === "prompted" ? PLANNER_PROMPTED : PLANNER_NATIVE,
452
- prompt: `GOAL: ${ctx.goal}${stateBlock(ctx.state)}
465
+ prompt: `GOAL: ${ctx.goal}${historyBlock(ctx.history)}${stateBlock(ctx.state)}
453
466
 
454
467
  TOOLS:
455
468
  ${ctx.toolCatalog}`
@@ -494,11 +507,12 @@ var withSystem = (base, extra) => extra && extra.trim() ? `${extra.trim()}
494
507
  ${base}` : base;
495
508
 
496
509
  // src/tools/mode.ts
497
- var modelProviderId = (model) => typeof model === "string" ? model : model.provider ?? "";
498
510
  var LOCAL_RE = /web-?llm|mlc|browser-ai|transformers|nano|built-?in/i;
499
511
  var selectToolMode = (model, mode = "auto") => {
500
512
  if (mode === "native" || mode === "prompted") return mode;
501
- return LOCAL_RE.test(modelProviderId(model)) ? "prompted" : "native";
513
+ if (typeof model === "string") return "native";
514
+ const provider = model.provider ?? "";
515
+ return LOCAL_RE.test(provider) ? "prompted" : "native";
502
516
  };
503
517
 
504
518
  // src/tools/types.ts
@@ -619,24 +633,35 @@ var normalizeSteps = (arr, cap = 12) => {
619
633
  }
620
634
  return out;
621
635
  };
636
+ var decodeJsonString = (body) => {
637
+ const v = tryParse(`"${body}"`);
638
+ return typeof v === "string" ? v : body;
639
+ };
622
640
  var salvageStringArray = (text, field) => {
623
- const at = text.search(new RegExp(`"${field}"\\s*:`));
624
- if (at < 0) return [];
625
- const region = text.slice(at);
641
+ const m = new RegExp(`"${field}"\\s*:\\s*\\[`).exec(text);
642
+ if (!m) return [];
626
643
  const out = [];
627
- const re = /"((?:[^"\\]|\\.)*)"/g;
628
- let skippedKey = false;
629
- let m = re.exec(region);
630
- while (m) {
631
- if (skippedKey) out.push(m[1]);
632
- skippedKey = true;
633
- m = re.exec(region);
644
+ let i = m.index + m[0].length;
645
+ while (i < text.length && text[i] !== "]") {
646
+ if (text[i] !== '"') {
647
+ i += 1;
648
+ continue;
649
+ }
650
+ let j = i + 1;
651
+ let esc = false;
652
+ while (j < text.length && (esc || text[j] !== '"')) {
653
+ esc = !esc && text[j] === "\\";
654
+ j += 1;
655
+ }
656
+ if (j >= text.length) break;
657
+ out.push(decodeJsonString(text.slice(i + 1, j)));
658
+ i = j + 1;
634
659
  }
635
660
  return out;
636
661
  };
637
662
  var salvageString = (text, field) => {
638
663
  const m = text.match(new RegExp(`"${field}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
639
- return m ? m[1].trim() : "";
664
+ return m ? decodeJsonString(m[1]).trim() : "";
640
665
  };
641
666
  var parsePlannerResponse = (raw) => {
642
667
  const text = stripFences(raw);
@@ -759,12 +784,14 @@ var runPrompted = async (model, opts) => {
759
784
  temperature: opts.temperature,
760
785
  abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
761
786
  });
762
- if (result.text) opts.callbacks?.onTextDelta?.(result.text);
763
787
  const parsed = parseExecutorResponse(result.text);
788
+ if (parsed.reply) opts.callbacks?.onTextDelta?.(parsed.reply);
789
+ const active = opts.activeTools;
790
+ const tools = active ? Object.fromEntries(Object.entries(opts.tools).filter(([name]) => active.includes(name))) : opts.tools;
764
791
  const toolCalls = [];
765
792
  for (const action of parsed.actions) {
766
793
  opts.callbacks?.onToolCall?.(action.tool, action.args);
767
- const rec = await dispatch(action, opts.tools, { abortSignal: opts.abortSignal });
794
+ const rec = await dispatch(action, tools, { abortSignal: opts.abortSignal });
768
795
  toolCalls.push(rec);
769
796
  opts.callbacks?.onToolResult?.(rec.name, rec.output, rec.ok);
770
797
  }
@@ -793,13 +820,15 @@ var summaryOf = (text, toolCallCount) => {
793
820
  };
794
821
  var executeStep = async (ctx, goal, step, index, total, done) => {
795
822
  const state = await ctx.state();
823
+ const active = activeToolNames(ctx, step);
824
+ const toolCatalog = active === void 0 ? ctx.toolCatalog : renderCatalog(Object.fromEntries(active.map((name) => [name, ctx.tools[name]])));
796
825
  const parts = ctx.prompts.executor({
797
826
  goal,
798
827
  state,
799
828
  step: step.description,
800
829
  index,
801
830
  total,
802
- toolCatalog: ctx.toolCatalog,
831
+ toolCatalog,
803
832
  done,
804
833
  mode: ctx.executorMode
805
834
  });
@@ -808,7 +837,7 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
808
837
  system: systemFor(ctx, parts.system),
809
838
  prompt: parts.prompt,
810
839
  tools: ctx.tools,
811
- activeTools: activeToolNames(ctx, step),
840
+ activeTools: active,
812
841
  maxSteps: ctx.config.maxStepsPerTask,
813
842
  maxOutputTokens: ctx.config.budgets.executor,
814
843
  temperature: ctx.config.temperature,
@@ -869,24 +898,26 @@ var toSteps = (raw) => raw.filter((s) => s.description && s.description.trim()).
869
898
  }));
870
899
  var createPlan = async (ctx, goal) => {
871
900
  const state = await ctx.state();
872
- const parts = ctx.prompts.planner({
873
- goal,
874
- state,
875
- toolCatalog: ctx.toolCatalog,
876
- mode: ctx.plannerMode
877
- });
878
- const system = systemFor(ctx, parts.system);
879
- const common = {
880
- system,
881
- prompt: parts.prompt,
882
- maxOutputTokens: ctx.config.budgets.planner,
883
- temperature: ctx.config.temperature,
884
- abortSignal: ctx.signal,
885
- timeoutMs: ctx.config.chatTimeoutMs
901
+ const commonFor = (mode) => {
902
+ const parts = ctx.prompts.planner({
903
+ goal,
904
+ state,
905
+ toolCatalog: ctx.toolCatalog,
906
+ mode,
907
+ history: ctx.history
908
+ });
909
+ return {
910
+ system: systemFor(ctx, parts.system),
911
+ prompt: parts.prompt,
912
+ maxOutputTokens: ctx.config.budgets.planner,
913
+ temperature: ctx.config.temperature,
914
+ abortSignal: ctx.signal,
915
+ timeoutMs: ctx.config.chatTimeoutMs
916
+ };
886
917
  };
887
918
  if (ctx.plannerMode === "native") {
888
919
  try {
889
- const result2 = await generateStructured(ctx.plannerModel, PlanSchema, common);
920
+ const result2 = await generateStructured(ctx.plannerModel, PlanSchema, commonFor("native"));
890
921
  return {
891
922
  plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps) },
892
923
  usage: normalizeUsage(result2.usage)
@@ -895,14 +926,14 @@ var createPlan = async (ctx, goal) => {
895
926
  ctx.emit({ type: "retry", phase: "plan", attempt: 1, error: asMessage(err) });
896
927
  }
897
928
  }
898
- const result = await generate(ctx.plannerModel, common);
929
+ const result = await generate(ctx.plannerModel, commonFor("prompted"));
899
930
  const parsed = parsePlannerResponse(result.text);
900
931
  return {
901
932
  plan: {
902
933
  thought: parsed.reply,
903
934
  steps: toSteps(parsed.plan.map((description) => ({ description })))
904
935
  },
905
- usage: ctx.plannerMode === "native" ? emptyUsage() : normalizeUsage(result.usage)
936
+ usage: normalizeUsage(result.usage)
906
937
  };
907
938
  };
908
939
  var asMessage = (err) => err instanceof Error ? err.message : String(err);
@@ -910,26 +941,31 @@ var asMessage = (err) => err instanceof Error ? err.message : String(err);
910
941
  // src/agent/replanner.ts
911
942
  var decideReplan = async (ctx, goal, done, remaining) => {
912
943
  const state = await ctx.state();
913
- const parts = ctx.prompts.replanner({ goal, state, done, remaining, mode: ctx.plannerMode });
914
- const common = {
915
- system: systemFor(ctx, parts.system),
916
- prompt: parts.prompt,
917
- maxOutputTokens: ctx.config.budgets.replanner,
918
- temperature: ctx.config.temperature,
919
- abortSignal: ctx.signal,
920
- timeoutMs: ctx.config.chatTimeoutMs
944
+ const commonFor = (mode) => {
945
+ const parts = ctx.prompts.replanner({ goal, state, done, remaining, mode });
946
+ return {
947
+ system: systemFor(ctx, parts.system),
948
+ prompt: parts.prompt,
949
+ maxOutputTokens: ctx.config.budgets.replanner,
950
+ temperature: ctx.config.temperature,
951
+ abortSignal: ctx.signal,
952
+ timeoutMs: ctx.config.chatTimeoutMs
953
+ };
921
954
  };
922
- try {
923
- if (ctx.plannerMode === "native") {
924
- const result2 = await generateStructured(ctx.plannerModel, ReplanSchema, common);
955
+ if (ctx.plannerMode === "native") {
956
+ try {
957
+ const result = await generateStructured(ctx.plannerModel, ReplanSchema, commonFor("native"));
925
958
  return {
926
- decision: result2.object.decision,
927
- reason: result2.object.reason,
928
- plan: result2.object.plan ?? [],
929
- usage: normalizeUsage(result2.usage)
959
+ decision: result.object.decision,
960
+ reason: result.object.reason,
961
+ plan: result.object.plan ?? [],
962
+ usage: normalizeUsage(result.usage)
930
963
  };
964
+ } catch {
931
965
  }
932
- const result = await generate(ctx.plannerModel, common);
966
+ }
967
+ try {
968
+ const result = await generate(ctx.plannerModel, commonFor("prompted"));
933
969
  const parsed = parseReplannerResponse(result.text);
934
970
  return {
935
971
  decision: parsed.decision,
@@ -946,7 +982,7 @@ var decideReplan = async (ctx, goal, done, remaining) => {
946
982
  var synthesizeAnswer = async (ctx, goal, done) => {
947
983
  const state = await ctx.state();
948
984
  const parts = ctx.prompts.synthesizer({ goal, state, done });
949
- const result = await generate(ctx.synthesizerModel, {
985
+ const result = stream(ctx.synthesizerModel, {
950
986
  system: systemFor(ctx, parts.system),
951
987
  prompt: parts.prompt,
952
988
  maxOutputTokens: ctx.config.budgets.synthesizer,
@@ -954,37 +990,54 @@ var synthesizeAnswer = async (ctx, goal, done) => {
954
990
  abortSignal: ctx.signal,
955
991
  timeoutMs: ctx.config.chatTimeoutMs
956
992
  });
957
- const clean = parsePlainText(result.text);
993
+ let text = "";
994
+ let verdict = "unknown";
995
+ for await (const delta of result.textStream) {
996
+ text += delta;
997
+ if (verdict === "unknown") {
998
+ const lead = text.trimStart();
999
+ if (!lead) continue;
1000
+ verdict = looksLikeJson(lead) ? "suppress" : "emit";
1001
+ if (verdict === "emit") ctx.emit({ type: "final.text-delta", delta: text });
1002
+ } else if (verdict === "emit") {
1003
+ ctx.emit({ type: "final.text-delta", delta });
1004
+ }
1005
+ }
1006
+ const clean = parsePlainText(text);
958
1007
  return {
959
1008
  text: clean || "Done \u2014 the changes have been applied.",
960
- usage: normalizeUsage(result.usage)
1009
+ usage: normalizeUsage(await result.usage)
961
1010
  };
962
1011
  };
963
1012
 
964
1013
  // src/agent/runner.ts
965
1014
  var errMessage = (err) => err instanceof Error ? err.message : String(err);
1015
+ var filterTools = (all, available, excluded) => {
1016
+ let entries = Object.entries(all);
1017
+ if (available && available.length > 0) entries = entries.filter(([n]) => available.includes(n));
1018
+ if (excluded && excluded.length > 0) entries = entries.filter(([n]) => !excluded.includes(n));
1019
+ return Object.fromEntries(entries);
1020
+ };
966
1021
  var createAgent = async (config) => {
967
1022
  const cfg = resolveConfig(config);
968
- const [plannerModel, executorModel, synthesizerModel] = await Promise.all([
969
- buildModelFromStage(
970
- resolveStage(config.model, config.planner, "planner"),
971
- config.credentials,
972
- cfg.clientName
973
- ),
974
- buildModelFromStage(
975
- resolveStage(config.model, void 0, "executor"),
976
- config.credentials,
977
- cfg.clientName
978
- ),
979
- buildModelFromStage(
980
- resolveStage(config.model, config.synthesizer, "synthesizer"),
981
- config.credentials,
982
- cfg.clientName
983
- )
1023
+ const buildStage = (override, stageName) => buildModelFromStage(
1024
+ resolveStage(config.model, override, stageName),
1025
+ config.credentials,
1026
+ cfg.clientName
1027
+ );
1028
+ const executorP = buildStage(void 0, "executor");
1029
+ const [executorModel, plannerModel, synthesizerModel] = await Promise.all([
1030
+ executorP,
1031
+ config.planner === void 0 ? executorP : buildStage(config.planner, "planner"),
1032
+ config.synthesizer === void 0 ? executorP : buildStage(config.synthesizer, "synthesizer")
984
1033
  ]);
985
1034
  const plannerMode = selectToolMode(plannerModel, cfg.toolMode);
986
1035
  const executorMode = selectToolMode(executorModel, cfg.toolMode);
987
- const tools = config.tools ?? {};
1036
+ const tools = filterTools(
1037
+ config.tools ?? {},
1038
+ config.availableTools,
1039
+ config.excludedTools
1040
+ );
988
1041
  const toolCatalog = renderCatalog(tools);
989
1042
  const prompts = { ...defaultPrompts, ...config.prompts };
990
1043
  const run = async (goal, opts = {}) => {
@@ -1009,21 +1062,6 @@ var createAgent = async (config) => {
1009
1062
  };
1010
1063
  if (!text) return result;
1011
1064
  const state = async () => config.describeState ? config.describeState() : void 0;
1012
- const ctx = {
1013
- config: cfg,
1014
- raw: config,
1015
- plannerModel,
1016
- executorModel,
1017
- synthesizerModel,
1018
- plannerMode,
1019
- executorMode,
1020
- tools,
1021
- toolCatalog,
1022
- prompts,
1023
- emit,
1024
- signal: opts.signal,
1025
- state
1026
- };
1027
1065
  const isAborted = () => opts.signal?.aborted === true;
1028
1066
  const bumpUsage = (u, phase) => {
1029
1067
  usage = addUsage(usage, u);
@@ -1038,6 +1076,29 @@ var createAgent = async (config) => {
1038
1076
  }
1039
1077
  };
1040
1078
  emit({ type: "run.start", goal: text });
1079
+ let history = [];
1080
+ if (config.memory) {
1081
+ try {
1082
+ history = await config.memory.load(sessionId);
1083
+ } catch {
1084
+ }
1085
+ }
1086
+ const ctx = {
1087
+ config: cfg,
1088
+ raw: config,
1089
+ plannerModel,
1090
+ executorModel,
1091
+ synthesizerModel,
1092
+ plannerMode,
1093
+ executorMode,
1094
+ tools,
1095
+ toolCatalog,
1096
+ prompts,
1097
+ emit,
1098
+ signal: opts.signal,
1099
+ state,
1100
+ history
1101
+ };
1041
1102
  await remember({ role: "user", content: text });
1042
1103
  try {
1043
1104
  const planned = await createPlan(ctx, text);
@@ -1078,6 +1139,11 @@ var createAgent = async (config) => {
1078
1139
  bumpUsage(out.usage, "execute");
1079
1140
  stepResult = out.result;
1080
1141
  } catch (err) {
1142
+ if (isAborted()) {
1143
+ emit({ type: "stopped" });
1144
+ result.stopped = true;
1145
+ return result;
1146
+ }
1081
1147
  emit({ type: "error", phase: "execute", error: errMessage(err) });
1082
1148
  stepResult = { step, summary: errMessage(err), toolCalls: [], blocked: true };
1083
1149
  }
@@ -1090,7 +1156,7 @@ var createAgent = async (config) => {
1090
1156
  done.push(
1091
1157
  `${step.description} \u2014 ${applied} applied${failed ? `, ${failed} failed` : ""}${stepResult.blocked ? ", blocked" : ""}`
1092
1158
  );
1093
- if (cfg.replan && remaining.length > 0 && iter < cfg.maxIterations && revisions < cfg.maxRevisions && !isAborted() && shouldReplan(stepResult)) {
1159
+ if (cfg.replan && iter < cfg.maxIterations && revisions < cfg.maxRevisions && !isAborted() && shouldReplan(stepResult)) {
1094
1160
  const decision = await decideReplan(
1095
1161
  ctx,
1096
1162
  text,
@@ -1130,11 +1196,13 @@ var createAgent = async (config) => {
1130
1196
  result.final = summary;
1131
1197
  if (config.memory && cfg.compressAfterChars > 0) {
1132
1198
  try {
1133
- const history = await config.memory.load(sessionId);
1134
- const compacted = await compressHistory(history, synthesizerModel, {
1135
- maxChars: cfg.compressAfterChars
1199
+ const transcript = await config.memory.load(sessionId);
1200
+ const compacted = await compressHistory(transcript, synthesizerModel, {
1201
+ maxChars: cfg.compressAfterChars,
1202
+ timeoutMs: cfg.chatTimeoutMs,
1203
+ abortSignal: opts.signal
1136
1204
  });
1137
- if (compacted !== history) await config.memory.replace(sessionId, compacted);
1205
+ if (compacted !== transcript) await config.memory.replace(sessionId, compacted);
1138
1206
  } catch {
1139
1207
  }
1140
1208
  }
@@ -1186,6 +1254,9 @@ var openAgentWebDB = (opts = {}) => {
1186
1254
  }
1187
1255
  });
1188
1256
  cache.set(name, p);
1257
+ p.catch(() => {
1258
+ if (cache.get(name) === p) cache.delete(name);
1259
+ });
1189
1260
  }
1190
1261
  return p;
1191
1262
  };
@@ -1199,7 +1270,7 @@ var webcrypto = () => {
1199
1270
  }
1200
1271
  return c;
1201
1272
  };
1202
- var getOrCreateVaultKey = async (dbName) => {
1273
+ var loadOrCreateKey = async (dbName) => {
1203
1274
  const db = await openAgentWebDB({ dbName });
1204
1275
  const existing = await db.get(KEYS_STORE, KEY_ID);
1205
1276
  if (existing) return existing;
@@ -1209,9 +1280,29 @@ var getOrCreateVaultKey = async (dbName) => {
1209
1280
  false,
1210
1281
  ["encrypt", "decrypt"]
1211
1282
  );
1212
- await db.put(KEYS_STORE, key, KEY_ID);
1283
+ const tx = db.transaction(KEYS_STORE, "readwrite");
1284
+ const winner = await tx.store.get(KEY_ID);
1285
+ if (winner) {
1286
+ await tx.done;
1287
+ return winner;
1288
+ }
1289
+ await tx.store.put(key, KEY_ID);
1290
+ await tx.done;
1213
1291
  return key;
1214
1292
  };
1293
+ var keyCache = /* @__PURE__ */ new Map();
1294
+ var getOrCreateVaultKey = (dbName) => {
1295
+ const cacheKey = dbName ?? "agent-web";
1296
+ let p = keyCache.get(cacheKey);
1297
+ if (!p) {
1298
+ p = loadOrCreateKey(dbName);
1299
+ keyCache.set(cacheKey, p);
1300
+ p.catch(() => {
1301
+ if (keyCache.get(cacheKey) === p) keyCache.delete(cacheKey);
1302
+ });
1303
+ }
1304
+ return p;
1305
+ };
1215
1306
  var textEncoder = new TextEncoder();
1216
1307
  var textDecoder = new TextDecoder();
1217
1308
  var encryptJSON = async (key, value) => {