assistme 0.3.4 → 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}`);
@@ -360,6 +384,8 @@ var BrowserController = class {
360
384
  connected = false;
361
385
  currentTabId = null;
362
386
  refCache = /* @__PURE__ */ new Map();
387
+ frameContexts = /* @__PURE__ */ new Map();
388
+ // refId → contextId
363
389
  constructor(port = 9222) {
364
390
  this.debugPort = port;
365
391
  }
@@ -410,9 +436,9 @@ var BrowserController = class {
410
436
  if (!settled) {
411
437
  settled = true;
412
438
  this.ws?.close();
413
- reject(new Error("Connection timeout (5s)"));
439
+ reject(new Error(`Connection timeout (${WS_CONNECT_TIMEOUT_MS}ms)`));
414
440
  }
415
- }, 5e3);
441
+ }, WS_CONNECT_TIMEOUT_MS);
416
442
  this.ws.on("open", () => {
417
443
  if (settled) return;
418
444
  settled = true;
@@ -460,6 +486,8 @@ var BrowserController = class {
460
486
  this.ws = null;
461
487
  this.connected = false;
462
488
  }
489
+ this.refCache.clear();
490
+ this.frameContexts.clear();
463
491
  return "Disconnected from browser.";
464
492
  }
465
493
  // ── CDP Protocol ────────────────────────────────────────────────
@@ -479,7 +507,7 @@ var BrowserController = class {
479
507
  const timeout = setTimeout(() => {
480
508
  this.callbacks.delete(id);
481
509
  reject(new Error(`CDP command timed out: ${method}`));
482
- }, 15e3);
510
+ }, CDP_COMMAND_TIMEOUT_MS);
483
511
  this.callbacks.set(id, (response) => {
484
512
  clearTimeout(timeout);
485
513
  if (response.error) {
@@ -653,13 +681,46 @@ URL: ${info.url}`;
653
681
  const result = await this.send("Runtime.evaluate", {
654
682
  expression: `
655
683
  (function() {
656
- const el = document.querySelector(${selectorJS});
684
+ var el = document.querySelector(${selectorJS});
685
+
686
+ // If not found in main document, search same-origin iframes
687
+ if (!el) {
688
+ var iframes = document.querySelectorAll('iframe');
689
+ for (var i = 0; i < iframes.length; i++) {
690
+ try {
691
+ var iframeDoc = iframes[i].contentDocument;
692
+ if (iframeDoc) {
693
+ el = iframeDoc.querySelector(${selectorJS});
694
+ if (el) break;
695
+ }
696
+ } catch(e) { /* cross-origin, skip */ }
697
+ }
698
+ }
699
+
657
700
  if (!el) return 'Element not found: ' + ${selectorJS};
658
701
 
659
702
  el.focus();
660
703
 
661
- // Clear existing value
662
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
704
+ // Check if this is a contenteditable element (rich text editor)
705
+ var isContentEditable = el.isContentEditable ||
706
+ el.getAttribute('contenteditable') === 'true' ||
707
+ el.getAttribute('contenteditable') === '';
708
+
709
+ if (isContentEditable) {
710
+ // For contenteditable: select all content, then replace
711
+ var ownerDoc = el.ownerDocument;
712
+ var sel = ownerDoc.defaultView.getSelection();
713
+ var range = ownerDoc.createRange();
714
+ range.selectNodeContents(el);
715
+ sel.removeAllRanges();
716
+ sel.addRange(range);
717
+ // Use insertText command which respects undo stack and triggers input events
718
+ ownerDoc.execCommand('insertText', false, ${textJS});
719
+ return 'Typed into: ' + (el.tagName || '') + ' [contenteditable]';
720
+ }
721
+
722
+ // For input/textarea: clear and set value
723
+ var nativeInputValueSetter = Object.getOwnPropertyDescriptor(
663
724
  window.HTMLInputElement.prototype, 'value'
664
725
  )?.set || Object.getOwnPropertyDescriptor(
665
726
  window.HTMLTextAreaElement.prototype, 'value'
@@ -679,7 +740,27 @@ URL: ${info.url}`;
679
740
  `,
680
741
  returnByValue: true
681
742
  });
682
- return result.result?.value || "Text entered.";
743
+ const textResult = result.result?.value || "";
744
+ if (textResult.startsWith("Element not found")) {
745
+ return this.typeAtFocus(text);
746
+ }
747
+ return textResult || "Text entered.";
748
+ }
749
+ /**
750
+ * Type text into the currently focused element using CDP Input.insertText.
751
+ * This bypasses DOM queries entirely and works with any focused element,
752
+ * including those inside cross-origin iframes or shadow DOM.
753
+ */
754
+ async typeAtFocus(text) {
755
+ this.ensureConnected();
756
+ const modKey = platform() === "darwin" ? "Meta" : "Control";
757
+ await this.pressKey(`${modKey}+a`);
758
+ await new Promise((r) => setTimeout(r, 50));
759
+ await this.pressKey("Backspace");
760
+ await new Promise((r) => setTimeout(r, 50));
761
+ await this.send("Input.insertText", { text });
762
+ await new Promise((r) => setTimeout(r, 100));
763
+ return "Text entered (into focused element).";
683
764
  }
684
765
  async pressKey(key) {
685
766
  this.ensureConnected();
@@ -929,6 +1010,7 @@ URL: ${info.url}`;
929
1010
  inputType: r.type || "",
930
1011
  box: r.box
931
1012
  }));
1013
+ await this.discoverCrossOriginFrameRefs(refs);
932
1014
  if (annotate && refs.length <= 40) {
933
1015
  const refsJson = JSON.stringify(refs);
934
1016
  await this.send("Runtime.evaluate", {
@@ -1003,6 +1085,199 @@ Refs:
1003
1085
  }
1004
1086
  return table;
1005
1087
  }
1088
+ // ── Cross-Origin Iframe Discovery ────────────────────────────────
1089
+ /**
1090
+ * Use CDP's Page.getFrameTree + Runtime.evaluate with contextId to discover
1091
+ * interactive elements inside cross-origin iframes (e.g., ProtonMail editor,
1092
+ * Google Docs, embedded rich text editors).
1093
+ *
1094
+ * Same-origin iframes are already handled inline by the main snapshot JS.
1095
+ * This method handles the ones that threw cross-origin errors.
1096
+ */
1097
+ async discoverCrossOriginFrameRefs(refs) {
1098
+ this.frameContexts.clear();
1099
+ try {
1100
+ const frameTree = await this.send("Page.getFrameTree");
1101
+ const mainFrameId = frameTree.frameTree?.frame?.id;
1102
+ const childFrames = frameTree.frameTree?.childFrames || [];
1103
+ if (childFrames.length === 0) return;
1104
+ const contexts = await this.getFrameContexts(mainFrameId || "");
1105
+ for (const child of childFrames) {
1106
+ const frameId = child.frame.id;
1107
+ const contextId = contexts.get(frameId);
1108
+ if (!contextId) continue;
1109
+ const iframeOffsetResult = await this.send("Runtime.evaluate", {
1110
+ expression: `
1111
+ (function() {
1112
+ var iframes = document.querySelectorAll('iframe');
1113
+ for (var i = 0; i < iframes.length; i++) {
1114
+ try {
1115
+ // Match by frame src or name
1116
+ var f = iframes[i];
1117
+ if (f.contentWindow) {
1118
+ var r = f.getBoundingClientRect();
1119
+ if (r.width > 10 && r.height > 10) {
1120
+ return JSON.stringify({ x: r.x, y: r.y, width: r.width, height: r.height, index: i });
1121
+ }
1122
+ }
1123
+ } catch(e) {}
1124
+ }
1125
+ return 'null';
1126
+ })()
1127
+ `,
1128
+ returnByValue: true
1129
+ });
1130
+ let iframeOffset = { x: 0, y: 0 };
1131
+ try {
1132
+ const parsed = JSON.parse(
1133
+ iframeOffsetResult.result?.value || "null"
1134
+ );
1135
+ if (parsed) iframeOffset = { x: parsed.x, y: parsed.y };
1136
+ } catch {
1137
+ }
1138
+ const startRefId = refs.length + 1;
1139
+ try {
1140
+ const frameResult = await this.send("Runtime.evaluate", {
1141
+ expression: `
1142
+ (function() {
1143
+ var selectors = [
1144
+ 'a[href]', 'button', 'input:not([type="hidden"])', 'select', 'textarea',
1145
+ '[role="button"]', '[role="link"]', '[role="checkbox"]', '[role="radio"]',
1146
+ '[role="combobox"]', '[role="listbox"]', '[role="menuitem"]', '[role="tab"]',
1147
+ '[role="switch"]', '[role="slider"]', '[role="option"]', '[role="searchbox"]',
1148
+ '[onclick]', '[tabindex]:not([tabindex="-1"])',
1149
+ '[contenteditable="true"]', '[contenteditable=""]'
1150
+ ].join(', ');
1151
+
1152
+ var all = document.querySelectorAll(selectors);
1153
+ // Also check if the body itself is contenteditable
1154
+ if (document.body && (document.body.isContentEditable || document.body.getAttribute('contenteditable') === 'true')) {
1155
+ all = [document.body].concat(Array.from(all));
1156
+ }
1157
+
1158
+ var refs = [];
1159
+ var startId = ${startRefId};
1160
+ var vh = window.innerHeight;
1161
+ var vw = window.innerWidth;
1162
+
1163
+ for (var i = 0; i < all.length && refs.length < 20; i++) {
1164
+ var el = all[i];
1165
+ var rect = el.getBoundingClientRect();
1166
+ if (rect.width < 5 || rect.height < 5) continue;
1167
+ var style = window.getComputedStyle(el);
1168
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
1169
+
1170
+ var role = el.getAttribute('role') || '';
1171
+ if (!role) {
1172
+ var tag = el.tagName.toLowerCase();
1173
+ if (tag === 'a') role = 'link';
1174
+ else if (tag === 'button') role = 'button';
1175
+ else if (tag === 'input') {
1176
+ var t = (el.type || 'text').toLowerCase();
1177
+ if (t === 'checkbox') role = 'checkbox';
1178
+ else if (t === 'radio') role = 'radio';
1179
+ else if (t === 'submit' || t === 'button') role = 'button';
1180
+ else role = 'textbox';
1181
+ }
1182
+ else if (tag === 'select') role = 'combobox';
1183
+ else if (tag === 'textarea') role = 'textbox';
1184
+ else if (el.isContentEditable) role = 'textbox';
1185
+ else role = tag;
1186
+ }
1187
+
1188
+ var name = '';
1189
+ var ariaLabel = el.getAttribute('aria-label');
1190
+ if (ariaLabel) {
1191
+ name = ariaLabel;
1192
+ } else if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
1193
+ name = el.getAttribute('placeholder') || el.getAttribute('name') || '';
1194
+ } else if (el.isContentEditable) {
1195
+ name = 'compose body';
1196
+ } else {
1197
+ name = (el.textContent || '').trim().slice(0, 60);
1198
+ }
1199
+
1200
+ var refId = startId + refs.length;
1201
+ el.setAttribute('data-assistme-ref', String(refId));
1202
+
1203
+ refs.push({
1204
+ id: refId,
1205
+ role: role,
1206
+ name: name,
1207
+ tag: el.tagName.toLowerCase(),
1208
+ type: el.getAttribute('type') || '',
1209
+ box: {
1210
+ x: Math.round(rect.x),
1211
+ y: Math.round(rect.y),
1212
+ width: Math.round(rect.width),
1213
+ height: Math.round(rect.height)
1214
+ },
1215
+ inFrame: true
1216
+ });
1217
+ }
1218
+
1219
+ return JSON.stringify(refs);
1220
+ })()
1221
+ `,
1222
+ contextId,
1223
+ returnByValue: true
1224
+ });
1225
+ const frameRefs = JSON.parse(
1226
+ frameResult.result?.value || "[]"
1227
+ );
1228
+ for (const r of frameRefs) {
1229
+ refs.push({
1230
+ id: r.id,
1231
+ role: r.role,
1232
+ name: r.name,
1233
+ tag: r.tag,
1234
+ inputType: r.type || "",
1235
+ box: {
1236
+ x: Math.round(r.box.x + iframeOffset.x),
1237
+ y: Math.round(r.box.y + iframeOffset.y),
1238
+ width: r.box.width,
1239
+ height: r.box.height
1240
+ }
1241
+ });
1242
+ if (this.frameContexts.size < FRAME_CONTEXTS_MAX_SIZE) {
1243
+ this.frameContexts.set(r.id, contextId);
1244
+ }
1245
+ }
1246
+ } catch {
1247
+ }
1248
+ }
1249
+ } catch {
1250
+ }
1251
+ }
1252
+ /**
1253
+ * Get execution context IDs for each frame in the page.
1254
+ * Uses Runtime.executionContextCreated events collected during the session,
1255
+ * or falls back to evaluating in known frames.
1256
+ */
1257
+ async getFrameContexts(_mainFrameId) {
1258
+ const contexts = /* @__PURE__ */ new Map();
1259
+ try {
1260
+ await this.send("Runtime.enable").catch(() => {
1261
+ });
1262
+ const frameTree = await this.send("Page.getFrameTree");
1263
+ const childFrames = frameTree.frameTree?.childFrames || [];
1264
+ for (const child of childFrames) {
1265
+ try {
1266
+ const world = await this.send("Page.createIsolatedWorld", {
1267
+ frameId: child.frame.id,
1268
+ worldName: "assistme-snapshot",
1269
+ grantUniveralAccess: true
1270
+ });
1271
+ if (world.executionContextId) {
1272
+ contexts.set(child.frame.id, world.executionContextId);
1273
+ }
1274
+ } catch {
1275
+ }
1276
+ }
1277
+ } catch {
1278
+ }
1279
+ return contexts;
1280
+ }
1006
1281
  // ── Ref Resolution ────────────────────────────────────────────────
1007
1282
  /**
1008
1283
  * Resolve a ref ID to its current center coordinates in the viewport.
@@ -1115,9 +1390,85 @@ Refs:
1115
1390
  returnByValue: true
1116
1391
  });
1117
1392
  const value = result.result?.value;
1118
- if (!value || value === "null") return null;
1393
+ if (value && value !== "null") {
1394
+ try {
1395
+ return JSON.parse(value);
1396
+ } catch {
1397
+ }
1398
+ }
1399
+ const frameContextId = this.frameContexts.get(refId);
1400
+ if (frameContextId) {
1401
+ return this.resolveRefInFrame(refId, frameContextId, role, name);
1402
+ }
1403
+ return null;
1404
+ }
1405
+ /**
1406
+ * Resolve a ref inside a cross-origin iframe using its execution context.
1407
+ * Returns coordinates adjusted by the iframe's viewport offset.
1408
+ */
1409
+ async resolveRefInFrame(refId, contextId, role, name) {
1410
+ const roleJS = JSON.stringify(role);
1411
+ const nameJS = JSON.stringify(name);
1119
1412
  try {
1120
- return JSON.parse(value);
1413
+ const offsetResult = await this.send("Runtime.evaluate", {
1414
+ expression: `
1415
+ (function() {
1416
+ var iframes = document.querySelectorAll('iframe');
1417
+ for (var i = 0; i < iframes.length; i++) {
1418
+ var r = iframes[i].getBoundingClientRect();
1419
+ if (r.width > 10 && r.height > 10) {
1420
+ return JSON.stringify({ x: r.x, y: r.y });
1421
+ }
1422
+ }
1423
+ return JSON.stringify({ x: 0, y: 0 });
1424
+ })()
1425
+ `,
1426
+ returnByValue: true
1427
+ });
1428
+ const offset = JSON.parse(
1429
+ offsetResult.result?.value || '{"x":0,"y":0}'
1430
+ );
1431
+ const frameResult = await this.send("Runtime.evaluate", {
1432
+ expression: `
1433
+ (function() {
1434
+ var el = document.querySelector('[data-assistme-ref="${refId}"]');
1435
+ if (!el && ${roleJS} && ${nameJS}) {
1436
+ // Fallback: search by role
1437
+ var candidates = document.querySelectorAll('*');
1438
+ for (var i = 0; i < candidates.length; i++) {
1439
+ var c = candidates[i];
1440
+ if (c.isContentEditable || c.getAttribute('contenteditable') === 'true') {
1441
+ el = c; break;
1442
+ }
1443
+ }
1444
+ }
1445
+ if (!el) return 'null';
1446
+
1447
+ el.scrollIntoView({ block: 'center', behavior: 'instant' });
1448
+ var r = el.getBoundingClientRect();
1449
+ if (r.width < 1 || r.height < 1) return JSON.stringify({ error: 'Zero size' });
1450
+
1451
+ return JSON.stringify({
1452
+ x: r.x + r.width / 2,
1453
+ y: r.y + r.height / 2,
1454
+ width: r.width,
1455
+ height: r.height
1456
+ });
1457
+ })()
1458
+ `,
1459
+ contextId,
1460
+ returnByValue: true
1461
+ });
1462
+ const value = frameResult.result?.value;
1463
+ if (!value || value === "null") return null;
1464
+ const parsed = JSON.parse(value);
1465
+ if (parsed.error) return parsed;
1466
+ return {
1467
+ x: parsed.x + offset.x,
1468
+ y: parsed.y + offset.y,
1469
+ width: parsed.width,
1470
+ height: parsed.height
1471
+ };
1121
1472
  } catch {
1122
1473
  return null;
1123
1474
  }
@@ -1204,11 +1555,23 @@ Refs:
1204
1555
  await new Promise((r) => setTimeout(r, 50));
1205
1556
  await this.pressKey("Backspace");
1206
1557
  await new Promise((r) => setTimeout(r, 50));
1207
- const cleared = await this.send("Runtime.evaluate", {
1558
+ const frameContextId = this.frameContexts.get(refId);
1559
+ const clearEvalOpts = {
1208
1560
  expression: `
1209
1561
  (function() {
1210
1562
  var el = document.querySelector('[data-assistme-ref="${refId}"]');
1211
1563
  if (!el) return 'no_element';
1564
+
1565
+ // For contenteditable elements, check textContent instead of value
1566
+ if (el.isContentEditable || el.getAttribute('contenteditable') === 'true') {
1567
+ if (el.textContent && el.textContent.trim() !== '') {
1568
+ el.textContent = '';
1569
+ el.dispatchEvent(new Event('input', { bubbles: true }));
1570
+ return 'js_cleared';
1571
+ }
1572
+ return 'ok';
1573
+ }
1574
+
1212
1575
  if (el.value !== undefined && el.value !== '') {
1213
1576
  // Ctrl+A didn't work (some frameworks intercept it) \u2014 clear via JS
1214
1577
  var setter = Object.getOwnPropertyDescriptor(
@@ -1226,9 +1589,13 @@ Refs:
1226
1589
  })()
1227
1590
  `,
1228
1591
  returnByValue: true
1229
- });
1592
+ };
1593
+ if (frameContextId) {
1594
+ clearEvalOpts.contextId = frameContextId;
1595
+ }
1596
+ const cleared = await this.send("Runtime.evaluate", clearEvalOpts);
1230
1597
  const clearStatus = cleared.result?.value || "ok";
1231
- if (clearStatus === "no_element") {
1598
+ if (clearStatus === "no_element" && !frameContextId) {
1232
1599
  return {
1233
1600
  success: false,
1234
1601
  message: `Ref ${refLabel} not found after click. Take a new snapshot.`
@@ -1989,51 +2356,22 @@ import ora3 from "ora";
1989
2356
  import { createInterface as createInterface2 } from "readline";
1990
2357
 
1991
2358
  // src/agent/scheduler.ts
1992
- var SCHEDULER_INTERVAL = 3e4;
2359
+ import { Cron } from "croner";
1993
2360
  function getNextRunTime(cronExpr, timezone, fromDate) {
1994
2361
  const now = fromDate || /* @__PURE__ */ new Date();
1995
- const parts = cronExpr.trim().split(/\s+/);
1996
- if (parts.length !== 5) {
1997
- throw new Error(`Invalid cron expression: ${cronExpr}`);
1998
- }
1999
- const [minExpr, hourExpr, domExpr, monExpr, dowExpr] = parts;
2000
- function parseField(expr, min, max) {
2001
- const values = [];
2002
- for (const part of expr.split(",")) {
2003
- if (part === "*") {
2004
- for (let i = min; i <= max; i++) values.push(i);
2005
- } else if (part.startsWith("*/")) {
2006
- const step = parseInt(part.slice(2));
2007
- for (let i = min; i <= max; i += step) values.push(i);
2008
- } else if (part.includes("-")) {
2009
- const [start, end] = part.split("-").map(Number);
2010
- for (let i = start; i <= end; i++) values.push(i);
2011
- } else {
2012
- values.push(parseInt(part));
2013
- }
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}`);
2014
2367
  }
2015
- return values.sort((a, b) => a - b);
2016
- }
2017
- const minutes = parseField(minExpr, 0, 59);
2018
- const hours = parseField(hourExpr, 0, 23);
2019
- const daysOfMonth = parseField(domExpr, 1, 31);
2020
- const months = parseField(monExpr, 1, 12);
2021
- const daysOfWeek = parseField(dowExpr, 0, 6);
2022
- const useUTC = timezone === "UTC";
2023
- const candidate = new Date(now.getTime() + 6e4);
2024
- candidate.setSeconds(0, 0);
2025
- for (let i = 0; i < 527040; i++) {
2026
- const m = useUTC ? candidate.getUTCMinutes() : candidate.getMinutes();
2027
- const h = useUTC ? candidate.getUTCHours() : candidate.getHours();
2028
- const dom = useUTC ? candidate.getUTCDate() : candidate.getDate();
2029
- const mon = (useUTC ? candidate.getUTCMonth() : candidate.getMonth()) + 1;
2030
- const dow = useUTC ? candidate.getUTCDay() : candidate.getDay();
2031
- if (minutes.includes(m) && hours.includes(h) && daysOfMonth.includes(dom) && months.includes(mon) && (dowExpr === "*" || daysOfWeek.includes(dow))) {
2032
- return candidate;
2033
- }
2034
- candidate.setTime(candidate.getTime() + 6e4);
2035
- }
2036
- 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
+ }
2037
2375
  }
2038
2376
  var Scheduler = class {
2039
2377
  timer = null;
@@ -2043,7 +2381,7 @@ var Scheduler = class {
2043
2381
  this.onScheduledTask = onScheduledTask;
2044
2382
  this.running = true;
2045
2383
  await this.initializeNextRuns();
2046
- this.timer = setInterval(() => this.checkDueTasks(), SCHEDULER_INTERVAL);
2384
+ this.timer = setInterval(() => this.checkDueTasks(), SCHEDULER_INTERVAL_MS);
2047
2385
  log.info("Scheduler started (checking every 30s)");
2048
2386
  }
2049
2387
  stop() {
@@ -2066,7 +2404,7 @@ var Scheduler = class {
2066
2404
  }
2067
2405
  }
2068
2406
  } catch (err) {
2069
- log.debug(`Scheduler init: ${err}`);
2407
+ log.debug(`Scheduler init: ${errorMessage(err)}`);
2070
2408
  }
2071
2409
  }
2072
2410
  async checkDueTasks() {
@@ -2090,7 +2428,7 @@ var Scheduler = class {
2090
2428
  last_error: null
2091
2429
  });
2092
2430
  } catch (err) {
2093
- const errMsg = err instanceof Error ? err.message : String(err);
2431
+ const errMsg = errorMessage(err);
2094
2432
  await callMcpHandler("schedule.update", {
2095
2433
  task_id: task.id,
2096
2434
  last_error: errMsg
@@ -2098,7 +2436,7 @@ var Scheduler = class {
2098
2436
  log.error(`Scheduled task "${task.name}" failed: ${errMsg}`);
2099
2437
  }
2100
2438
  } catch (err) {
2101
- log.debug(`Scheduler check error: ${err}`);
2439
+ log.debug(`Scheduler check error: ${errorMessage(err)}`);
2102
2440
  }
2103
2441
  }
2104
2442
  };
@@ -2618,7 +2956,7 @@ var SkillManager = class {
2618
2956
  userId = null;
2619
2957
  /** Cache for findRelevant() — keyed by prompt, invalidated on skill changes */
2620
2958
  relevanceCache = /* @__PURE__ */ new Map();
2621
- DESCRIPTION_BUDGET_CHARS = 16e3;
2959
+ DESCRIPTION_BUDGET_CHARS = SKILL_DESCRIPTION_BUDGET_CHARS;
2622
2960
  setUserId(userId) {
2623
2961
  this.userId = userId;
2624
2962
  }
@@ -2627,7 +2965,9 @@ var SkillManager = class {
2627
2965
  try {
2628
2966
  const data = await callMcpHandler("skill.load");
2629
2967
  this.skills.clear();
2630
- for (const row of data || []) {
2968
+ for (const raw of data || []) {
2969
+ const row = safeParse(SkillRowSchema, raw);
2970
+ if (!row) continue;
2631
2971
  const skill = this.rowToSkill(row);
2632
2972
  this.skills.set(skill.name, skill);
2633
2973
  }
@@ -2641,22 +2981,22 @@ var SkillManager = class {
2641
2981
  }
2642
2982
  rowToSkill(row) {
2643
2983
  return {
2644
- name: row.name,
2645
- description: row.description || "",
2646
- version: row.version || "1.0.0",
2984
+ name: String(row.name),
2985
+ description: String(row.description ?? ""),
2986
+ version: String(row.version ?? "1.0.0"),
2647
2987
  userInvocable: row.user_invocable !== false,
2648
2988
  disableModelInvocation: row.disable_model_invocation === true,
2649
- keywords: row.keywords || [],
2650
- allowedTools: row.allowed_tools || [],
2651
- 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 ?? ""),
2652
2992
  metadata: parseDbMetadata(row.metadata),
2653
- homepage: row.homepage || "",
2654
- content: row.content,
2993
+ homepage: String(row.homepage ?? ""),
2994
+ content: String(row.content ?? ""),
2655
2995
  filePath: "",
2656
2996
  source: row.source || "manual",
2657
- dbId: row.id,
2658
- sourceSkillId: row.source_skill_id || void 0,
2659
- 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
2660
3000
  };
2661
3001
  }
2662
3002
  /** Invalidate caches when skills change (create, add, update, remove). */
@@ -2786,13 +3126,14 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
2786
3126
  metadata
2787
3127
  }
2788
3128
  );
2789
- const row = Array.isArray(data) ? data[0] : data;
3129
+ const raw = Array.isArray(data) ? data[0] : data;
3130
+ const row = safeParse(SkillCreateResultSchema, raw);
2790
3131
  if (!row) {
2791
- log.debug(`Skill create returned no data for "${name}"`);
3132
+ log.debug(`Skill create returned invalid data for "${name}"`);
2792
3133
  return null;
2793
3134
  }
2794
3135
  const id = row.out_id || row.id;
2795
- const skillName = row.out_name || row.name;
3136
+ const skillName = row.out_name || row.name || name;
2796
3137
  this.skills.set(skillName, {
2797
3138
  name: skillName,
2798
3139
  description,
@@ -2962,11 +3303,11 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
2962
3303
  });
2963
3304
  if (data) {
2964
3305
  return data.map((row) => ({
2965
- name: row.name,
2966
- description: row.description || "",
2967
- emoji: row.emoji || "",
2968
- source: row.source || "manual",
2969
- 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
2970
3311
  }));
2971
3312
  }
2972
3313
  } catch {
@@ -3029,17 +3370,17 @@ _(${skills.length - included} additional skills available \u2014 use skill_searc
3029
3370
  limit: options?.limit || 20,
3030
3371
  offset: options?.offset || 0
3031
3372
  });
3032
- return (data || []).map((r) => ({
3373
+ return (data || []).map((r) => safeParse(BrowseSkillRowSchema, r)).filter(Boolean).map((r) => ({
3033
3374
  id: r.id,
3034
3375
  name: r.name,
3035
- description: r.description || "",
3036
- emoji: r.emoji || "",
3037
- version: r.version || "1.0.0",
3038
- authorName: r.author_name || "",
3039
- category: r.category || "",
3040
- installCount: r.install_count || 0,
3041
- avgRating: r.avg_rating != null ? r.avg_rating : null,
3042
- 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
3043
3384
  }));
3044
3385
  } catch {
3045
3386
  return [];
@@ -3064,8 +3405,12 @@ function substituteArguments(content, args) {
3064
3405
  content = content.replace(/\$(\d+)(?!\w)/g, (_, i) => parts[parseInt(i)] || "");
3065
3406
  return content;
3066
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+)/;
3067
3409
  function preprocessDynamicContext(content, cwd) {
3068
3410
  return content.replace(/!`([^`]+)`/g, (_, cmd) => {
3411
+ if (!SAFE_DYNAMIC_COMMANDS.test(cmd.trim())) {
3412
+ return `[command blocked: ${cmd}]`;
3413
+ }
3069
3414
  try {
3070
3415
  return execSync2(cmd, { timeout: 1e4, encoding: "utf-8", cwd }).trim();
3071
3416
  } catch {
@@ -3140,7 +3485,9 @@ Respond with a JSON object now.`;
3140
3485
  } else if (message.type === "result") {
3141
3486
  const resultMsg = message;
3142
3487
  if (resultMsg.subtype === "success" && "total_cost_usd" in resultMsg) {
3143
- 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
+ );
3144
3491
  }
3145
3492
  }
3146
3493
  }
@@ -3149,13 +3496,9 @@ Respond with a JSON object now.`;
3149
3496
  log.debug("Skill evaluation: no valid JSON in response");
3150
3497
  return;
3151
3498
  }
3152
- if (!["create", "update", "skip"].includes(decision.action)) {
3153
- log.debug("Skill evaluation: invalid action");
3154
- return;
3155
- }
3156
3499
  await executeSkillDecision(decision, skillManager);
3157
3500
  } catch (err) {
3158
- log.debug(`Skill evaluation error: ${err}`);
3501
+ log.debug(`Skill evaluation error: ${errorMessage(err)}`);
3159
3502
  }
3160
3503
  }
3161
3504
  async function executeSkillDecision(decision, skillManager) {
@@ -3165,13 +3508,17 @@ async function executeSkillDecision(decision, skillManager) {
3165
3508
  log.debug("Skill create skipped: missing name or instructions");
3166
3509
  return;
3167
3510
  }
3168
- let skillName = decision.name;
3169
- if (validateSkillName(skillName)) {
3170
- skillName = normalizeSkillName(skillName);
3171
- if (!skillName || validateSkillName(skillName)) {
3172
- log.debug(`Skill create skipped: name "${decision.name}" cannot be normalized`);
3173
- return;
3174
- }
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) {
3175
3522
  log.debug(`Normalized skill name: "${decision.name}" \u2192 "${skillName}"`);
3176
3523
  }
3177
3524
  const existing = skillManager.findSimilar(skillName);
@@ -3230,25 +3577,28 @@ async function executeSkillDecision(decision, skillManager) {
3230
3577
  }
3231
3578
  function parseJsonResponse(text) {
3232
3579
  const trimmed = text.trim();
3233
- try {
3234
- const parsed = JSON.parse(trimmed);
3235
- if (parsed.action) return parsed;
3236
- } catch {
3237
- }
3580
+ const candidates = [trimmed];
3238
3581
  const start = trimmed.indexOf("{");
3239
- if (start === -1) return null;
3240
- let depth = 0;
3241
- for (let i = start; i < trimmed.length; i++) {
3242
- if (trimmed[i] === "{") depth++;
3243
- else if (trimmed[i] === "}") depth--;
3244
- if (depth === 0) {
3245
- try {
3246
- return JSON.parse(trimmed.slice(start, i + 1));
3247
- } catch {
3248
- 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;
3249
3590
  }
3250
3591
  }
3251
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
+ }
3252
3602
  return null;
3253
3603
  }
3254
3604
 
@@ -3295,14 +3645,16 @@ import { z } from "zod/v4";
3295
3645
 
3296
3646
  // src/tools/filesystem.ts
3297
3647
  import { readFile, writeFile, readdir, stat, mkdir } from "fs/promises";
3298
- import { resolve, relative, join as join2 } from "path";
3648
+ import { resolve, relative, join as join2, sep } from "path";
3299
3649
  import { glob } from "glob";
3300
3650
  function assertWithinWorkspace(filePath) {
3301
3651
  const config = getConfig();
3302
3652
  const resolved = resolve(config.workspacePath, filePath);
3303
- if (!resolved.startsWith(config.workspacePath)) {
3304
- throw new Error(
3305
- `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"
3306
3658
  );
3307
3659
  }
3308
3660
  return resolved;
@@ -3332,7 +3684,7 @@ async function searchFiles(pattern, directory) {
3332
3684
  ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
3333
3685
  });
3334
3686
  if (matches.length === 0) return "No files found matching the pattern.";
3335
- 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");
3336
3688
  }
3337
3689
  async function listDirectory(path) {
3338
3690
  const config = getConfig();
@@ -3342,9 +3694,7 @@ async function listDirectory(path) {
3342
3694
  for (const entry of entries) {
3343
3695
  if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
3344
3696
  const icon = entry.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}";
3345
- const info = entry.isFile() ? await stat(join2(resolved, entry.name)).then(
3346
- (s) => ` (${formatSize(s.size)})`
3347
- ) : "";
3697
+ const info = entry.isFile() ? await stat(join2(resolved, entry.name)).then((s) => ` (${formatSize(s.size)})`) : "";
3348
3698
  results.push(`${icon} ${entry.name}${info}`);
3349
3699
  }
3350
3700
  return results.join("\n") || "Empty directory.";
@@ -3362,9 +3712,14 @@ async function searchContent(pattern, fileGlob, directory) {
3362
3712
  nodir: true,
3363
3713
  ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
3364
3714
  });
3365
- 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
+ }
3366
3721
  const results = [];
3367
- for (const file of files.slice(0, 200)) {
3722
+ for (const file of files.slice(0, MAX_CONTENT_SEARCH_FILES)) {
3368
3723
  try {
3369
3724
  const content = await readFile(join2(cwd, file), "utf-8");
3370
3725
  const lines = content.split("\n");
@@ -3373,10 +3728,10 @@ async function searchContent(pattern, fileGlob, directory) {
3373
3728
  const relPath = relative(config.workspacePath, join2(cwd, file));
3374
3729
  results.push(`${relPath}:${i + 1}: ${lines[i].trim()}`);
3375
3730
  regex.lastIndex = 0;
3376
- if (results.length >= 30) break;
3731
+ if (results.length >= MAX_CONTENT_SEARCH_RESULTS) break;
3377
3732
  }
3378
3733
  }
3379
- if (results.length >= 30) break;
3734
+ if (results.length >= MAX_CONTENT_SEARCH_RESULTS) break;
3380
3735
  } catch {
3381
3736
  }
3382
3737
  }
@@ -3385,8 +3740,6 @@ async function searchContent(pattern, fileGlob, directory) {
3385
3740
 
3386
3741
  // src/tools/shell.ts
3387
3742
  import { exec } from "child_process";
3388
- var TIMEOUT_MS = 3e4;
3389
- var MAX_OUTPUT = 5e4;
3390
3743
  var BLOCKED_PATTERNS = [
3391
3744
  /rm\s+(-\w*\s+)*-\w*r\w*\s+\/($|\s)/i,
3392
3745
  // rm -rf /, rm -fr /, etc.
@@ -3414,7 +3767,7 @@ function isBlocked(command) {
3414
3767
  }
3415
3768
  async function executeShell(command, cwd) {
3416
3769
  if (isBlocked(command)) {
3417
- throw new Error(`Command blocked for safety: "${command}"`);
3770
+ throw new AppError(`Command blocked for safety: "${command}"`, "COMMAND_BLOCKED");
3418
3771
  }
3419
3772
  const config = getConfig();
3420
3773
  const workDir = cwd || config.workspacePath;
@@ -3423,7 +3776,7 @@ async function executeShell(command, cwd) {
3423
3776
  command,
3424
3777
  {
3425
3778
  cwd: workDir,
3426
- timeout: TIMEOUT_MS,
3779
+ timeout: SHELL_TIMEOUT_MS,
3427
3780
  maxBuffer: 1024 * 1024,
3428
3781
  // 1MB buffer
3429
3782
  env: { ...process.env, TERM: "dumb" }
@@ -3441,10 +3794,10 @@ ${stderr}` : "";
3441
3794
  if (error && !stdout && !stderr) {
3442
3795
  output = `Error: ${error.message}`;
3443
3796
  }
3444
- if (output.length > MAX_OUTPUT) {
3445
- output = output.slice(0, MAX_OUTPUT) + `
3797
+ if (output.length > SHELL_MAX_OUTPUT) {
3798
+ output = output.slice(0, SHELL_MAX_OUTPUT) + `
3446
3799
 
3447
- [Output truncated at ${MAX_OUTPUT} bytes]`;
3800
+ [Output truncated at ${SHELL_MAX_OUTPUT} bytes]`;
3448
3801
  }
3449
3802
  resolve2(output || "(no output)");
3450
3803
  }
@@ -3811,7 +4164,7 @@ function createBrowserMcpServer() {
3811
4164
  ),
3812
4165
  tool(
3813
4166
  "browser_type",
3814
- "Type text into an input field in the user's browser.",
4167
+ "Type text into an input field in the user's browser. If the CSS selector fails, automatically falls back to typing into the currently focused element. Works with contenteditable elements (rich text editors) and cross-origin iframes.",
3815
4168
  {
3816
4169
  selector: z.string().describe("CSS selector of the input element"),
3817
4170
  text: z.string().describe("Text to type")
@@ -5104,7 +5457,7 @@ function createEventHooks(taskId, toolCallRecords) {
5104
5457
  const rawName = preInput.tool_name;
5105
5458
  const displayName = stripMcpPrefix(rawName);
5106
5459
  const toolInput = preInput.tool_input;
5107
- log.tool(displayName, JSON.stringify(toolInput).slice(0, 200));
5460
+ log.tool(displayName, JSON.stringify(toolInput).slice(0, MAX_TOOL_INPUT_LOG_LENGTH));
5108
5461
  await emitEvent(taskId, "tool_use_start", { name: displayName });
5109
5462
  await emitEvent(taskId, "tool_use_input", { input: toolInput });
5110
5463
  if (displayName === "browser_request_user_action") {
@@ -5123,15 +5476,15 @@ function createEventHooks(taskId, toolCallRecords) {
5123
5476
  const toolInput = postInput.tool_input;
5124
5477
  const toolResponse = postInput.tool_response;
5125
5478
  const resultStr = typeof toolResponse === "string" ? toolResponse : JSON.stringify(toolResponse);
5126
- log.result(resultStr.slice(0, 200));
5479
+ log.result(resultStr.slice(0, MAX_TOOL_INPUT_LOG_LENGTH));
5127
5480
  await emitEvent(taskId, "tool_result", {
5128
5481
  name: displayName,
5129
- result: resultStr.slice(0, 1e4)
5482
+ result: resultStr.slice(0, MAX_TOOL_RESULT_LENGTH)
5130
5483
  });
5131
5484
  toolCallRecords.push({
5132
5485
  name: displayName,
5133
5486
  input: toolInput || {},
5134
- result: resultStr.slice(0, 300)
5487
+ result: resultStr.slice(0, MAX_SKILL_RECORD_RESULT_LENGTH)
5135
5488
  });
5136
5489
  return {};
5137
5490
  };
@@ -5310,8 +5663,6 @@ var TaskTimeout = class {
5310
5663
  }
5311
5664
  }
5312
5665
  };
5313
- var MAX_HISTORY_ENTRIES = 10;
5314
- var MAX_RESPONSE_LENGTH = 1500;
5315
5666
  var TaskProcessor = class {
5316
5667
  memoryManager = null;
5317
5668
  skillManager;
@@ -5346,7 +5697,7 @@ var TaskProcessor = class {
5346
5697
  async processTask(task) {
5347
5698
  const config = getConfig();
5348
5699
  resetEventSequence();
5349
- const taskTimeoutMs = (config.taskTimeoutMinutes || 10) * 6e4;
5700
+ const taskTimeoutMs = config.taskTimeoutMinutes * 6e4;
5350
5701
  newCorrelationId();
5351
5702
  log.info(`Processing task ${task.id.slice(0, 8)}...`);
5352
5703
  let finalResponse = "";
@@ -5385,7 +5736,7 @@ var TaskProcessor = class {
5385
5736
  for (const entry of history) {
5386
5737
  historyPrompt += `User: ${entry.prompt}
5387
5738
  `;
5388
- 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;
5389
5740
  historyPrompt += `Assistant: ${truncated}
5390
5741
 
5391
5742
  `;
@@ -5464,16 +5815,11 @@ var TaskProcessor = class {
5464
5815
  persistSession: true,
5465
5816
  abortController
5466
5817
  };
5467
- const taskStartTime = Date.now();
5468
5818
  try {
5469
5819
  for await (const message of query2({
5470
5820
  prompt: promptMessages(),
5471
5821
  options
5472
5822
  })) {
5473
- if (Date.now() - taskStartTime > taskTimeoutMs) {
5474
- finalResponse += "\n\n[Task timed out]";
5475
- break;
5476
- }
5477
5823
  switch (message.type) {
5478
5824
  case "assistant": {
5479
5825
  const assistantMsg = message;
@@ -5485,7 +5831,8 @@ var TaskProcessor = class {
5485
5831
  text: block.text
5486
5832
  });
5487
5833
  } else if (block.type === "thinking" && "thinking" in block) {
5488
- const thinkingText = block.thinking;
5834
+ const thinkingBlock = block;
5835
+ const thinkingText = thinkingBlock.thinking;
5489
5836
  log.debug(`Thinking: ${thinkingText.slice(0, 100)}...`);
5490
5837
  await emitEvent(task.id, "thinking", {
5491
5838
  text: thinkingText
@@ -5518,8 +5865,11 @@ var TaskProcessor = class {
5518
5865
  break;
5519
5866
  }
5520
5867
  default:
5521
- if (message.type === "system" && "subtype" in message && message.subtype === "init") {
5522
- 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
+ }
5523
5873
  }
5524
5874
  log.debug(`SDK message type: ${message.type}`);
5525
5875
  break;
@@ -5528,10 +5878,9 @@ var TaskProcessor = class {
5528
5878
  } finally {
5529
5879
  taskTimeout.clear();
5530
5880
  }
5531
- const MAX_CONTENT_LENGTH = 5e4;
5532
- 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;
5533
5882
  await withRetry(() => completeTask(task.id, truncatedResponse, tokenUsage), {
5534
- maxRetries: 2,
5883
+ maxRetries: MAX_COMPLETE_TASK_RETRIES,
5535
5884
  baseDelayMs: 300,
5536
5885
  label: "completeTask"
5537
5886
  });
@@ -5549,7 +5898,7 @@ var TaskProcessor = class {
5549
5898
  );
5550
5899
  }
5551
5900
  } catch (err) {
5552
- const errorMsg = err instanceof Error ? err.message : String(err);
5901
+ const errorMsg = errorMessage(err);
5553
5902
  log.error(`Task failed: ${errorMsg}`);
5554
5903
  await failTask(task.id, errorMsg);
5555
5904
  await emitEvent(task.id, "error", { message: errorMsg });
@@ -6053,7 +6402,7 @@ function registerJobCommands(program2) {
6053
6402
  jobCmd.command("list").description("List your defined jobs").action(async () => {
6054
6403
  try {
6055
6404
  const userId = await getCurrentUserId();
6056
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6405
+ const { JobRunner: JobRunner2 } = await import("./job-runner-JT3JWZBV.js");
6057
6406
  const runner = new JobRunner2();
6058
6407
  const jobs = await runner.listJobs();
6059
6408
  if (jobs.length === 0) {
@@ -6077,7 +6426,7 @@ function registerJobCommands(program2) {
6077
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) => {
6078
6427
  try {
6079
6428
  const userId = await getCurrentUserId();
6080
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6429
+ const { JobRunner: JobRunner2 } = await import("./job-runner-JT3JWZBV.js");
6081
6430
  const runner = new JobRunner2();
6082
6431
  const runs = await runner.getRunHistory(name, parseInt(opts.limit || "5"));
6083
6432
  if (runs.length === 0) {
@@ -6116,7 +6465,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`));
6116
6465
  process.exit(1);
6117
6466
  }
6118
6467
  const userId = await getCurrentUserId();
6119
- const { JobRunner: JobRunner2 } = await import("./job-runner-P2L6MOOX.js");
6468
+ const { JobRunner: JobRunner2 } = await import("./job-runner-JT3JWZBV.js");
6120
6469
  const runner = new JobRunner2();
6121
6470
  const job = await runner.loadJob(name);
6122
6471
  if (!job) {