assistme 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/{chunk-KX7ITO55.js → chunk-4SBIN27G.js} +184 -22
  2. package/dist/{chunk-TTEGHE2E.js → chunk-JVA6DHXD.js} +6 -4
  3. package/dist/{config-PUIS2TQL.js → config-T4357GAE.js} +1 -1
  4. package/dist/index.js +434 -221
  5. package/dist/job-runner-CJ7HM4GZ.js +7 -0
  6. package/package.json +2 -1
  7. package/src/agent/event-hooks.ts +59 -10
  8. package/src/agent/job-runner.ts +52 -40
  9. package/src/agent/memory.ts +124 -0
  10. package/src/agent/processor.ts +62 -87
  11. package/src/agent/scheduler.ts +22 -59
  12. package/src/agent/skill-evaluator.ts +192 -87
  13. package/src/agent/skills.ts +57 -36
  14. package/src/agent/system-prompt.ts +9 -0
  15. package/src/browser/controller.ts +16 -5
  16. package/src/db/types.ts +3 -1
  17. package/src/tools/filesystem.ts +32 -35
  18. package/src/tools/shell.ts +18 -22
  19. package/src/utils/config.ts +15 -9
  20. package/src/utils/constants.ts +98 -0
  21. package/src/utils/errors.ts +37 -0
  22. package/src/utils/schemas.ts +148 -0
  23. package/{src → tests}/agent/event-hooks.test.ts +121 -33
  24. package/{src → tests}/agent/mcp-servers.test.ts +43 -29
  25. package/{src → tests}/agent/memory.test.ts +71 -3
  26. package/{src → tests}/agent/processor.test.ts +59 -55
  27. package/{src → tests}/agent/scheduler.test.ts +1 -1
  28. package/{src → tests}/agent/session.test.ts +20 -10
  29. package/{src → tests}/agent/skills.test.ts +51 -29
  30. package/{src → tests}/credentials/credential-store.test.ts +23 -8
  31. package/{src → tests}/credentials/encryption.test.ts +1 -1
  32. package/{src → tests}/db/supabase.test.ts +4 -4
  33. package/{src → tests}/tools/filesystem.test.ts +6 -15
  34. package/{src → tests}/tools/shell.test.ts +1 -1
  35. package/{src → tests}/utils/config.test.ts +3 -2
  36. package/{src → tests}/utils/rate-limiter.test.ts +1 -1
  37. package/{src → tests}/utils/retry.test.ts +6 -12
  38. package/tsconfig.json +1 -1
  39. package/vitest.config.ts +1 -1
  40. package/dist/job-runner-P2L6MOOX.js +0 -7
package/dist/index.js CHANGED
@@ -1,20 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AppError,
4
+ BrowseSkillRowSchema,
5
+ CDP_COMMAND_TIMEOUT_MS,
6
+ FRAME_CONTEXTS_MAX_SIZE,
3
7
  JobRunner,
8
+ MAX_BUDGET_USD,
9
+ MAX_COMPLETE_TASK_RETRIES,
10
+ MAX_CONTENT_SEARCH_FILES,
11
+ MAX_CONTENT_SEARCH_RESULTS,
12
+ MAX_FILE_SEARCH_RESULTS,
13
+ MAX_HISTORY_ENTRIES,
14
+ MAX_HISTORY_RESPONSE_LENGTH,
15
+ MAX_RESPONSE_CONTENT_LENGTH,
16
+ MAX_SKILL_RECORD_RESULT_LENGTH,
17
+ MAX_TOOL_INPUT_LOG_LENGTH,
18
+ MAX_TOOL_RESULT_LENGTH,
19
+ MEMORY_COMPRESSION_TARGET,
20
+ MEMORY_COMPRESSION_THRESHOLD,
21
+ MEMORY_DEDUP_SIMILARITY_THRESHOLD,
22
+ SCHEDULER_INTERVAL_MS,
23
+ SHELL_MAX_OUTPUT,
24
+ SHELL_TIMEOUT_MS,
25
+ SKILL_DESCRIPTION_BUDGET_CHARS,
26
+ SKILL_VALIDATION_MAX_TURNS,
27
+ SkillCreateResultSchema,
28
+ SkillDecisionSchema,
29
+ SkillRowSchema,
30
+ WS_CONNECT_TIMEOUT_MS,
4
31
  callMcpHandler,
32
+ errorMessage,
5
33
  log,
6
34
  newCorrelationId,
7
35
  readAuthStore,
36
+ safeParse,
8
37
  setCorrelationId,
9
38
  setLogLevel,
10
39
  writeAuthStore
11
- } from "./chunk-KX7ITO55.js";
40
+ } from "./chunk-4SBIN27G.js";
12
41
  import {
13
42
  clearConfig,
14
43
  getConfig,
15
44
  getConfigPath,
16
45
  setConfig
17
- } from "./chunk-TTEGHE2E.js";
46
+ } from "./chunk-JVA6DHXD.js";
18
47
 
19
48
  // src/index.ts
20
49
  import { Command } from "commander";
@@ -54,7 +83,7 @@ async function logout() {
54
83
 
55
84
  // src/db/session.ts
56
85
  async function createSession(sessionName, workspacePath, version2) {
57
- const { getConfig: getConfig2 } = await import("./config-PUIS2TQL.js");
86
+ const { getConfig: getConfig2 } = await import("./config-T4357GAE.js");
58
87
  const data = await callMcpHandler("session.create", {
59
88
  session_name: sessionName,
60
89
  workspace_path: workspacePath,
@@ -135,11 +164,11 @@ async function completeTask(messageId, resultSummary, tokenUsage) {
135
164
  token_usage: tokenUsage || null
136
165
  });
137
166
  }
138
- async function failTask(messageId, errorMessage) {
167
+ async function failTask(messageId, errorMessage2) {
139
168
  try {
140
169
  await callMcpHandler("task.fail", {
141
170
  message_id: messageId,
142
- error: errorMessage
171
+ error: errorMessage2
143
172
  });
144
173
  } catch (err) {
145
174
  log.error(`Failed to update task status: ${err instanceof Error ? err.message : err}`);
@@ -412,9 +441,9 @@ var BrowserController = class {
412
441
  if (!settled) {
413
442
  settled = true;
414
443
  this.ws?.close();
415
- reject(new Error("Connection timeout (5s)"));
444
+ reject(new Error(`Connection timeout (${WS_CONNECT_TIMEOUT_MS}ms)`));
416
445
  }
417
- }, 5e3);
446
+ }, WS_CONNECT_TIMEOUT_MS);
418
447
  this.ws.on("open", () => {
419
448
  if (settled) return;
420
449
  settled = true;
@@ -462,6 +491,8 @@ var BrowserController = class {
462
491
  this.ws = null;
463
492
  this.connected = false;
464
493
  }
494
+ this.refCache.clear();
495
+ this.frameContexts.clear();
465
496
  return "Disconnected from browser.";
466
497
  }
467
498
  // ── CDP Protocol ────────────────────────────────────────────────
@@ -481,7 +512,7 @@ var BrowserController = class {
481
512
  const timeout = setTimeout(() => {
482
513
  this.callbacks.delete(id);
483
514
  reject(new Error(`CDP command timed out: ${method}`));
484
- }, 15e3);
515
+ }, CDP_COMMAND_TIMEOUT_MS);
485
516
  this.callbacks.set(id, (response) => {
486
517
  clearTimeout(timeout);
487
518
  if (response.error) {
@@ -1213,7 +1244,9 @@ Refs:
1213
1244
  height: r.box.height
1214
1245
  }
1215
1246
  });
1216
- this.frameContexts.set(r.id, contextId);
1247
+ if (this.frameContexts.size < FRAME_CONTEXTS_MAX_SIZE) {
1248
+ this.frameContexts.set(r.id, contextId);
1249
+ }
1217
1250
  }
1218
1251
  } catch {
1219
1252
  }
@@ -2328,51 +2361,22 @@ import ora3 from "ora";
2328
2361
  import { createInterface as createInterface2 } from "readline";
2329
2362
 
2330
2363
  // src/agent/scheduler.ts
2331
- var SCHEDULER_INTERVAL = 3e4;
2364
+ import { Cron } from "croner";
2332
2365
  function getNextRunTime(cronExpr, timezone, fromDate) {
2333
2366
  const now = fromDate || /* @__PURE__ */ new Date();
2334
- const parts = cronExpr.trim().split(/\s+/);
2335
- if (parts.length !== 5) {
2336
- throw new Error(`Invalid cron expression: ${cronExpr}`);
2337
- }
2338
- const [minExpr, hourExpr, domExpr, monExpr, dowExpr] = parts;
2339
- function parseField(expr, min, max) {
2340
- const values = [];
2341
- for (const part of expr.split(",")) {
2342
- if (part === "*") {
2343
- for (let i = min; i <= max; i++) values.push(i);
2344
- } else if (part.startsWith("*/")) {
2345
- const step = parseInt(part.slice(2));
2346
- for (let i = min; i <= max; i += step) values.push(i);
2347
- } else if (part.includes("-")) {
2348
- const [start, end] = part.split("-").map(Number);
2349
- for (let i = start; i <= end; i++) values.push(i);
2350
- } else {
2351
- values.push(parseInt(part));
2352
- }
2367
+ try {
2368
+ const job = new Cron(cronExpr, { timezone: timezone || "UTC" });
2369
+ const next = job.nextRun(now);
2370
+ if (!next) {
2371
+ throw new Error(`No future run time found for cron expression: ${cronExpr}`);
2372
+ }
2373
+ return next;
2374
+ } catch (err) {
2375
+ if (err instanceof Error && err.message.includes("No future run time")) {
2376
+ throw err;
2353
2377
  }
2354
- return values.sort((a, b) => a - b);
2355
- }
2356
- const minutes = parseField(minExpr, 0, 59);
2357
- const hours = parseField(hourExpr, 0, 23);
2358
- const daysOfMonth = parseField(domExpr, 1, 31);
2359
- const months = parseField(monExpr, 1, 12);
2360
- const daysOfWeek = parseField(dowExpr, 0, 6);
2361
- const useUTC = timezone === "UTC";
2362
- const candidate = new Date(now.getTime() + 6e4);
2363
- candidate.setSeconds(0, 0);
2364
- for (let i = 0; i < 527040; i++) {
2365
- const m = useUTC ? candidate.getUTCMinutes() : candidate.getMinutes();
2366
- const h = useUTC ? candidate.getUTCHours() : candidate.getHours();
2367
- const dom = useUTC ? candidate.getUTCDate() : candidate.getDate();
2368
- const mon = (useUTC ? candidate.getUTCMonth() : candidate.getMonth()) + 1;
2369
- const dow = useUTC ? candidate.getUTCDay() : candidate.getDay();
2370
- if (minutes.includes(m) && hours.includes(h) && daysOfMonth.includes(dom) && months.includes(mon) && (dowExpr === "*" || daysOfWeek.includes(dow))) {
2371
- return candidate;
2372
- }
2373
- candidate.setTime(candidate.getTime() + 6e4);
2374
- }
2375
- return new Date(now.getTime() + 864e5);
2378
+ throw new Error(`Invalid cron expression "${cronExpr}": ${errorMessage(err)}`);
2379
+ }
2376
2380
  }
2377
2381
  var Scheduler = class {
2378
2382
  timer = null;
@@ -2382,7 +2386,7 @@ var Scheduler = class {
2382
2386
  this.onScheduledTask = onScheduledTask;
2383
2387
  this.running = true;
2384
2388
  await this.initializeNextRuns();
2385
- this.timer = setInterval(() => this.checkDueTasks(), SCHEDULER_INTERVAL);
2389
+ this.timer = setInterval(() => this.checkDueTasks(), SCHEDULER_INTERVAL_MS);
2386
2390
  log.info("Scheduler started (checking every 30s)");
2387
2391
  }
2388
2392
  stop() {
@@ -2405,7 +2409,7 @@ var Scheduler = class {
2405
2409
  }
2406
2410
  }
2407
2411
  } catch (err) {
2408
- log.debug(`Scheduler init: ${err}`);
2412
+ log.debug(`Scheduler init: ${errorMessage(err)}`);
2409
2413
  }
2410
2414
  }
2411
2415
  async checkDueTasks() {
@@ -2429,7 +2433,7 @@ var Scheduler = class {
2429
2433
  last_error: null
2430
2434
  });
2431
2435
  } catch (err) {
2432
- const errMsg = err instanceof Error ? err.message : String(err);
2436
+ const errMsg = errorMessage(err);
2433
2437
  await callMcpHandler("schedule.update", {
2434
2438
  task_id: task.id,
2435
2439
  last_error: errMsg
@@ -2437,7 +2441,7 @@ var Scheduler = class {
2437
2441
  log.error(`Scheduled task "${task.name}" failed: ${errMsg}`);
2438
2442
  }
2439
2443
  } catch (err) {
2440
- log.debug(`Scheduler check error: ${err}`);
2444
+ log.debug(`Scheduler check error: ${errorMessage(err)}`);
2441
2445
  }
2442
2446
  }
2443
2447
  };
@@ -2808,7 +2812,92 @@ var MemoryManager = class {
2808
2812
  });
2809
2813
  return result.count;
2810
2814
  }
2815
+ // ── Compression & Deduplication ──────────────────────────────────
2816
+ /**
2817
+ * Check if memory count exceeds threshold and compress if needed.
2818
+ * Called automatically after task completion.
2819
+ */
2820
+ async compressIfNeeded() {
2821
+ try {
2822
+ const all = await this.list(void 0, 200);
2823
+ if (all.length < MEMORY_COMPRESSION_THRESHOLD) {
2824
+ return 0;
2825
+ }
2826
+ log.info(`Memory compression triggered: ${all.length} memories (threshold: ${MEMORY_COMPRESSION_THRESHOLD})`);
2827
+ let removed = 0;
2828
+ const now = Date.now();
2829
+ for (const m of all) {
2830
+ if (m.expires_at && new Date(m.expires_at).getTime() < now) {
2831
+ await this.remove(m.id);
2832
+ removed++;
2833
+ }
2834
+ }
2835
+ const remaining = all.filter(
2836
+ (m) => !m.expires_at || new Date(m.expires_at).getTime() >= now
2837
+ );
2838
+ const duplicateIds = this.findDuplicates(remaining);
2839
+ for (const id of duplicateIds) {
2840
+ await this.remove(id);
2841
+ removed++;
2842
+ }
2843
+ const afterDedup = remaining.filter((m) => !duplicateIds.has(m.id));
2844
+ if (afterDedup.length > MEMORY_COMPRESSION_TARGET) {
2845
+ const toRemove = afterDedup.sort((a, b) => {
2846
+ if (a.importance !== b.importance) return a.importance - b.importance;
2847
+ if (a.access_count !== b.access_count) return a.access_count - b.access_count;
2848
+ return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
2849
+ }).slice(0, afterDedup.length - MEMORY_COMPRESSION_TARGET);
2850
+ for (const m of toRemove) {
2851
+ if (m.category === "instruction" && m.importance >= 8) continue;
2852
+ await this.remove(m.id);
2853
+ removed++;
2854
+ }
2855
+ }
2856
+ if (removed > 0) {
2857
+ log.info(`Memory compression complete: removed ${removed} memories`);
2858
+ }
2859
+ return removed;
2860
+ } catch (err) {
2861
+ log.warn(`Memory compression error: ${err instanceof Error ? err.message : err}`);
2862
+ return 0;
2863
+ }
2864
+ }
2865
+ /**
2866
+ * Find duplicate memories based on content similarity.
2867
+ * Returns the IDs of memories that should be removed (keeps the higher-importance duplicate).
2868
+ */
2869
+ findDuplicates(memories) {
2870
+ const toRemove = /* @__PURE__ */ new Set();
2871
+ for (let i = 0; i < memories.length; i++) {
2872
+ if (toRemove.has(memories[i].id)) continue;
2873
+ for (let j = i + 1; j < memories.length; j++) {
2874
+ if (toRemove.has(memories[j].id)) continue;
2875
+ if (memories[i].category !== memories[j].category) continue;
2876
+ const similarity = computeWordOverlap(memories[i].content, memories[j].content);
2877
+ if (similarity >= MEMORY_DEDUP_SIMILARITY_THRESHOLD) {
2878
+ if (memories[i].importance > memories[j].importance || memories[i].importance === memories[j].importance && new Date(memories[i].created_at) > new Date(memories[j].created_at)) {
2879
+ toRemove.add(memories[j].id);
2880
+ } else {
2881
+ toRemove.add(memories[i].id);
2882
+ }
2883
+ }
2884
+ }
2885
+ }
2886
+ return toRemove;
2887
+ }
2811
2888
  };
2889
+ function computeWordOverlap(a, b) {
2890
+ const wordsA = new Set(a.toLowerCase().split(/\s+/).filter(Boolean));
2891
+ const wordsB = new Set(b.toLowerCase().split(/\s+/).filter(Boolean));
2892
+ if (wordsA.size === 0 && wordsB.size === 0) return 1;
2893
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
2894
+ let intersection = 0;
2895
+ for (const w of wordsA) {
2896
+ if (wordsB.has(w)) intersection++;
2897
+ }
2898
+ const union = wordsA.size + wordsB.size - intersection;
2899
+ return union === 0 ? 0 : intersection / union;
2900
+ }
2812
2901
 
2813
2902
  // src/agent/skills.ts
2814
2903
  import { execSync as execSync2 } from "child_process";
@@ -2957,7 +3046,7 @@ var SkillManager = class {
2957
3046
  userId = null;
2958
3047
  /** Cache for findRelevant() — keyed by prompt, invalidated on skill changes */
2959
3048
  relevanceCache = /* @__PURE__ */ new Map();
2960
- DESCRIPTION_BUDGET_CHARS = 16e3;
3049
+ DESCRIPTION_BUDGET_CHARS = SKILL_DESCRIPTION_BUDGET_CHARS;
2961
3050
  setUserId(userId) {
2962
3051
  this.userId = userId;
2963
3052
  }
@@ -2966,7 +3055,9 @@ var SkillManager = class {
2966
3055
  try {
2967
3056
  const data = await callMcpHandler("skill.load");
2968
3057
  this.skills.clear();
2969
- for (const row of data || []) {
3058
+ for (const raw of data || []) {
3059
+ const row = safeParse(SkillRowSchema, raw);
3060
+ if (!row) continue;
2970
3061
  const skill = this.rowToSkill(row);
2971
3062
  this.skills.set(skill.name, skill);
2972
3063
  }
@@ -2980,22 +3071,22 @@ var SkillManager = class {
2980
3071
  }
2981
3072
  rowToSkill(row) {
2982
3073
  return {
2983
- name: row.name,
2984
- description: row.description || "",
2985
- version: row.version || "1.0.0",
3074
+ name: String(row.name),
3075
+ description: String(row.description ?? ""),
3076
+ version: String(row.version ?? "1.0.0"),
2986
3077
  userInvocable: row.user_invocable !== false,
2987
3078
  disableModelInvocation: row.disable_model_invocation === true,
2988
- keywords: row.keywords || [],
2989
- allowedTools: row.allowed_tools || [],
2990
- argumentHint: row.argument_hint || "",
3079
+ keywords: Array.isArray(row.keywords) ? row.keywords : [],
3080
+ allowedTools: Array.isArray(row.allowed_tools) ? row.allowed_tools : [],
3081
+ argumentHint: String(row.argument_hint ?? ""),
2991
3082
  metadata: parseDbMetadata(row.metadata),
2992
- homepage: row.homepage || "",
2993
- content: row.content,
3083
+ homepage: String(row.homepage ?? ""),
3084
+ content: String(row.content ?? ""),
2994
3085
  filePath: "",
2995
3086
  source: row.source || "manual",
2996
- dbId: row.id,
2997
- sourceSkillId: row.source_skill_id || void 0,
2998
- invocationCount: row.invocation_count || 0
3087
+ dbId: row.id != null ? String(row.id) : void 0,
3088
+ sourceSkillId: row.source_skill_id != null ? String(row.source_skill_id) : void 0,
3089
+ invocationCount: typeof row.invocation_count === "number" ? row.invocation_count : 0
2999
3090
  };
3000
3091
  }
3001
3092
  /** Invalidate caches when skills change (create, add, update, remove). */
@@ -3125,13 +3216,14 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
3125
3216
  metadata
3126
3217
  }
3127
3218
  );
3128
- const row = Array.isArray(data) ? data[0] : data;
3219
+ const raw = Array.isArray(data) ? data[0] : data;
3220
+ const row = safeParse(SkillCreateResultSchema, raw);
3129
3221
  if (!row) {
3130
- log.debug(`Skill create returned no data for "${name}"`);
3222
+ log.debug(`Skill create returned invalid data for "${name}"`);
3131
3223
  return null;
3132
3224
  }
3133
3225
  const id = row.out_id || row.id;
3134
- const skillName = row.out_name || row.name;
3226
+ const skillName = row.out_name || row.name || name;
3135
3227
  this.skills.set(skillName, {
3136
3228
  name: skillName,
3137
3229
  description,
@@ -3301,11 +3393,11 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
3301
3393
  });
3302
3394
  if (data) {
3303
3395
  return data.map((row) => ({
3304
- name: row.name,
3305
- description: row.description || "",
3306
- emoji: row.emoji || "",
3307
- source: row.source || "manual",
3308
- invocationCount: row.invocation_count || 0
3396
+ name: String(row.name),
3397
+ description: String(row.description ?? ""),
3398
+ emoji: String(row.emoji ?? ""),
3399
+ source: String(row.source ?? "manual"),
3400
+ invocationCount: typeof row.invocation_count === "number" ? row.invocation_count : 0
3309
3401
  }));
3310
3402
  }
3311
3403
  } catch {
@@ -3368,17 +3460,17 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
3368
3460
  limit: options?.limit || 20,
3369
3461
  offset: options?.offset || 0
3370
3462
  });
3371
- return (data || []).map((r) => ({
3463
+ return (data || []).map((r) => safeParse(BrowseSkillRowSchema, r)).filter(Boolean).map((r) => ({
3372
3464
  id: r.id,
3373
3465
  name: r.name,
3374
- description: r.description || "",
3375
- emoji: r.emoji || "",
3376
- version: r.version || "1.0.0",
3377
- authorName: r.author_name || "",
3378
- category: r.category || "",
3379
- installCount: r.install_count || 0,
3380
- avgRating: r.avg_rating != null ? r.avg_rating : null,
3381
- ratingCount: r.rating_count || 0
3466
+ description: r.description,
3467
+ emoji: r.emoji,
3468
+ version: r.version,
3469
+ authorName: r.author_name,
3470
+ category: r.category,
3471
+ installCount: r.install_count,
3472
+ avgRating: r.avg_rating ?? null,
3473
+ ratingCount: r.rating_count
3382
3474
  }));
3383
3475
  } catch {
3384
3476
  return [];
@@ -3403,8 +3495,12 @@ function substituteArguments(content, args) {
3403
3495
  content = content.replace(/\$(\d+)(?!\w)/g, (_, i) => parts[parseInt(i)] || "");
3404
3496
  return content;
3405
3497
  }
3498
+ var SAFE_DYNAMIC_COMMANDS = /^(date|whoami|hostname|uname|pwd|echo|node\s+--version|npm\s+--version|git\s+(branch|rev-parse|log\s+--oneline)|cat\s+)/;
3406
3499
  function preprocessDynamicContext(content, cwd) {
3407
3500
  return content.replace(/!`([^`]+)`/g, (_, cmd) => {
3501
+ if (!SAFE_DYNAMIC_COMMANDS.test(cmd.trim())) {
3502
+ return `[command blocked: ${cmd}]`;
3503
+ }
3408
3504
  try {
3409
3505
  return execSync2(cmd, { timeout: 1e4, encoding: "utf-8", cwd }).trim();
3410
3506
  } catch {
@@ -3417,6 +3513,36 @@ function preprocessDynamicContext(content, cwd) {
3417
3513
  import {
3418
3514
  query
3419
3515
  } from "@anthropic-ai/claude-agent-sdk";
3516
+ var SKILL_DECISION_OUTPUT_FORMAT = {
3517
+ type: "json_schema",
3518
+ schema: {
3519
+ type: "object",
3520
+ properties: {
3521
+ action: { type: "string", enum: ["create", "update", "skip"] },
3522
+ name: { type: "string" },
3523
+ description: { type: "string" },
3524
+ instructions: { type: "string" },
3525
+ emoji: { type: "string" },
3526
+ keywords: { type: "array", items: { type: "string" } },
3527
+ existing_skill_name: { type: "string" },
3528
+ improved_instructions: { type: "string" },
3529
+ improved_description: { type: "string" },
3530
+ reason: { type: "string" }
3531
+ },
3532
+ required: ["action", "reason"]
3533
+ }
3534
+ };
3535
+ var SKILL_VALIDATION_OUTPUT_FORMAT = {
3536
+ type: "json_schema",
3537
+ schema: {
3538
+ type: "object",
3539
+ properties: {
3540
+ valid: { type: "boolean" },
3541
+ improvements: { type: "string" }
3542
+ },
3543
+ required: ["valid"]
3544
+ }
3545
+ };
3420
3546
  var SKILL_EVALUATION_PROMPT = `You just completed a task. Now evaluate whether it should be saved as a reusable Agent Skill.
3421
3547
 
3422
3548
  ## Agent Skills Format (agentskills.io)
@@ -3444,6 +3570,22 @@ Respond with ONLY a JSON object (no markdown, no explanation outside the JSON).
3444
3570
  Always include "reason" explaining your decision.
3445
3571
 
3446
3572
  Use your judgment \u2014 no rigid rules. Consider: Is this repeatable? Can it be generalized? Would it save time next time?`;
3573
+ var SKILL_VALIDATION_PROMPT = `Validate this auto-generated skill before it becomes active.
3574
+
3575
+ Check:
3576
+ 1. Are the instructions clear, complete, and actionable?
3577
+ 2. Do they use generic placeholders (not hardcoded values)?
3578
+ 3. Are error handling steps included?
3579
+ 4. Is the description accurate and searchable?
3580
+ 5. Would this actually work if followed step-by-step?
3581
+
3582
+ Respond with ONLY a JSON object:
3583
+ - {"valid": true, "improvements": null}
3584
+ - {"valid": false, "improvements": "Specific improvements needed"}
3585
+ - {"valid": true, "improvements": "Optional minor improvements"}
3586
+
3587
+ Skill to validate:
3588
+ `;
3447
3589
  async function evaluateAndMaybeCreateSkill(opts) {
3448
3590
  const { sessionId, skillManager, model } = opts;
3449
3591
  if (!sessionId) {
@@ -3459,58 +3601,93 @@ ${existingList}
3459
3601
 
3460
3602
  Respond with a JSON object now.`;
3461
3603
  try {
3462
- let responseText = "";
3604
+ let structuredOutput;
3463
3605
  for await (const message of query({
3464
3606
  prompt,
3465
3607
  options: {
3466
3608
  resume: sessionId,
3467
3609
  model,
3468
3610
  maxTurns: 1,
3469
- allowedTools: []
3611
+ allowedTools: [],
3612
+ effort: "low",
3613
+ outputFormat: SKILL_DECISION_OUTPUT_FORMAT
3470
3614
  }
3471
3615
  })) {
3472
- if (message.type === "assistant") {
3473
- const assistantMsg = message;
3474
- for (const block of assistantMsg.message.content) {
3475
- if (block.type === "text") {
3476
- responseText += block.text;
3477
- }
3478
- }
3479
- } else if (message.type === "result") {
3616
+ if (message.type === "result") {
3480
3617
  const resultMsg = message;
3481
- if (resultMsg.subtype === "success" && "total_cost_usd" in resultMsg) {
3482
- log.debug(`Skill evaluation cost: $${resultMsg.total_cost_usd.toFixed(4)}`);
3618
+ if (resultMsg.subtype === "success") {
3619
+ const successMsg = resultMsg;
3620
+ structuredOutput = successMsg.structured_output;
3621
+ log.debug(
3622
+ `Skill evaluation cost: $${successMsg.total_cost_usd.toFixed(4)}`
3623
+ );
3483
3624
  }
3484
3625
  }
3485
3626
  }
3486
- const decision = parseJsonResponse(responseText);
3627
+ const decision = structuredOutput ? safeParse(SkillDecisionSchema, structuredOutput) : null;
3487
3628
  if (!decision) {
3488
3629
  log.debug("Skill evaluation: no valid JSON in response");
3489
3630
  return;
3490
3631
  }
3491
- if (!["create", "update", "skip"].includes(decision.action)) {
3492
- log.debug("Skill evaluation: invalid action");
3493
- return;
3632
+ await executeSkillDecision(decision, skillManager, sessionId, model);
3633
+ } catch (err) {
3634
+ log.debug(`Skill evaluation error: ${errorMessage(err)}`);
3635
+ }
3636
+ }
3637
+ async function validateSkill(name, description, instructions, sessionId, model) {
3638
+ try {
3639
+ const skillDoc = `Name: ${name}
3640
+ Description: ${description}
3641
+
3642
+ Instructions:
3643
+ ${instructions}`;
3644
+ let structuredOutput;
3645
+ for await (const message of query({
3646
+ prompt: SKILL_VALIDATION_PROMPT + skillDoc,
3647
+ options: {
3648
+ resume: sessionId,
3649
+ model,
3650
+ maxTurns: SKILL_VALIDATION_MAX_TURNS,
3651
+ allowedTools: [],
3652
+ effort: "low",
3653
+ outputFormat: SKILL_VALIDATION_OUTPUT_FORMAT
3654
+ }
3655
+ })) {
3656
+ if (message.type === "result") {
3657
+ const resultMsg = message;
3658
+ if (resultMsg.subtype === "success") {
3659
+ structuredOutput = resultMsg.structured_output;
3660
+ }
3661
+ }
3662
+ }
3663
+ const parsed = structuredOutput;
3664
+ if (parsed) {
3665
+ return { valid: parsed.valid, improvements: parsed.improvements || void 0 };
3494
3666
  }
3495
- await executeSkillDecision(decision, skillManager);
3667
+ return { valid: true };
3496
3668
  } catch (err) {
3497
- log.debug(`Skill evaluation error: ${err}`);
3669
+ log.debug(`Skill validation error: ${errorMessage(err)}`);
3670
+ return { valid: true };
3498
3671
  }
3499
3672
  }
3500
- async function executeSkillDecision(decision, skillManager) {
3673
+ async function executeSkillDecision(decision, skillManager, sessionId, model) {
3501
3674
  switch (decision.action) {
3502
3675
  case "create": {
3503
3676
  if (!decision.name || !decision.instructions) {
3504
3677
  log.debug("Skill create skipped: missing name or instructions");
3505
3678
  return;
3506
3679
  }
3507
- let skillName = decision.name;
3508
- if (validateSkillName(skillName)) {
3509
- skillName = normalizeSkillName(skillName);
3510
- if (!skillName || validateSkillName(skillName)) {
3511
- log.debug(`Skill create skipped: name "${decision.name}" cannot be normalized`);
3512
- return;
3513
- }
3680
+ const skillName = normalizeSkillName(decision.name);
3681
+ if (!skillName) {
3682
+ log.debug(`Skill create skipped: name "${decision.name}" cannot be normalized`);
3683
+ return;
3684
+ }
3685
+ const validationError = validateSkillName(skillName);
3686
+ if (validationError) {
3687
+ log.debug(`Skill create skipped: ${validationError}`);
3688
+ return;
3689
+ }
3690
+ if (skillName !== decision.name) {
3514
3691
  log.debug(`Normalized skill name: "${decision.name}" \u2192 "${skillName}"`);
3515
3692
  }
3516
3693
  const existing = skillManager.findSimilar(skillName);
@@ -3518,10 +3695,33 @@ async function executeSkillDecision(decision, skillManager) {
3518
3695
  log.debug(`Skill create skipped: similar skill "${existing.name}" exists`);
3519
3696
  return;
3520
3697
  }
3698
+ let instructions = decision.instructions;
3699
+ if (sessionId) {
3700
+ log.debug(`Validating skill "${skillName}" before activation...`);
3701
+ const validation = await validateSkill(
3702
+ skillName,
3703
+ decision.description || "",
3704
+ instructions,
3705
+ sessionId,
3706
+ model
3707
+ );
3708
+ if (!validation.valid) {
3709
+ log.info(
3710
+ `Skill "${skillName}" failed validation: ${validation.improvements}. Skipping creation.`
3711
+ );
3712
+ return;
3713
+ }
3714
+ if (validation.improvements) {
3715
+ log.debug(`Skill "${skillName}" validated with suggestions: ${validation.improvements}`);
3716
+ instructions += `
3717
+
3718
+ <!-- Validation notes: ${validation.improvements} -->`;
3719
+ }
3720
+ }
3521
3721
  const result = await skillManager.create(
3522
3722
  skillName,
3523
3723
  decision.description || "",
3524
- decision.instructions,
3724
+ instructions,
3525
3725
  {
3526
3726
  source: "auto_extracted",
3527
3727
  emoji: decision.emoji,
@@ -3532,7 +3732,7 @@ async function executeSkillDecision(decision, skillManager) {
3532
3732
  await skillManager.syncToAgentSkills(
3533
3733
  skillName,
3534
3734
  decision.description || "",
3535
- decision.instructions,
3735
+ instructions,
3536
3736
  "1.0.0",
3537
3737
  {
3538
3738
  source: "auto_extracted",
@@ -3541,7 +3741,7 @@ async function executeSkillDecision(decision, skillManager) {
3541
3741
  sourceSkillId: result.id
3542
3742
  }
3543
3743
  );
3544
- log.info(`Auto-created skill "${skillName}": ${decision.reason}`);
3744
+ log.info(`Auto-created skill "${skillName}" (validated): ${decision.reason}`);
3545
3745
  }
3546
3746
  break;
3547
3747
  }
@@ -3550,6 +3750,21 @@ async function executeSkillDecision(decision, skillManager) {
3550
3750
  log.debug("Skill update skipped: missing skill name or instructions");
3551
3751
  return;
3552
3752
  }
3753
+ if (sessionId) {
3754
+ const validation = await validateSkill(
3755
+ decision.existing_skill_name,
3756
+ decision.improved_description || "",
3757
+ decision.improved_instructions,
3758
+ sessionId,
3759
+ model
3760
+ );
3761
+ if (!validation.valid) {
3762
+ log.info(
3763
+ `Skill update for "${decision.existing_skill_name}" failed validation. Skipping.`
3764
+ );
3765
+ return;
3766
+ }
3767
+ }
3553
3768
  const updated = skillManager.update(
3554
3769
  decision.existing_skill_name,
3555
3770
  decision.improved_instructions,
@@ -3567,29 +3782,6 @@ async function executeSkillDecision(decision, skillManager) {
3567
3782
  break;
3568
3783
  }
3569
3784
  }
3570
- function parseJsonResponse(text) {
3571
- const trimmed = text.trim();
3572
- try {
3573
- const parsed = JSON.parse(trimmed);
3574
- if (parsed.action) return parsed;
3575
- } catch {
3576
- }
3577
- const start = trimmed.indexOf("{");
3578
- if (start === -1) return null;
3579
- let depth = 0;
3580
- for (let i = start; i < trimmed.length; i++) {
3581
- if (trimmed[i] === "{") depth++;
3582
- else if (trimmed[i] === "}") depth--;
3583
- if (depth === 0) {
3584
- try {
3585
- return JSON.parse(trimmed.slice(start, i + 1));
3586
- } catch {
3587
- return null;
3588
- }
3589
- }
3590
- }
3591
- return null;
3592
- }
3593
3785
 
3594
3786
  // src/utils/retry.ts
3595
3787
  async function withRetry(fn, opts = {}) {
@@ -3634,14 +3826,16 @@ import { z } from "zod/v4";
3634
3826
 
3635
3827
  // src/tools/filesystem.ts
3636
3828
  import { readFile, writeFile, readdir, stat, mkdir } from "fs/promises";
3637
- import { resolve, relative, join as join2 } from "path";
3829
+ import { resolve, relative, join as join2, sep } from "path";
3638
3830
  import { glob } from "glob";
3639
3831
  function assertWithinWorkspace(filePath) {
3640
3832
  const config = getConfig();
3641
3833
  const resolved = resolve(config.workspacePath, filePath);
3642
- if (!resolved.startsWith(config.workspacePath)) {
3643
- throw new Error(
3644
- `Access denied: path "${filePath}" is outside workspace "${config.workspacePath}"`
3834
+ const rel = relative(config.workspacePath, resolved);
3835
+ if (rel.startsWith("..") || rel.startsWith(sep + sep)) {
3836
+ throw new AppError(
3837
+ `Access denied: path "${filePath}" is outside workspace "${config.workspacePath}"`,
3838
+ "PATH_TRAVERSAL"
3645
3839
  );
3646
3840
  }
3647
3841
  return resolved;
@@ -3671,7 +3865,7 @@ async function searchFiles(pattern, directory) {
3671
3865
  ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
3672
3866
  });
3673
3867
  if (matches.length === 0) return "No files found matching the pattern.";
3674
- return matches.slice(0, 50).map((m) => relative(config.workspacePath, join2(cwd, m))).join("\n");
3868
+ return matches.slice(0, MAX_FILE_SEARCH_RESULTS).map((m) => relative(config.workspacePath, join2(cwd, m))).join("\n");
3675
3869
  }
3676
3870
  async function listDirectory(path) {
3677
3871
  const config = getConfig();
@@ -3681,9 +3875,7 @@ async function listDirectory(path) {
3681
3875
  for (const entry of entries) {
3682
3876
  if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
3683
3877
  const icon = entry.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}";
3684
- const info = entry.isFile() ? await stat(join2(resolved, entry.name)).then(
3685
- (s) => ` (${formatSize(s.size)})`
3686
- ) : "";
3878
+ const info = entry.isFile() ? await stat(join2(resolved, entry.name)).then((s) => ` (${formatSize(s.size)})`) : "";
3687
3879
  results.push(`${icon} ${entry.name}${info}`);
3688
3880
  }
3689
3881
  return results.join("\n") || "Empty directory.";
@@ -3701,9 +3893,14 @@ async function searchContent(pattern, fileGlob, directory) {
3701
3893
  nodir: true,
3702
3894
  ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
3703
3895
  });
3704
- const regex = new RegExp(pattern, "gi");
3896
+ let regex;
3897
+ try {
3898
+ regex = new RegExp(pattern, "gi");
3899
+ } catch {
3900
+ regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
3901
+ }
3705
3902
  const results = [];
3706
- for (const file of files.slice(0, 200)) {
3903
+ for (const file of files.slice(0, MAX_CONTENT_SEARCH_FILES)) {
3707
3904
  try {
3708
3905
  const content = await readFile(join2(cwd, file), "utf-8");
3709
3906
  const lines = content.split("\n");
@@ -3712,10 +3909,10 @@ async function searchContent(pattern, fileGlob, directory) {
3712
3909
  const relPath = relative(config.workspacePath, join2(cwd, file));
3713
3910
  results.push(`${relPath}:${i + 1}: ${lines[i].trim()}`);
3714
3911
  regex.lastIndex = 0;
3715
- if (results.length >= 30) break;
3912
+ if (results.length >= MAX_CONTENT_SEARCH_RESULTS) break;
3716
3913
  }
3717
3914
  }
3718
- if (results.length >= 30) break;
3915
+ if (results.length >= MAX_CONTENT_SEARCH_RESULTS) break;
3719
3916
  } catch {
3720
3917
  }
3721
3918
  }
@@ -3724,8 +3921,6 @@ async function searchContent(pattern, fileGlob, directory) {
3724
3921
 
3725
3922
  // src/tools/shell.ts
3726
3923
  import { exec } from "child_process";
3727
- var TIMEOUT_MS = 3e4;
3728
- var MAX_OUTPUT = 5e4;
3729
3924
  var BLOCKED_PATTERNS = [
3730
3925
  /rm\s+(-\w*\s+)*-\w*r\w*\s+\/($|\s)/i,
3731
3926
  // rm -rf /, rm -fr /, etc.
@@ -3753,7 +3948,7 @@ function isBlocked(command) {
3753
3948
  }
3754
3949
  async function executeShell(command, cwd) {
3755
3950
  if (isBlocked(command)) {
3756
- throw new Error(`Command blocked for safety: "${command}"`);
3951
+ throw new AppError(`Command blocked for safety: "${command}"`, "COMMAND_BLOCKED");
3757
3952
  }
3758
3953
  const config = getConfig();
3759
3954
  const workDir = cwd || config.workspacePath;
@@ -3762,7 +3957,7 @@ async function executeShell(command, cwd) {
3762
3957
  command,
3763
3958
  {
3764
3959
  cwd: workDir,
3765
- timeout: TIMEOUT_MS,
3960
+ timeout: SHELL_TIMEOUT_MS,
3766
3961
  maxBuffer: 1024 * 1024,
3767
3962
  // 1MB buffer
3768
3963
  env: { ...process.env, TERM: "dumb" }
@@ -3780,10 +3975,10 @@ ${stderr}` : "";
3780
3975
  if (error && !stdout && !stderr) {
3781
3976
  output = `Error: ${error.message}`;
3782
3977
  }
3783
- if (output.length > MAX_OUTPUT) {
3784
- output = output.slice(0, MAX_OUTPUT) + `
3978
+ if (output.length > SHELL_MAX_OUTPUT) {
3979
+ output = output.slice(0, SHELL_MAX_OUTPUT) + `
3785
3980
 
3786
- [Output truncated at ${MAX_OUTPUT} bytes]`;
3981
+ [Output truncated at ${SHELL_MAX_OUTPUT} bytes]`;
3787
3982
  }
3788
3983
  resolve2(output || "(no output)");
3789
3984
  }
@@ -5436,14 +5631,14 @@ function stripMcpPrefix(toolName) {
5436
5631
  const match = toolName.match(/^mcp__[^_]+(?:__)?(.+)$/);
5437
5632
  return match ? match[1] : toolName;
5438
5633
  }
5439
- function createEventHooks(taskId, toolCallRecords) {
5634
+ function createEventHooks(taskId, toolCallRecords, toolFailures = []) {
5440
5635
  const preToolUseHook = async (input) => {
5441
5636
  if (input.hook_event_name !== "PreToolUse") return { continue: true };
5442
5637
  const preInput = input;
5443
5638
  const rawName = preInput.tool_name;
5444
5639
  const displayName = stripMcpPrefix(rawName);
5445
5640
  const toolInput = preInput.tool_input;
5446
- log.tool(displayName, JSON.stringify(toolInput).slice(0, 200));
5641
+ log.tool(displayName, JSON.stringify(toolInput).slice(0, MAX_TOOL_INPUT_LOG_LENGTH));
5447
5642
  await emitEvent(taskId, "tool_use_start", { name: displayName });
5448
5643
  await emitEvent(taskId, "tool_use_input", { input: toolInput });
5449
5644
  if (displayName === "browser_request_user_action") {
@@ -5462,21 +5657,42 @@ function createEventHooks(taskId, toolCallRecords) {
5462
5657
  const toolInput = postInput.tool_input;
5463
5658
  const toolResponse = postInput.tool_response;
5464
5659
  const resultStr = typeof toolResponse === "string" ? toolResponse : JSON.stringify(toolResponse);
5465
- log.result(resultStr.slice(0, 200));
5660
+ log.result(resultStr.slice(0, MAX_TOOL_INPUT_LOG_LENGTH));
5466
5661
  await emitEvent(taskId, "tool_result", {
5467
5662
  name: displayName,
5468
- result: resultStr.slice(0, 1e4)
5663
+ result: resultStr.slice(0, MAX_TOOL_RESULT_LENGTH)
5469
5664
  });
5470
5665
  toolCallRecords.push({
5471
5666
  name: displayName,
5472
5667
  input: toolInput || {},
5473
- result: resultStr.slice(0, 300)
5668
+ result: resultStr.slice(0, MAX_SKILL_RECORD_RESULT_LENGTH)
5474
5669
  });
5475
5670
  return {};
5476
5671
  };
5672
+ const postToolUseFailureHook = async (input) => {
5673
+ if (input.hook_event_name !== "PostToolUseFailure") return {};
5674
+ const failureInput = input;
5675
+ const rawName = failureInput.tool_name;
5676
+ const displayName = stripMcpPrefix(rawName);
5677
+ const errorStr = failureInput.error;
5678
+ toolFailures.push({
5679
+ toolName: displayName,
5680
+ input: failureInput.tool_input || {},
5681
+ error: errorStr.slice(0, 500),
5682
+ timestamp: Date.now()
5683
+ });
5684
+ await emitEvent(taskId, "tool_failure", {
5685
+ name: displayName,
5686
+ error: errorStr.slice(0, 500),
5687
+ failure_count: toolFailures.filter((f) => f.toolName === displayName).length
5688
+ });
5689
+ log.warn(`Tool failure tracked: ${displayName} (total: ${toolFailures.length})`);
5690
+ return {};
5691
+ };
5477
5692
  return {
5478
5693
  PreToolUse: [{ hooks: [preToolUseHook] }],
5479
- PostToolUse: [{ hooks: [postToolUseHook] }]
5694
+ PostToolUse: [{ hooks: [postToolUseHook] }],
5695
+ PostToolUseFailure: [{ hooks: [postToolUseFailureHook] }]
5480
5696
  };
5481
5697
  }
5482
5698
 
@@ -5588,7 +5804,16 @@ Workflow for form filling (e.g. "\u6CE8\u518C\u4E00\u4E2A Gmail \u8D26\u53F7"):
5588
5804
  4. Check the screenshot \u2014 if validation errors appear, re-snapshot and fix
5589
5805
  5. When a username/email is taken, append a random 4-digit suffix and retry
5590
5806
 
5807
+ 7. FAILURE RECOVERY \u2014 Strategy Switching:
5808
+ If a tool call fails, do NOT repeat the same call. Reflect on why it failed and switch strategy:
5809
+ - CSS selector fails \u2192 use browser_snapshot refs instead
5810
+ - Direct navigation fails \u2192 search for the page first
5811
+ - API/programmatic approach fails \u2192 use browser UI instead
5812
+ - One data source fails \u2192 try an alternative source
5813
+ - If stuck after 2 failed attempts at the same step, try a fundamentally different approach
5814
+
5591
5815
  Guidelines:
5816
+ - SELF-VERIFY before finishing: re-read modified files, take a final screenshot after browser actions, or re-check output to confirm correctness. Never assume success without confirming the end state.
5592
5817
  - Always use the real browser for web tasks, never try to fetch URLs programmatically
5593
5818
  - ALWAYS use browser_snapshot as your primary way to understand a page \u2014 the ref table gives actionable refs, the screenshot gives visual context
5594
5819
  - Use browser_act to batch multiple actions \u2014 fill an entire form in one call instead of individual clicks/types
@@ -5649,18 +5874,22 @@ var TaskTimeout = class {
5649
5874
  }
5650
5875
  }
5651
5876
  };
5652
- var MAX_HISTORY_ENTRIES = 10;
5653
- var MAX_RESPONSE_LENGTH = 1500;
5654
5877
  var TaskProcessor = class {
5655
5878
  memoryManager = null;
5656
5879
  skillManager;
5657
5880
  sessionId = null;
5881
+ userId = null;
5658
5882
  /** In-memory conversation history, keyed by conversation_id */
5659
5883
  historyCache = /* @__PURE__ */ new Map();
5660
5884
  constructor() {
5661
5885
  this.skillManager = new SkillManager();
5662
5886
  }
5887
+ /** @deprecated Use setUserId() instead */
5663
5888
  init(userId) {
5889
+ this.setUserId(userId);
5890
+ }
5891
+ setUserId(userId) {
5892
+ this.userId = userId;
5664
5893
  this.memoryManager = new MemoryManager();
5665
5894
  this.skillManager.setUserId(userId);
5666
5895
  this.skillManager.loadFromDb().catch((err) => {
@@ -5685,11 +5914,12 @@ var TaskProcessor = class {
5685
5914
  async processTask(task) {
5686
5915
  const config = getConfig();
5687
5916
  resetEventSequence();
5688
- const taskTimeoutMs = (config.taskTimeoutMinutes || 10) * 6e4;
5917
+ const taskTimeoutMs = config.taskTimeoutMinutes * 6e4;
5689
5918
  newCorrelationId();
5690
5919
  log.info(`Processing task ${task.id.slice(0, 8)}...`);
5691
5920
  let finalResponse = "";
5692
5921
  const toolCallRecords = [];
5922
+ const toolFailures = [];
5693
5923
  let tokenUsage;
5694
5924
  let agentSessionId;
5695
5925
  try {
@@ -5724,7 +5954,7 @@ var TaskProcessor = class {
5724
5954
  for (const entry of history) {
5725
5955
  historyPrompt += `User: ${entry.prompt}
5726
5956
  `;
5727
- const truncated = entry.response.length > MAX_RESPONSE_LENGTH ? entry.response.slice(0, MAX_RESPONSE_LENGTH) + "\u2026" : entry.response;
5957
+ const truncated = entry.response.length > MAX_HISTORY_RESPONSE_LENGTH ? entry.response.slice(0, MAX_HISTORY_RESPONSE_LENGTH) + "\u2026" : entry.response;
5728
5958
  historyPrompt += `Assistant: ${truncated}
5729
5959
 
5730
5960
  `;
@@ -5742,18 +5972,15 @@ var TaskProcessor = class {
5742
5972
  onUserWaitStart: () => taskTimeout.pause(),
5743
5973
  onUserWaitEnd: () => taskTimeout.resume()
5744
5974
  });
5745
- const eventHooks = createEventHooks(task.id, toolCallRecords);
5975
+ const eventHooks = createEventHooks(task.id, toolCallRecords, toolFailures);
5746
5976
  const allowedTools = [
5747
- // SDK built-in tools
5748
5977
  "Read",
5749
5978
  "Write",
5750
5979
  "Edit",
5751
5980
  "Bash",
5752
5981
  "Glob",
5753
5982
  "Grep",
5754
- // Browser MCP tools
5755
5983
  ...BROWSER_TOOL_NAMES.map((n) => `mcp__assistme-browser__${n}`),
5756
- // Agent MCP tools (memory, skills)
5757
5984
  "mcp__assistme-agent__memory_store",
5758
5985
  "mcp__assistme-agent__skill_create",
5759
5986
  "mcp__assistme-agent__skill_improve",
@@ -5764,29 +5991,19 @@ var TaskProcessor = class {
5764
5991
  "mcp__assistme-agent__skill_browse",
5765
5992
  "mcp__assistme-agent__skill_add",
5766
5993
  "mcp__assistme-agent__skill_publish",
5767
- // User interaction
5768
5994
  "mcp__assistme-agent__ask_user",
5769
- // Job automation tools
5770
5995
  "mcp__assistme-agent__job_run",
5771
5996
  "mcp__assistme-agent__job_schedule",
5772
5997
  "mcp__assistme-agent__job_status",
5773
- // Credential tools (local storage)
5774
5998
  "mcp__assistme-agent__credential_get",
5775
5999
  "mcp__assistme-agent__credential_set",
5776
6000
  "mcp__assistme-agent__credential_list",
5777
6001
  "mcp__assistme-agent__credential_remove"
5778
6002
  ];
5779
- async function* promptMessages() {
5780
- yield {
5781
- type: "user",
5782
- message: {
5783
- role: "user",
5784
- content: task.prompt
5785
- },
5786
- parent_tool_use_id: null,
5787
- session_id: ""
5788
- };
5789
- }
6003
+ const mcpServers = {
6004
+ "assistme-browser": browserServer,
6005
+ "assistme-agent": agentToolsServer
6006
+ };
5790
6007
  const options = {
5791
6008
  model: config.model,
5792
6009
  systemPrompt,
@@ -5795,24 +6012,16 @@ var TaskProcessor = class {
5795
6012
  allowedTools,
5796
6013
  permissionMode: "bypassPermissions",
5797
6014
  allowDangerouslySkipPermissions: true,
5798
- mcpServers: {
5799
- "assistme-browser": browserServer,
5800
- "assistme-agent": agentToolsServer
5801
- },
6015
+ mcpServers,
5802
6016
  hooks: eventHooks,
5803
6017
  persistSession: true,
5804
- abortController
6018
+ abortController,
6019
+ thinking: { type: "adaptive" },
6020
+ effort: "high",
6021
+ maxBudgetUsd: MAX_BUDGET_USD
5805
6022
  };
5806
- const taskStartTime = Date.now();
5807
6023
  try {
5808
- for await (const message of query2({
5809
- prompt: promptMessages(),
5810
- options
5811
- })) {
5812
- if (Date.now() - taskStartTime > taskTimeoutMs) {
5813
- finalResponse += "\n\n[Task timed out]";
5814
- break;
5815
- }
6024
+ for await (const message of query2({ prompt: task.prompt, options })) {
5816
6025
  switch (message.type) {
5817
6026
  case "assistant": {
5818
6027
  const assistantMsg = message;
@@ -5820,15 +6029,11 @@ var TaskProcessor = class {
5820
6029
  if (block.type === "text") {
5821
6030
  finalResponse += block.text;
5822
6031
  log.agent(block.text);
5823
- await emitEvent(task.id, "text_delta", {
5824
- text: block.text
5825
- });
6032
+ await emitEvent(task.id, "text_delta", { text: block.text });
5826
6033
  } else if (block.type === "thinking" && "thinking" in block) {
5827
- const thinkingText = block.thinking;
5828
- log.debug(`Thinking: ${thinkingText.slice(0, 100)}...`);
5829
- await emitEvent(task.id, "thinking", {
5830
- text: thinkingText
5831
- });
6034
+ const thinkingBlock = block;
6035
+ log.debug(`Thinking: ${thinkingBlock.thinking.slice(0, 100)}...`);
6036
+ await emitEvent(task.id, "thinking", { text: thinkingBlock.thinking });
5832
6037
  }
5833
6038
  }
5834
6039
  break;
@@ -5844,21 +6049,25 @@ var TaskProcessor = class {
5844
6049
  if (!finalResponse && successMsg.result) {
5845
6050
  finalResponse = successMsg.result;
5846
6051
  }
6052
+ agentSessionId = successMsg.session_id;
5847
6053
  log.info(
5848
6054
  `Task cost: $${successMsg.total_cost_usd.toFixed(4)}, turns: ${successMsg.num_turns}`
5849
6055
  );
5850
6056
  } else {
5851
- const errorMsg = resultMsg;
5852
- log.warn(`SDK result: ${errorMsg.subtype}`);
5853
- for (const err of errorMsg.errors) {
6057
+ const errMsg = resultMsg;
6058
+ log.warn(`SDK result: ${errMsg.subtype}`);
6059
+ for (const err of errMsg.errors) {
5854
6060
  await emitEvent(task.id, "error", { message: err });
5855
6061
  }
5856
6062
  }
5857
6063
  break;
5858
6064
  }
5859
6065
  default:
5860
- if (message.type === "system" && "subtype" in message && message.subtype === "init") {
5861
- agentSessionId = message.session_id;
6066
+ if (message.type === "system" && "subtype" in message) {
6067
+ const sysMsg = message;
6068
+ if (sysMsg.subtype === "init" && sysMsg.session_id) {
6069
+ agentSessionId = sysMsg.session_id;
6070
+ }
5862
6071
  }
5863
6072
  log.debug(`SDK message type: ${message.type}`);
5864
6073
  break;
@@ -5867,10 +6076,9 @@ var TaskProcessor = class {
5867
6076
  } finally {
5868
6077
  taskTimeout.clear();
5869
6078
  }
5870
- const MAX_CONTENT_LENGTH = 5e4;
5871
- const truncatedResponse = finalResponse.length > MAX_CONTENT_LENGTH ? finalResponse.slice(0, MAX_CONTENT_LENGTH) + "\n\n[Response truncated]" : finalResponse;
6079
+ const truncatedResponse = finalResponse.length > MAX_RESPONSE_CONTENT_LENGTH ? finalResponse.slice(0, MAX_RESPONSE_CONTENT_LENGTH) + "\n\n[Response truncated]" : finalResponse;
5872
6080
  await withRetry(() => completeTask(task.id, truncatedResponse, tokenUsage), {
5873
- maxRetries: 2,
6081
+ maxRetries: MAX_COMPLETE_TASK_RETRIES,
5874
6082
  baseDelayMs: 300,
5875
6083
  label: "completeTask"
5876
6084
  });
@@ -5882,16 +6090,21 @@ var TaskProcessor = class {
5882
6090
  convHistory.splice(0, convHistory.length - MAX_HISTORY_ENTRIES * 2);
5883
6091
  }
5884
6092
  this.historyCache.set(task.conversation_id, convHistory);
6093
+ if (this.memoryManager) {
6094
+ this.memoryManager.compressIfNeeded().catch(
6095
+ (err) => log.debug(`Memory compression skipped: ${err}`)
6096
+ );
6097
+ }
5885
6098
  if (agentSessionId) {
5886
6099
  this.evaluateSkillPostTask(agentSessionId, config.model).catch(
5887
6100
  (err) => log.debug(`Post-task skill evaluation skipped: ${err}`)
5888
6101
  );
5889
6102
  }
5890
6103
  } catch (err) {
5891
- const errorMsg = err instanceof Error ? err.message : String(err);
5892
- log.error(`Task failed: ${errorMsg}`);
5893
- await failTask(task.id, errorMsg);
5894
- await emitEvent(task.id, "error", { message: errorMsg });
6104
+ const errMsg = errorMessage(err);
6105
+ log.error(`Task failed: ${errMsg}`);
6106
+ await failTask(task.id, errMsg);
6107
+ await emitEvent(task.id, "error", { message: errMsg });
5895
6108
  await emitEvent(task.id, "status_change", { status: "failed" });
5896
6109
  } finally {
5897
6110
  setCorrelationId(null);
@@ -6392,7 +6605,7 @@ function registerJobCommands(program2) {
6392
6605
  jobCmd.command("list").description("List your defined jobs").action(async () => {
6393
6606
  try {
6394
6607
  const userId = await getCurrentUserId();
6395
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6608
+ const { JobRunner: JobRunner2 } = await import("./job-runner-CJ7HM4GZ.js");
6396
6609
  const runner = new JobRunner2();
6397
6610
  const jobs = await runner.listJobs();
6398
6611
  if (jobs.length === 0) {
@@ -6416,7 +6629,7 @@ function registerJobCommands(program2) {
6416
6629
  jobCmd.command("status [name]").description("Show run history for a job (or all jobs)").option("-l, --limit <number>", "Max runs to show (default: 5)").action(async (name, opts) => {
6417
6630
  try {
6418
6631
  const userId = await getCurrentUserId();
6419
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6632
+ const { JobRunner: JobRunner2 } = await import("./job-runner-CJ7HM4GZ.js");
6420
6633
  const runner = new JobRunner2();
6421
6634
  const runs = await runner.getRunHistory(name, parseInt(opts.limit || "5"));
6422
6635
  if (runs.length === 0) {
@@ -6455,7 +6668,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`));
6455
6668
  process.exit(1);
6456
6669
  }
6457
6670
  const userId = await getCurrentUserId();
6458
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6671
+ const { JobRunner: JobRunner2 } = await import("./job-runner-CJ7HM4GZ.js");
6459
6672
  const runner = new JobRunner2();
6460
6673
  const job = await runner.loadJob(name);
6461
6674
  if (!job) {