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/{chunk-KX7ITO55.js → chunk-4YWS463E.js} +174 -22
- package/dist/{chunk-TTEGHE2E.js → chunk-JVA6DHXD.js} +6 -4
- package/dist/{config-PUIS2TQL.js → config-T4357GAE.js} +1 -1
- package/dist/index.js +165 -155
- package/dist/job-runner-JT3JWZBV.js +7 -0
- package/package.json +2 -1
- package/src/agent/event-hooks.ts +16 -8
- package/src/agent/job-runner.ts +52 -40
- package/src/agent/processor.ts +24 -27
- package/src/agent/scheduler.ts +22 -59
- package/src/agent/skill-evaluator.ts +45 -52
- package/src/agent/skills.ts +57 -36
- package/src/browser/controller.ts +16 -5
- package/src/tools/filesystem.ts +32 -35
- package/src/tools/shell.ts +18 -22
- package/src/utils/config.test.ts +1 -1
- package/src/utils/config.ts +15 -9
- package/src/utils/constants.ts +77 -0
- package/src/utils/errors.ts +37 -0
- package/src/utils/schemas.ts +115 -0
- package/dist/job-runner-P2L6MOOX.js +0 -7
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-
|
|
35
|
+
} from "./chunk-4YWS463E.js";
|
|
12
36
|
import {
|
|
13
37
|
clearConfig,
|
|
14
38
|
getConfig,
|
|
15
39
|
getConfigPath,
|
|
16
40
|
setConfig
|
|
17
|
-
} from "./chunk-
|
|
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-
|
|
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,
|
|
162
|
+
async function failTask(messageId, errorMessage2) {
|
|
139
163
|
try {
|
|
140
164
|
await callMcpHandler("task.fail", {
|
|
141
165
|
message_id: messageId,
|
|
142
|
-
error:
|
|
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(
|
|
439
|
+
reject(new Error(`Connection timeout (${WS_CONNECT_TIMEOUT_MS}ms)`));
|
|
416
440
|
}
|
|
417
|
-
},
|
|
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
|
-
},
|
|
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.
|
|
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
|
-
|
|
2359
|
+
import { Cron } from "croner";
|
|
2332
2360
|
function getNextRunTime(cronExpr, timezone, fromDate) {
|
|
2333
2361
|
const now = fromDate || /* @__PURE__ */ new Date();
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
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(),
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
2998
|
-
invocationCount: row.invocation_count
|
|
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
|
|
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
|
|
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
|
|
3308
|
-
invocationCount: row.invocation_count
|
|
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
|
|
3377
|
-
authorName: r.author_name
|
|
3378
|
-
category: r.category
|
|
3379
|
-
installCount: r.install_count
|
|
3380
|
-
avgRating: r.avg_rating
|
|
3381
|
-
ratingCount: r.rating_count
|
|
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(
|
|
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 (
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
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
|
-
|
|
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
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
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
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 >=
|
|
3731
|
+
if (results.length >= MAX_CONTENT_SEARCH_RESULTS) break;
|
|
3716
3732
|
}
|
|
3717
3733
|
}
|
|
3718
|
-
if (results.length >=
|
|
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
|
|
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:
|
|
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 >
|
|
3784
|
-
output = output.slice(0,
|
|
3797
|
+
if (output.length > SHELL_MAX_OUTPUT) {
|
|
3798
|
+
output = output.slice(0, SHELL_MAX_OUTPUT) + `
|
|
3785
3799
|
|
|
3786
|
-
[Output truncated at ${
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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 >
|
|
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
|
|
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
|
|
5861
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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-
|
|
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-
|
|
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-
|
|
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) {
|