assistme 0.3.5 → 0.3.6

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
@@ -1,20 +1,44 @@
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_COMPLETE_TASK_RETRIES,
9
+ MAX_CONTENT_SEARCH_FILES,
10
+ MAX_CONTENT_SEARCH_RESULTS,
11
+ MAX_FILE_SEARCH_RESULTS,
12
+ MAX_HISTORY_ENTRIES,
13
+ MAX_HISTORY_RESPONSE_LENGTH,
14
+ MAX_RESPONSE_CONTENT_LENGTH,
15
+ MAX_SKILL_RECORD_RESULT_LENGTH,
16
+ MAX_TOOL_INPUT_LOG_LENGTH,
17
+ MAX_TOOL_RESULT_LENGTH,
18
+ SCHEDULER_INTERVAL_MS,
19
+ SHELL_MAX_OUTPUT,
20
+ SHELL_TIMEOUT_MS,
21
+ SKILL_DESCRIPTION_BUDGET_CHARS,
22
+ SkillCreateResultSchema,
23
+ SkillDecisionSchema,
24
+ SkillRowSchema,
25
+ WS_CONNECT_TIMEOUT_MS,
4
26
  callMcpHandler,
27
+ errorMessage,
5
28
  log,
6
29
  newCorrelationId,
7
30
  readAuthStore,
31
+ safeParse,
8
32
  setCorrelationId,
9
33
  setLogLevel,
10
34
  writeAuthStore
11
- } from "./chunk-KX7ITO55.js";
35
+ } from "./chunk-4YWS463E.js";
12
36
  import {
13
37
  clearConfig,
14
38
  getConfig,
15
39
  getConfigPath,
16
40
  setConfig
17
- } from "./chunk-TTEGHE2E.js";
41
+ } from "./chunk-JVA6DHXD.js";
18
42
 
19
43
  // src/index.ts
20
44
  import { Command } from "commander";
@@ -54,7 +78,7 @@ async function logout() {
54
78
 
55
79
  // src/db/session.ts
56
80
  async function createSession(sessionName, workspacePath, version2) {
57
- const { getConfig: getConfig2 } = await import("./config-PUIS2TQL.js");
81
+ const { getConfig: getConfig2 } = await import("./config-T4357GAE.js");
58
82
  const data = await callMcpHandler("session.create", {
59
83
  session_name: sessionName,
60
84
  workspace_path: workspacePath,
@@ -135,11 +159,11 @@ async function completeTask(messageId, resultSummary, tokenUsage) {
135
159
  token_usage: tokenUsage || null
136
160
  });
137
161
  }
138
- async function failTask(messageId, errorMessage) {
162
+ async function failTask(messageId, errorMessage2) {
139
163
  try {
140
164
  await callMcpHandler("task.fail", {
141
165
  message_id: messageId,
142
- error: errorMessage
166
+ error: errorMessage2
143
167
  });
144
168
  } catch (err) {
145
169
  log.error(`Failed to update task status: ${err instanceof Error ? err.message : err}`);
@@ -412,9 +436,9 @@ var BrowserController = class {
412
436
  if (!settled) {
413
437
  settled = true;
414
438
  this.ws?.close();
415
- reject(new Error("Connection timeout (5s)"));
439
+ reject(new Error(`Connection timeout (${WS_CONNECT_TIMEOUT_MS}ms)`));
416
440
  }
417
- }, 5e3);
441
+ }, WS_CONNECT_TIMEOUT_MS);
418
442
  this.ws.on("open", () => {
419
443
  if (settled) return;
420
444
  settled = true;
@@ -462,6 +486,8 @@ var BrowserController = class {
462
486
  this.ws = null;
463
487
  this.connected = false;
464
488
  }
489
+ this.refCache.clear();
490
+ this.frameContexts.clear();
465
491
  return "Disconnected from browser.";
466
492
  }
467
493
  // ── CDP Protocol ────────────────────────────────────────────────
@@ -481,7 +507,7 @@ var BrowserController = class {
481
507
  const timeout = setTimeout(() => {
482
508
  this.callbacks.delete(id);
483
509
  reject(new Error(`CDP command timed out: ${method}`));
484
- }, 15e3);
510
+ }, CDP_COMMAND_TIMEOUT_MS);
485
511
  this.callbacks.set(id, (response) => {
486
512
  clearTimeout(timeout);
487
513
  if (response.error) {
@@ -1213,7 +1239,9 @@ Refs:
1213
1239
  height: r.box.height
1214
1240
  }
1215
1241
  });
1216
- this.frameContexts.set(r.id, contextId);
1242
+ if (this.frameContexts.size < FRAME_CONTEXTS_MAX_SIZE) {
1243
+ this.frameContexts.set(r.id, contextId);
1244
+ }
1217
1245
  }
1218
1246
  } catch {
1219
1247
  }
@@ -2328,51 +2356,22 @@ import ora3 from "ora";
2328
2356
  import { createInterface as createInterface2 } from "readline";
2329
2357
 
2330
2358
  // src/agent/scheduler.ts
2331
- var SCHEDULER_INTERVAL = 3e4;
2359
+ import { Cron } from "croner";
2332
2360
  function getNextRunTime(cronExpr, timezone, fromDate) {
2333
2361
  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
- }
2362
+ try {
2363
+ const job = new Cron(cronExpr, { timezone: timezone || "UTC" });
2364
+ const next = job.nextRun(now);
2365
+ if (!next) {
2366
+ throw new Error(`No future run time found for cron expression: ${cronExpr}`);
2353
2367
  }
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);
2368
+ return next;
2369
+ } catch (err) {
2370
+ if (err instanceof Error && err.message.includes("No future run time")) {
2371
+ throw err;
2372
+ }
2373
+ throw new Error(`Invalid cron expression "${cronExpr}": ${errorMessage(err)}`);
2374
+ }
2376
2375
  }
2377
2376
  var Scheduler = class {
2378
2377
  timer = null;
@@ -2382,7 +2381,7 @@ var Scheduler = class {
2382
2381
  this.onScheduledTask = onScheduledTask;
2383
2382
  this.running = true;
2384
2383
  await this.initializeNextRuns();
2385
- this.timer = setInterval(() => this.checkDueTasks(), SCHEDULER_INTERVAL);
2384
+ this.timer = setInterval(() => this.checkDueTasks(), SCHEDULER_INTERVAL_MS);
2386
2385
  log.info("Scheduler started (checking every 30s)");
2387
2386
  }
2388
2387
  stop() {
@@ -2405,7 +2404,7 @@ var Scheduler = class {
2405
2404
  }
2406
2405
  }
2407
2406
  } catch (err) {
2408
- log.debug(`Scheduler init: ${err}`);
2407
+ log.debug(`Scheduler init: ${errorMessage(err)}`);
2409
2408
  }
2410
2409
  }
2411
2410
  async checkDueTasks() {
@@ -2429,7 +2428,7 @@ var Scheduler = class {
2429
2428
  last_error: null
2430
2429
  });
2431
2430
  } catch (err) {
2432
- const errMsg = err instanceof Error ? err.message : String(err);
2431
+ const errMsg = errorMessage(err);
2433
2432
  await callMcpHandler("schedule.update", {
2434
2433
  task_id: task.id,
2435
2434
  last_error: errMsg
@@ -2437,7 +2436,7 @@ var Scheduler = class {
2437
2436
  log.error(`Scheduled task "${task.name}" failed: ${errMsg}`);
2438
2437
  }
2439
2438
  } catch (err) {
2440
- log.debug(`Scheduler check error: ${err}`);
2439
+ log.debug(`Scheduler check error: ${errorMessage(err)}`);
2441
2440
  }
2442
2441
  }
2443
2442
  };
@@ -2957,7 +2956,7 @@ var SkillManager = class {
2957
2956
  userId = null;
2958
2957
  /** Cache for findRelevant() — keyed by prompt, invalidated on skill changes */
2959
2958
  relevanceCache = /* @__PURE__ */ new Map();
2960
- DESCRIPTION_BUDGET_CHARS = 16e3;
2959
+ DESCRIPTION_BUDGET_CHARS = SKILL_DESCRIPTION_BUDGET_CHARS;
2961
2960
  setUserId(userId) {
2962
2961
  this.userId = userId;
2963
2962
  }
@@ -2966,7 +2965,9 @@ var SkillManager = class {
2966
2965
  try {
2967
2966
  const data = await callMcpHandler("skill.load");
2968
2967
  this.skills.clear();
2969
- for (const row of data || []) {
2968
+ for (const raw of data || []) {
2969
+ const row = safeParse(SkillRowSchema, raw);
2970
+ if (!row) continue;
2970
2971
  const skill = this.rowToSkill(row);
2971
2972
  this.skills.set(skill.name, skill);
2972
2973
  }
@@ -2980,22 +2981,22 @@ var SkillManager = class {
2980
2981
  }
2981
2982
  rowToSkill(row) {
2982
2983
  return {
2983
- name: row.name,
2984
- description: row.description || "",
2985
- version: row.version || "1.0.0",
2984
+ name: String(row.name),
2985
+ description: String(row.description ?? ""),
2986
+ version: String(row.version ?? "1.0.0"),
2986
2987
  userInvocable: row.user_invocable !== false,
2987
2988
  disableModelInvocation: row.disable_model_invocation === true,
2988
- keywords: row.keywords || [],
2989
- allowedTools: row.allowed_tools || [],
2990
- argumentHint: row.argument_hint || "",
2989
+ keywords: Array.isArray(row.keywords) ? row.keywords : [],
2990
+ allowedTools: Array.isArray(row.allowed_tools) ? row.allowed_tools : [],
2991
+ argumentHint: String(row.argument_hint ?? ""),
2991
2992
  metadata: parseDbMetadata(row.metadata),
2992
- homepage: row.homepage || "",
2993
- content: row.content,
2993
+ homepage: String(row.homepage ?? ""),
2994
+ content: String(row.content ?? ""),
2994
2995
  filePath: "",
2995
2996
  source: row.source || "manual",
2996
- dbId: row.id,
2997
- sourceSkillId: row.source_skill_id || void 0,
2998
- invocationCount: row.invocation_count || 0
2997
+ dbId: row.id != null ? String(row.id) : void 0,
2998
+ sourceSkillId: row.source_skill_id != null ? String(row.source_skill_id) : void 0,
2999
+ invocationCount: typeof row.invocation_count === "number" ? row.invocation_count : 0
2999
3000
  };
3000
3001
  }
3001
3002
  /** Invalidate caches when skills change (create, add, update, remove). */
@@ -3125,13 +3126,14 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
3125
3126
  metadata
3126
3127
  }
3127
3128
  );
3128
- const row = Array.isArray(data) ? data[0] : data;
3129
+ const raw = Array.isArray(data) ? data[0] : data;
3130
+ const row = safeParse(SkillCreateResultSchema, raw);
3129
3131
  if (!row) {
3130
- log.debug(`Skill create returned no data for "${name}"`);
3132
+ log.debug(`Skill create returned invalid data for "${name}"`);
3131
3133
  return null;
3132
3134
  }
3133
3135
  const id = row.out_id || row.id;
3134
- const skillName = row.out_name || row.name;
3136
+ const skillName = row.out_name || row.name || name;
3135
3137
  this.skills.set(skillName, {
3136
3138
  name: skillName,
3137
3139
  description,
@@ -3301,11 +3303,11 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
3301
3303
  });
3302
3304
  if (data) {
3303
3305
  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
3306
+ name: String(row.name),
3307
+ description: String(row.description ?? ""),
3308
+ emoji: String(row.emoji ?? ""),
3309
+ source: String(row.source ?? "manual"),
3310
+ invocationCount: typeof row.invocation_count === "number" ? row.invocation_count : 0
3309
3311
  }));
3310
3312
  }
3311
3313
  } catch {
@@ -3368,17 +3370,17 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
3368
3370
  limit: options?.limit || 20,
3369
3371
  offset: options?.offset || 0
3370
3372
  });
3371
- return (data || []).map((r) => ({
3373
+ return (data || []).map((r) => safeParse(BrowseSkillRowSchema, r)).filter(Boolean).map((r) => ({
3372
3374
  id: r.id,
3373
3375
  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
3376
+ description: r.description,
3377
+ emoji: r.emoji,
3378
+ version: r.version,
3379
+ authorName: r.author_name,
3380
+ category: r.category,
3381
+ installCount: r.install_count,
3382
+ avgRating: r.avg_rating ?? null,
3383
+ ratingCount: r.rating_count
3382
3384
  }));
3383
3385
  } catch {
3384
3386
  return [];
@@ -3403,8 +3405,12 @@ function substituteArguments(content, args) {
3403
3405
  content = content.replace(/\$(\d+)(?!\w)/g, (_, i) => parts[parseInt(i)] || "");
3404
3406
  return content;
3405
3407
  }
3408
+ 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
3409
  function preprocessDynamicContext(content, cwd) {
3407
3410
  return content.replace(/!`([^`]+)`/g, (_, cmd) => {
3411
+ if (!SAFE_DYNAMIC_COMMANDS.test(cmd.trim())) {
3412
+ return `[command blocked: ${cmd}]`;
3413
+ }
3408
3414
  try {
3409
3415
  return execSync2(cmd, { timeout: 1e4, encoding: "utf-8", cwd }).trim();
3410
3416
  } catch {
@@ -3479,7 +3485,9 @@ Respond with a JSON object now.`;
3479
3485
  } else if (message.type === "result") {
3480
3486
  const resultMsg = message;
3481
3487
  if (resultMsg.subtype === "success" && "total_cost_usd" in resultMsg) {
3482
- log.debug(`Skill evaluation cost: $${resultMsg.total_cost_usd.toFixed(4)}`);
3488
+ log.debug(
3489
+ `Skill evaluation cost: $${resultMsg.total_cost_usd.toFixed(4)}`
3490
+ );
3483
3491
  }
3484
3492
  }
3485
3493
  }
@@ -3488,13 +3496,9 @@ Respond with a JSON object now.`;
3488
3496
  log.debug("Skill evaluation: no valid JSON in response");
3489
3497
  return;
3490
3498
  }
3491
- if (!["create", "update", "skip"].includes(decision.action)) {
3492
- log.debug("Skill evaluation: invalid action");
3493
- return;
3494
- }
3495
3499
  await executeSkillDecision(decision, skillManager);
3496
3500
  } catch (err) {
3497
- log.debug(`Skill evaluation error: ${err}`);
3501
+ log.debug(`Skill evaluation error: ${errorMessage(err)}`);
3498
3502
  }
3499
3503
  }
3500
3504
  async function executeSkillDecision(decision, skillManager) {
@@ -3504,13 +3508,17 @@ async function executeSkillDecision(decision, skillManager) {
3504
3508
  log.debug("Skill create skipped: missing name or instructions");
3505
3509
  return;
3506
3510
  }
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
- }
3511
+ let skillName = normalizeSkillName(decision.name);
3512
+ if (!skillName) {
3513
+ log.debug(`Skill create skipped: name "${decision.name}" cannot be normalized`);
3514
+ return;
3515
+ }
3516
+ const validationError = validateSkillName(skillName);
3517
+ if (validationError) {
3518
+ log.debug(`Skill create skipped: ${validationError}`);
3519
+ return;
3520
+ }
3521
+ if (skillName !== decision.name) {
3514
3522
  log.debug(`Normalized skill name: "${decision.name}" \u2192 "${skillName}"`);
3515
3523
  }
3516
3524
  const existing = skillManager.findSimilar(skillName);
@@ -3569,25 +3577,28 @@ async function executeSkillDecision(decision, skillManager) {
3569
3577
  }
3570
3578
  function parseJsonResponse(text) {
3571
3579
  const trimmed = text.trim();
3572
- try {
3573
- const parsed = JSON.parse(trimmed);
3574
- if (parsed.action) return parsed;
3575
- } catch {
3576
- }
3580
+ const candidates = [trimmed];
3577
3581
  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;
3582
+ if (start !== -1) {
3583
+ let depth = 0;
3584
+ for (let i = start; i < trimmed.length; i++) {
3585
+ if (trimmed[i] === "{") depth++;
3586
+ else if (trimmed[i] === "}") depth--;
3587
+ if (depth === 0) {
3588
+ candidates.push(trimmed.slice(start, i + 1));
3589
+ break;
3588
3590
  }
3589
3591
  }
3590
3592
  }
3593
+ for (const candidate of candidates) {
3594
+ try {
3595
+ const parsed = JSON.parse(candidate);
3596
+ const validated = safeParse(SkillDecisionSchema, parsed);
3597
+ if (validated) return validated;
3598
+ } catch {
3599
+ continue;
3600
+ }
3601
+ }
3591
3602
  return null;
3592
3603
  }
3593
3604
 
@@ -3634,14 +3645,16 @@ import { z } from "zod/v4";
3634
3645
 
3635
3646
  // src/tools/filesystem.ts
3636
3647
  import { readFile, writeFile, readdir, stat, mkdir } from "fs/promises";
3637
- import { resolve, relative, join as join2 } from "path";
3648
+ import { resolve, relative, join as join2, sep } from "path";
3638
3649
  import { glob } from "glob";
3639
3650
  function assertWithinWorkspace(filePath) {
3640
3651
  const config = getConfig();
3641
3652
  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}"`
3653
+ const rel = relative(config.workspacePath, resolved);
3654
+ if (rel.startsWith("..") || rel.startsWith(sep + sep)) {
3655
+ throw new AppError(
3656
+ `Access denied: path "${filePath}" is outside workspace "${config.workspacePath}"`,
3657
+ "PATH_TRAVERSAL"
3645
3658
  );
3646
3659
  }
3647
3660
  return resolved;
@@ -3671,7 +3684,7 @@ async function searchFiles(pattern, directory) {
3671
3684
  ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
3672
3685
  });
3673
3686
  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");
3687
+ return matches.slice(0, MAX_FILE_SEARCH_RESULTS).map((m) => relative(config.workspacePath, join2(cwd, m))).join("\n");
3675
3688
  }
3676
3689
  async function listDirectory(path) {
3677
3690
  const config = getConfig();
@@ -3681,9 +3694,7 @@ async function listDirectory(path) {
3681
3694
  for (const entry of entries) {
3682
3695
  if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
3683
3696
  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
- ) : "";
3697
+ const info = entry.isFile() ? await stat(join2(resolved, entry.name)).then((s) => ` (${formatSize(s.size)})`) : "";
3687
3698
  results.push(`${icon} ${entry.name}${info}`);
3688
3699
  }
3689
3700
  return results.join("\n") || "Empty directory.";
@@ -3701,9 +3712,14 @@ async function searchContent(pattern, fileGlob, directory) {
3701
3712
  nodir: true,
3702
3713
  ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
3703
3714
  });
3704
- const regex = new RegExp(pattern, "gi");
3715
+ let regex;
3716
+ try {
3717
+ regex = new RegExp(pattern, "gi");
3718
+ } catch {
3719
+ regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
3720
+ }
3705
3721
  const results = [];
3706
- for (const file of files.slice(0, 200)) {
3722
+ for (const file of files.slice(0, MAX_CONTENT_SEARCH_FILES)) {
3707
3723
  try {
3708
3724
  const content = await readFile(join2(cwd, file), "utf-8");
3709
3725
  const lines = content.split("\n");
@@ -3712,10 +3728,10 @@ async function searchContent(pattern, fileGlob, directory) {
3712
3728
  const relPath = relative(config.workspacePath, join2(cwd, file));
3713
3729
  results.push(`${relPath}:${i + 1}: ${lines[i].trim()}`);
3714
3730
  regex.lastIndex = 0;
3715
- if (results.length >= 30) break;
3731
+ if (results.length >= MAX_CONTENT_SEARCH_RESULTS) break;
3716
3732
  }
3717
3733
  }
3718
- if (results.length >= 30) break;
3734
+ if (results.length >= MAX_CONTENT_SEARCH_RESULTS) break;
3719
3735
  } catch {
3720
3736
  }
3721
3737
  }
@@ -3724,8 +3740,6 @@ async function searchContent(pattern, fileGlob, directory) {
3724
3740
 
3725
3741
  // src/tools/shell.ts
3726
3742
  import { exec } from "child_process";
3727
- var TIMEOUT_MS = 3e4;
3728
- var MAX_OUTPUT = 5e4;
3729
3743
  var BLOCKED_PATTERNS = [
3730
3744
  /rm\s+(-\w*\s+)*-\w*r\w*\s+\/($|\s)/i,
3731
3745
  // rm -rf /, rm -fr /, etc.
@@ -3753,7 +3767,7 @@ function isBlocked(command) {
3753
3767
  }
3754
3768
  async function executeShell(command, cwd) {
3755
3769
  if (isBlocked(command)) {
3756
- throw new Error(`Command blocked for safety: "${command}"`);
3770
+ throw new AppError(`Command blocked for safety: "${command}"`, "COMMAND_BLOCKED");
3757
3771
  }
3758
3772
  const config = getConfig();
3759
3773
  const workDir = cwd || config.workspacePath;
@@ -3762,7 +3776,7 @@ async function executeShell(command, cwd) {
3762
3776
  command,
3763
3777
  {
3764
3778
  cwd: workDir,
3765
- timeout: TIMEOUT_MS,
3779
+ timeout: SHELL_TIMEOUT_MS,
3766
3780
  maxBuffer: 1024 * 1024,
3767
3781
  // 1MB buffer
3768
3782
  env: { ...process.env, TERM: "dumb" }
@@ -3780,10 +3794,10 @@ ${stderr}` : "";
3780
3794
  if (error && !stdout && !stderr) {
3781
3795
  output = `Error: ${error.message}`;
3782
3796
  }
3783
- if (output.length > MAX_OUTPUT) {
3784
- output = output.slice(0, MAX_OUTPUT) + `
3797
+ if (output.length > SHELL_MAX_OUTPUT) {
3798
+ output = output.slice(0, SHELL_MAX_OUTPUT) + `
3785
3799
 
3786
- [Output truncated at ${MAX_OUTPUT} bytes]`;
3800
+ [Output truncated at ${SHELL_MAX_OUTPUT} bytes]`;
3787
3801
  }
3788
3802
  resolve2(output || "(no output)");
3789
3803
  }
@@ -5443,7 +5457,7 @@ function createEventHooks(taskId, toolCallRecords) {
5443
5457
  const rawName = preInput.tool_name;
5444
5458
  const displayName = stripMcpPrefix(rawName);
5445
5459
  const toolInput = preInput.tool_input;
5446
- log.tool(displayName, JSON.stringify(toolInput).slice(0, 200));
5460
+ log.tool(displayName, JSON.stringify(toolInput).slice(0, MAX_TOOL_INPUT_LOG_LENGTH));
5447
5461
  await emitEvent(taskId, "tool_use_start", { name: displayName });
5448
5462
  await emitEvent(taskId, "tool_use_input", { input: toolInput });
5449
5463
  if (displayName === "browser_request_user_action") {
@@ -5462,15 +5476,15 @@ function createEventHooks(taskId, toolCallRecords) {
5462
5476
  const toolInput = postInput.tool_input;
5463
5477
  const toolResponse = postInput.tool_response;
5464
5478
  const resultStr = typeof toolResponse === "string" ? toolResponse : JSON.stringify(toolResponse);
5465
- log.result(resultStr.slice(0, 200));
5479
+ log.result(resultStr.slice(0, MAX_TOOL_INPUT_LOG_LENGTH));
5466
5480
  await emitEvent(taskId, "tool_result", {
5467
5481
  name: displayName,
5468
- result: resultStr.slice(0, 1e4)
5482
+ result: resultStr.slice(0, MAX_TOOL_RESULT_LENGTH)
5469
5483
  });
5470
5484
  toolCallRecords.push({
5471
5485
  name: displayName,
5472
5486
  input: toolInput || {},
5473
- result: resultStr.slice(0, 300)
5487
+ result: resultStr.slice(0, MAX_SKILL_RECORD_RESULT_LENGTH)
5474
5488
  });
5475
5489
  return {};
5476
5490
  };
@@ -5649,8 +5663,6 @@ var TaskTimeout = class {
5649
5663
  }
5650
5664
  }
5651
5665
  };
5652
- var MAX_HISTORY_ENTRIES = 10;
5653
- var MAX_RESPONSE_LENGTH = 1500;
5654
5666
  var TaskProcessor = class {
5655
5667
  memoryManager = null;
5656
5668
  skillManager;
@@ -5685,7 +5697,7 @@ var TaskProcessor = class {
5685
5697
  async processTask(task) {
5686
5698
  const config = getConfig();
5687
5699
  resetEventSequence();
5688
- const taskTimeoutMs = (config.taskTimeoutMinutes || 10) * 6e4;
5700
+ const taskTimeoutMs = config.taskTimeoutMinutes * 6e4;
5689
5701
  newCorrelationId();
5690
5702
  log.info(`Processing task ${task.id.slice(0, 8)}...`);
5691
5703
  let finalResponse = "";
@@ -5724,7 +5736,7 @@ var TaskProcessor = class {
5724
5736
  for (const entry of history) {
5725
5737
  historyPrompt += `User: ${entry.prompt}
5726
5738
  `;
5727
- const truncated = entry.response.length > MAX_RESPONSE_LENGTH ? entry.response.slice(0, MAX_RESPONSE_LENGTH) + "\u2026" : entry.response;
5739
+ const truncated = entry.response.length > MAX_HISTORY_RESPONSE_LENGTH ? entry.response.slice(0, MAX_HISTORY_RESPONSE_LENGTH) + "\u2026" : entry.response;
5728
5740
  historyPrompt += `Assistant: ${truncated}
5729
5741
 
5730
5742
  `;
@@ -5803,16 +5815,11 @@ var TaskProcessor = class {
5803
5815
  persistSession: true,
5804
5816
  abortController
5805
5817
  };
5806
- const taskStartTime = Date.now();
5807
5818
  try {
5808
5819
  for await (const message of query2({
5809
5820
  prompt: promptMessages(),
5810
5821
  options
5811
5822
  })) {
5812
- if (Date.now() - taskStartTime > taskTimeoutMs) {
5813
- finalResponse += "\n\n[Task timed out]";
5814
- break;
5815
- }
5816
5823
  switch (message.type) {
5817
5824
  case "assistant": {
5818
5825
  const assistantMsg = message;
@@ -5824,7 +5831,8 @@ var TaskProcessor = class {
5824
5831
  text: block.text
5825
5832
  });
5826
5833
  } else if (block.type === "thinking" && "thinking" in block) {
5827
- const thinkingText = block.thinking;
5834
+ const thinkingBlock = block;
5835
+ const thinkingText = thinkingBlock.thinking;
5828
5836
  log.debug(`Thinking: ${thinkingText.slice(0, 100)}...`);
5829
5837
  await emitEvent(task.id, "thinking", {
5830
5838
  text: thinkingText
@@ -5857,8 +5865,11 @@ var TaskProcessor = class {
5857
5865
  break;
5858
5866
  }
5859
5867
  default:
5860
- if (message.type === "system" && "subtype" in message && message.subtype === "init") {
5861
- agentSessionId = message.session_id;
5868
+ if (message.type === "system" && "subtype" in message) {
5869
+ const sysMsg = message;
5870
+ if (sysMsg.subtype === "init" && sysMsg.session_id) {
5871
+ agentSessionId = sysMsg.session_id;
5872
+ }
5862
5873
  }
5863
5874
  log.debug(`SDK message type: ${message.type}`);
5864
5875
  break;
@@ -5867,10 +5878,9 @@ var TaskProcessor = class {
5867
5878
  } finally {
5868
5879
  taskTimeout.clear();
5869
5880
  }
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;
5881
+ const truncatedResponse = finalResponse.length > MAX_RESPONSE_CONTENT_LENGTH ? finalResponse.slice(0, MAX_RESPONSE_CONTENT_LENGTH) + "\n\n[Response truncated]" : finalResponse;
5872
5882
  await withRetry(() => completeTask(task.id, truncatedResponse, tokenUsage), {
5873
- maxRetries: 2,
5883
+ maxRetries: MAX_COMPLETE_TASK_RETRIES,
5874
5884
  baseDelayMs: 300,
5875
5885
  label: "completeTask"
5876
5886
  });
@@ -5888,7 +5898,7 @@ var TaskProcessor = class {
5888
5898
  );
5889
5899
  }
5890
5900
  } catch (err) {
5891
- const errorMsg = err instanceof Error ? err.message : String(err);
5901
+ const errorMsg = errorMessage(err);
5892
5902
  log.error(`Task failed: ${errorMsg}`);
5893
5903
  await failTask(task.id, errorMsg);
5894
5904
  await emitEvent(task.id, "error", { message: errorMsg });
@@ -6392,7 +6402,7 @@ function registerJobCommands(program2) {
6392
6402
  jobCmd.command("list").description("List your defined jobs").action(async () => {
6393
6403
  try {
6394
6404
  const userId = await getCurrentUserId();
6395
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6405
+ const { JobRunner: JobRunner2 } = await import("./job-runner-JT3JWZBV.js");
6396
6406
  const runner = new JobRunner2();
6397
6407
  const jobs = await runner.listJobs();
6398
6408
  if (jobs.length === 0) {
@@ -6416,7 +6426,7 @@ function registerJobCommands(program2) {
6416
6426
  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
6427
  try {
6418
6428
  const userId = await getCurrentUserId();
6419
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6429
+ const { JobRunner: JobRunner2 } = await import("./job-runner-JT3JWZBV.js");
6420
6430
  const runner = new JobRunner2();
6421
6431
  const runs = await runner.getRunHistory(name, parseInt(opts.limit || "5"));
6422
6432
  if (runs.length === 0) {
@@ -6455,7 +6465,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`));
6455
6465
  process.exit(1);
6456
6466
  }
6457
6467
  const userId = await getCurrentUserId();
6458
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6468
+ const { JobRunner: JobRunner2 } = await import("./job-runner-JT3JWZBV.js");
6459
6469
  const runner = new JobRunner2();
6460
6470
  const job = await runner.loadJob(name);
6461
6471
  if (!job) {