nodal-agents 0.7.2 → 0.7.5
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/cli.js +118 -2
- package/migrations/0058_entity_llm_keys_context_window.sql +6 -0
- package/migrations/0059_agent_jobs_conversation_id.sql +10 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/runner.js +1435 -571
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +9 -9
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/routes-manifest.json +32 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +2 -2
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
- package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
- package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
- package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/chat/page.js +2 -2
- package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/connectors/page.js +1 -1
- package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page.js +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
- package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
- package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/learned-skills/page.js +2 -2
- package/web/.next/server/app/(dashboard)/learned-skills/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/learned-skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/llm-providers/page.js +1 -1
- package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/logs/page.js +2 -2
- package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
- package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
- package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page.js +2 -2
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/settings/page.js +2 -2
- package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/settings/root-context/page.js +2 -2
- package/web/.next/server/app/(dashboard)/settings/root-context/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/settings/root-context/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +1 -1
- package/web/.next/server/app/_global-error.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/api/auth/[...all]/route.js +1 -1
- package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
- package/web/.next/server/app/api/oauth/[provider]/callback/route.js.nft.json +1 -1
- package/web/.next/server/app/api/oauth/[provider]/start/route.js +1 -1
- package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/onboarding/page.js +201 -193
- package/web/.next/server/app/onboarding/page.js.nft.json +1 -1
- package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app-paths-manifest.json +9 -9
- package/web/.next/server/chunks/2043.js +1 -0
- package/web/.next/server/chunks/2046.js +1 -0
- package/web/.next/server/chunks/2713.js +26 -18
- package/web/.next/server/chunks/4479.js +1 -0
- package/web/.next/server/chunks/4574.js +1 -1
- package/web/.next/server/chunks/{8810.js → 5744.js} +5 -4
- package/web/.next/server/chunks/{8464.js → 7388.js} +1 -2
- package/web/.next/server/chunks/7560.js +1 -0
- package/web/.next/server/middleware-build-manifest.js +1 -1
- package/web/.next/server/pages/404.html +1 -1
- package/web/.next/server/pages/500.html +1 -1
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/chunks/3114-d846062bc8434b58.js +1 -0
- package/web/.next/static/chunks/4893-730f4c62f76c6c48.js +1 -0
- package/web/.next/static/chunks/8097-bad68cd78aa59a10.js +1 -0
- package/web/.next/static/chunks/{9836-177f6ca2f15ff703.js → 9836-a69e19544b669a71.js} +1 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-18973089c62eb12e.js +6 -0
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-7913483b3bfd7dda.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/agents/page-eb7b72d4d7bfde01.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/approvals/{page-b3a838ecbc94a030.js → page-dc8dc54ed07bc81a.js} +1 -1
- package/web/.next/static/chunks/app/(dashboard)/automations/page-1034613ecd525509.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/chat/page-4d0e2fd060f2aafb.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/connectors/{page-2c6f913cc6aea4b7.js → page-8bad5bc43f197471.js} +1 -1
- package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-116a001adade3a1e.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/jobs/page-0fc661a4e7588bdb.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/layout-9068b164d3ae4853.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-01f1b68b334bf442.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-99fd3e7b631131e8.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/logs/page-94babd54bdd0d1af.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/mcp/page-2d190bdd49b7b4b4.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/memories/page-ac872edd26efe323.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/page-8cd3f1d4d072ac25.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/settings/page-aed8fc039205ccd7.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-a2eb786f9d897813.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-7f87e2c66d1ceea4.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/new/page-54734a6e0675887a.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/page-cae26ae45b9b11e5.js +1 -0
- package/web/.next/static/chunks/app/onboarding/{page-f2f04878e81fdf23.js → page-e63eee720a95baac.js} +2 -2
- package/web/.next/static/css/{4d808b91d7b58fce.css → 289a2e89c0d093fd.css} +1 -1
- package/web/.next/server/chunks/3223.js +0 -1
- package/web/.next/server/chunks/5515.js +0 -1
- package/web/.next/server/chunks/6468.js +0 -1
- package/web/.next/server/chunks/8410.js +0 -1
- package/web/.next/static/chunks/4964-c6711d3d479b6085.js +0 -1
- package/web/.next/static/chunks/609-a0c06f3592427d58.js +0 -1
- package/web/.next/static/chunks/7704-7ba1c3d45662ffc8.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-cfce874f4acede5e.js +0 -6
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-9249c2839f551ba1.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/page-7d784d737acedccf.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/automations/page-b1b548de4f52696c.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/chat/page-bcf6dcc73b370eb0.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-43f033251eddcc4b.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/jobs/page-1f892f950bb30403.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/layout-e184673151ff8a05.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-f873bb6f8539e47e.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-64adda30176f4ccb.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/logs/page-79cfd9141c39d8ef.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/mcp/page-d276d7bab25286a1.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/memories/page-c66720494a340503.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/page-3649204dd27a0ad0.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/settings/page-1cab404d2eccfd80.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-b5898c5444f88c9a.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-82d91fc6b6acc161.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/new/page-58b5ed04583ca7d4.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/page-312bd4ae8ddc8114.js +0 -1
- /package/web/.next/static/{SgnP_MouhUmw0bBf-lUmD → XF1rXMlGkoyAu_GI5theh}/_buildManifest.js +0 -0
- /package/web/.next/static/{SgnP_MouhUmw0bBf-lUmD → XF1rXMlGkoyAu_GI5theh}/_ssgManifest.js +0 -0
package/runner.js
CHANGED
|
@@ -294,6 +294,10 @@ var init_job = __esm({
|
|
|
294
294
|
task: z7.string().min(1),
|
|
295
295
|
original_task: z7.string().nullable(),
|
|
296
296
|
chat_id: z7.string().nullable(),
|
|
297
|
+
// Groups jobs from the same conversational thread (migration 0059) — see
|
|
298
|
+
// apps/runner/src/job/conversation-id.ts for the stamping rule. Null for
|
|
299
|
+
// non-conversational jobs (cron/schedule/webhook with no parent).
|
|
300
|
+
conversation_id: z7.string().guid().nullable(),
|
|
297
301
|
system_prompt: z7.string().nullable(),
|
|
298
302
|
messages: z7.array(z7.record(z7.string(), z7.unknown())),
|
|
299
303
|
tools_used: z7.array(z7.string()),
|
|
@@ -334,6 +338,7 @@ var init_job = __esm({
|
|
|
334
338
|
status: JobStatusSchema.default("pending"),
|
|
335
339
|
original_task: z7.string().nullable().optional(),
|
|
336
340
|
chat_id: z7.string().nullable().optional(),
|
|
341
|
+
conversation_id: z7.string().guid().nullable().optional(),
|
|
337
342
|
system_prompt: z7.string().nullable().optional(),
|
|
338
343
|
request_id: z7.string().nullable().optional(),
|
|
339
344
|
parent_job_id: z7.string().guid().nullable().optional()
|
|
@@ -341,6 +346,38 @@ var init_job = __esm({
|
|
|
341
346
|
}
|
|
342
347
|
});
|
|
343
348
|
|
|
349
|
+
// ../../packages/shared/src/entities/job-classification.ts
|
|
350
|
+
var CHAT_DELIVERY_TOOLS, CHAT_CONTEXT_READ_TOOLS, CHAT_ALLOWED_TOOLS;
|
|
351
|
+
var init_job_classification = __esm({
|
|
352
|
+
"../../packages/shared/src/entities/job-classification.ts"() {
|
|
353
|
+
"use strict";
|
|
354
|
+
CHAT_DELIVERY_TOOLS = [
|
|
355
|
+
"telegram_send_message",
|
|
356
|
+
"send_image",
|
|
357
|
+
"send_file",
|
|
358
|
+
"send_video",
|
|
359
|
+
"send_audio",
|
|
360
|
+
"send_voice",
|
|
361
|
+
"return_result",
|
|
362
|
+
"dashboard_publish"
|
|
363
|
+
];
|
|
364
|
+
CHAT_CONTEXT_READ_TOOLS = [
|
|
365
|
+
"query_memory",
|
|
366
|
+
"search_history",
|
|
367
|
+
"skill_view",
|
|
368
|
+
"list_models",
|
|
369
|
+
"list_schedules",
|
|
370
|
+
"file_read",
|
|
371
|
+
"file_list",
|
|
372
|
+
"file_search"
|
|
373
|
+
];
|
|
374
|
+
CHAT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
|
|
375
|
+
...CHAT_DELIVERY_TOOLS,
|
|
376
|
+
...CHAT_CONTEXT_READ_TOOLS
|
|
377
|
+
]);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
344
381
|
// ../../packages/shared/src/entities/task.ts
|
|
345
382
|
import { z as z8 } from "zod";
|
|
346
383
|
var TaskDescriptionSchema, TaskTitleSchema, AgentTaskSchema, AgentTaskInsertSchema;
|
|
@@ -446,7 +483,9 @@ var init_connector = __esm({
|
|
|
446
483
|
name: z10.string().min(1).max(120),
|
|
447
484
|
slug: z10.string().min(1).max(80).regex(/^[a-z0-9-]+$/),
|
|
448
485
|
base_url: z10.string().nullable(),
|
|
449
|
-
//
|
|
486
|
+
// audit#2026-07-07 SEC-5: this comment used to claim pgp_sym_encrypt (pgcrypto),
|
|
487
|
+
// which is not used anywhere. Real encryption is application-level AES-256-GCM
|
|
488
|
+
// via @nodal-agents/secrets before the value ever reaches the DB; raw value never returned.
|
|
450
489
|
api_key: z10.string().nullable(),
|
|
451
490
|
active: z10.boolean(),
|
|
452
491
|
auth_type: ConnectorAuthTypeSchema,
|
|
@@ -968,7 +1007,7 @@ function parseRootGrants(raw) {
|
|
|
968
1007
|
autonomy
|
|
969
1008
|
};
|
|
970
1009
|
}
|
|
971
|
-
var AutonomyLevelSchema, RootGrantsSchema, DEFAULT_ROOT_GRANTS, INITIAL_AUTO_ROOT_GRANTS, META_TOOL_BY_GRANT;
|
|
1010
|
+
var AutonomyLevelSchema, RootGrantsSchema, DEFAULT_ROOT_GRANTS, INITIAL_AUTO_ROOT_GRANTS, META_TOOL_BY_GRANT, META_TOOL_NAMES;
|
|
972
1011
|
var init_root_agent = __esm({
|
|
973
1012
|
"../../packages/shared/src/root-agent.ts"() {
|
|
974
1013
|
"use strict";
|
|
@@ -1028,6 +1067,26 @@ var init_root_agent = __esm({
|
|
|
1028
1067
|
attachConnector: ["attach_connector", "detach_connector"],
|
|
1029
1068
|
manageSchedules: ["create_schedule", "update_schedule", "toggle_schedule", "run_schedule"]
|
|
1030
1069
|
};
|
|
1070
|
+
META_TOOL_NAMES = [
|
|
1071
|
+
"create_agent",
|
|
1072
|
+
"update_agent",
|
|
1073
|
+
"attach_agent",
|
|
1074
|
+
"detach_agent",
|
|
1075
|
+
"create_skill",
|
|
1076
|
+
"update_skill",
|
|
1077
|
+
"attach_skill",
|
|
1078
|
+
"detach_skill",
|
|
1079
|
+
"create_mcp",
|
|
1080
|
+
"attach_mcp",
|
|
1081
|
+
"detach_mcp",
|
|
1082
|
+
"create_connector",
|
|
1083
|
+
"attach_connector",
|
|
1084
|
+
"detach_connector",
|
|
1085
|
+
"create_schedule",
|
|
1086
|
+
"update_schedule",
|
|
1087
|
+
"toggle_schedule",
|
|
1088
|
+
"run_schedule"
|
|
1089
|
+
];
|
|
1031
1090
|
}
|
|
1032
1091
|
});
|
|
1033
1092
|
|
|
@@ -1149,8 +1208,13 @@ function modelCanSeeImages(modelId) {
|
|
|
1149
1208
|
function findModelCatalogEntry(provider, modelId) {
|
|
1150
1209
|
return MODEL_CATALOG[provider]?.find((e) => e.modelId === modelId);
|
|
1151
1210
|
}
|
|
1152
|
-
function modelContextWindow(provider, modelId) {
|
|
1153
|
-
|
|
1211
|
+
function modelContextWindow(provider, modelId, storedWindow) {
|
|
1212
|
+
const catalogued = findModelCatalogEntry(provider, modelId)?.contextWindow;
|
|
1213
|
+
if (catalogued !== void 0) return catalogued;
|
|
1214
|
+
if (typeof storedWindow === "number" && Number.isFinite(storedWindow) && storedWindow > 0) {
|
|
1215
|
+
return storedWindow;
|
|
1216
|
+
}
|
|
1217
|
+
return DEFAULT_CONTEXT_WINDOW;
|
|
1154
1218
|
}
|
|
1155
1219
|
function estimateModelCostUsd(provider, modelId, inputTokens, outputTokens) {
|
|
1156
1220
|
const pricing = findModelCatalogEntry(provider, modelId)?.pricing;
|
|
@@ -1503,6 +1567,284 @@ var init_timezone = __esm({
|
|
|
1503
1567
|
}
|
|
1504
1568
|
});
|
|
1505
1569
|
|
|
1570
|
+
// ../../packages/shared/src/redact-secrets.ts
|
|
1571
|
+
function redactSecretsForAudit(value) {
|
|
1572
|
+
if (Array.isArray(value)) return value.map(redactSecretsForAudit);
|
|
1573
|
+
if (value !== null && typeof value === "object") {
|
|
1574
|
+
const out = {};
|
|
1575
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1576
|
+
if (SECRET_FIELD_RE.test(k)) {
|
|
1577
|
+
out[k] = v == null ? v : REDACTED;
|
|
1578
|
+
} else if (k.toLowerCase() === "env" && v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
1579
|
+
const envOut = {};
|
|
1580
|
+
for (const ek of Object.keys(v)) envOut[ek] = REDACTED;
|
|
1581
|
+
out[k] = envOut;
|
|
1582
|
+
} else {
|
|
1583
|
+
out[k] = redactSecretsForAudit(v);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return out;
|
|
1587
|
+
}
|
|
1588
|
+
return value;
|
|
1589
|
+
}
|
|
1590
|
+
var REDACTED, SECRET_FIELD_RE;
|
|
1591
|
+
var init_redact_secrets = __esm({
|
|
1592
|
+
"../../packages/shared/src/redact-secrets.ts"() {
|
|
1593
|
+
"use strict";
|
|
1594
|
+
REDACTED = "***";
|
|
1595
|
+
SECRET_FIELD_RE = /^(api[_-]?key|api[_-]?token|access[_-]?token|refresh[_-]?token|client[_-]?secret|secret|password|token|credential)$/i;
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
// ../../packages/shared/src/catastrophic-command.ts
|
|
1600
|
+
function stripQuotes(token) {
|
|
1601
|
+
return token.replace(/^["']|["']$/g, "");
|
|
1602
|
+
}
|
|
1603
|
+
function normalizeSlashes(s) {
|
|
1604
|
+
return s.replace(/\/{2,}/g, "/").replace(/\\{2,}/g, "\\");
|
|
1605
|
+
}
|
|
1606
|
+
function stripWrapperPrefix(tokens) {
|
|
1607
|
+
let i = 0;
|
|
1608
|
+
while (i < tokens.length && WRAPPER_LEADER.has((tokens[i] ?? "").toLowerCase())) {
|
|
1609
|
+
i += 1;
|
|
1610
|
+
if (i < tokens.length && /^[-/]/.test(tokens[i] ?? "")) {
|
|
1611
|
+
i += 1;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return tokens.slice(i);
|
|
1615
|
+
}
|
|
1616
|
+
function interpreterBasename(token) {
|
|
1617
|
+
const t = stripQuotes(token);
|
|
1618
|
+
const base = t.split(/[\\/]/).pop() ?? t;
|
|
1619
|
+
return base.replace(/\.(exe|com)$/i, "").toLowerCase();
|
|
1620
|
+
}
|
|
1621
|
+
function skipPassthroughLeaders(tokens) {
|
|
1622
|
+
let i = 0;
|
|
1623
|
+
while (i < tokens.length && PASSTHROUGH_LEADERS.has(interpreterBasename(tokens[i] ?? ""))) {
|
|
1624
|
+
i += 1;
|
|
1625
|
+
while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i] ?? "")) i += 1;
|
|
1626
|
+
if (i < tokens.length && /^[-/]/.test(tokens[i] ?? "")) i += 1;
|
|
1627
|
+
}
|
|
1628
|
+
return tokens.slice(i);
|
|
1629
|
+
}
|
|
1630
|
+
function interpreterKind(name) {
|
|
1631
|
+
if (name === "py" || /^python[0-9.]*$/.test(name)) return "python";
|
|
1632
|
+
if (/^node(js)?$/.test(name)) return "node";
|
|
1633
|
+
if (/^perl[0-9.]*$/.test(name)) return "perl";
|
|
1634
|
+
if (/^ruby[0-9.]*$/.test(name)) return "ruby";
|
|
1635
|
+
if (/^php[0-9.]*$/.test(name)) return "php";
|
|
1636
|
+
if (["sh", "bash", "zsh", "ksh", "dash", "ash"].includes(name)) return "shell";
|
|
1637
|
+
if (name === "powershell" || name === "pwsh") return "powershell";
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
function isInlineEvalFlag(kind, flag) {
|
|
1641
|
+
switch (kind) {
|
|
1642
|
+
case "python":
|
|
1643
|
+
return flag === "-c";
|
|
1644
|
+
case "node":
|
|
1645
|
+
return flag === "-e" || flag === "--eval" || flag === "-p" || flag === "--print";
|
|
1646
|
+
case "perl":
|
|
1647
|
+
case "ruby":
|
|
1648
|
+
return flag === "-e";
|
|
1649
|
+
case "php":
|
|
1650
|
+
return flag === "-r";
|
|
1651
|
+
case "shell":
|
|
1652
|
+
return flag === "-c";
|
|
1653
|
+
case "powershell":
|
|
1654
|
+
return /^-com/.test(flag) || /^-enc/.test(flag);
|
|
1655
|
+
default:
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
function hasInlineInterpreterEval(tokens) {
|
|
1660
|
+
const rest = skipPassthroughLeaders(tokens);
|
|
1661
|
+
if (rest.length === 0) return false;
|
|
1662
|
+
const kind = interpreterKind(interpreterBasename(rest[0] ?? ""));
|
|
1663
|
+
if (!kind) return false;
|
|
1664
|
+
return rest.slice(1).some((t) => isInlineEvalFlag(kind, stripQuotes(t).toLowerCase()));
|
|
1665
|
+
}
|
|
1666
|
+
function hasPipeIntoBareInterpreter(c) {
|
|
1667
|
+
const parts = c.split("|");
|
|
1668
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1669
|
+
const toks = (parts[i] ?? "").trim().split(/\s+/).map(stripQuotes).filter((t) => t.length > 0);
|
|
1670
|
+
const rest = skipPassthroughLeaders(toks);
|
|
1671
|
+
if (rest.length === 0) continue;
|
|
1672
|
+
if (!interpreterKind(interpreterBasename(rest[0] ?? ""))) continue;
|
|
1673
|
+
const hasScriptArg = rest.slice(1).some((t) => !t.startsWith("-"));
|
|
1674
|
+
if (!hasScriptArg) return true;
|
|
1675
|
+
}
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
function isWindowsRootOrWildcardTarget(token) {
|
|
1679
|
+
const t = stripQuotes(token);
|
|
1680
|
+
if (t === "*" || t === "\\" || t === "/") return true;
|
|
1681
|
+
if (/^[a-z]:\\?\*?$/i.test(t)) return true;
|
|
1682
|
+
if (/^%(systemdrive|systemroot|userprofile)%\\?\*?$/i.test(t)) return true;
|
|
1683
|
+
if (/^\$env:(systemdrive|systemroot|userprofile)\\?\*?$/i.test(t)) return true;
|
|
1684
|
+
return false;
|
|
1685
|
+
}
|
|
1686
|
+
function isCatastrophicCommand(cmd) {
|
|
1687
|
+
if (typeof cmd !== "string" || cmd.trim() === "") return false;
|
|
1688
|
+
const c = normalizeSlashes(cmd.trim());
|
|
1689
|
+
if (FORK_BOMB.test(c) || MKFS.test(c) || DD_TO_DEVICE.test(c) || POWER_STATE.test(c) || INIT_RUNLEVEL.test(c) || OVERWRITE_DEVICE.test(c) || DISKPART.test(c) || DISK_CMDLET.test(c)) {
|
|
1690
|
+
return true;
|
|
1691
|
+
}
|
|
1692
|
+
for (const seg of c.split(/[;&|\n\r]+/)) {
|
|
1693
|
+
const s = seg.trim();
|
|
1694
|
+
if (!s) continue;
|
|
1695
|
+
const tokens = s.split(/\s+/).map(stripQuotes);
|
|
1696
|
+
const cmdTokens = stripWrapperPrefix(tokens);
|
|
1697
|
+
const cmdWord = cmdTokens[0] ?? "";
|
|
1698
|
+
if (/^rm$/i.test(cmdWord)) {
|
|
1699
|
+
const recursive = /\s-\S*r/i.test(s) || /\s--recursive\b/i.test(s);
|
|
1700
|
+
const force = /\s-\S*f/i.test(s) || /\s--force\b/i.test(s);
|
|
1701
|
+
if (recursive && force) {
|
|
1702
|
+
if (/\s--no-preserve-root\b/i.test(s)) return true;
|
|
1703
|
+
if (/(\s|=)(\/|\/\*|~|~\/\*?|\$HOME\/?\*?|\*)(\s|$|"|')/.test(s)) return true;
|
|
1704
|
+
if (tokens.some((t) => isWindowsRootOrWildcardTarget(t))) return true;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
if (/^format(\.(com|exe))?$/i.test(cmdWord) && cmdTokens.slice(1).some((d) => /^[a-z]:([\\/]\*?)?$/i.test(d))) {
|
|
1708
|
+
return true;
|
|
1709
|
+
}
|
|
1710
|
+
if (/^(ri|remove-item|del|erase|rd|rmdir)$/i.test(cmdWord)) {
|
|
1711
|
+
const psRecursiveForce = /(^|\s)-r(ecurse)?\b/i.test(s) && /(^|\s)-f(orce)?\b/i.test(s);
|
|
1712
|
+
const cmdRecursiveForce = /\/s\b/i.test(s) && /\/q\b/i.test(s);
|
|
1713
|
+
if (psRecursiveForce || cmdRecursiveForce) {
|
|
1714
|
+
if (tokens.some((t) => isWindowsRootOrWildcardTarget(t))) return true;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return false;
|
|
1719
|
+
}
|
|
1720
|
+
function isInlineInterpreterEvalCommand(cmd) {
|
|
1721
|
+
if (typeof cmd !== "string" || cmd.trim() === "") return false;
|
|
1722
|
+
const c = normalizeSlashes(cmd.trim());
|
|
1723
|
+
if (AWK_CODE_EXEC.test(c) || hasPipeIntoBareInterpreter(c)) return true;
|
|
1724
|
+
for (const seg of c.split(/[;&|\n\r]+/)) {
|
|
1725
|
+
const s = seg.trim();
|
|
1726
|
+
if (!s) continue;
|
|
1727
|
+
const tokens = s.split(/\s+/).map(stripQuotes);
|
|
1728
|
+
if (hasInlineInterpreterEval(tokens)) return true;
|
|
1729
|
+
}
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
function isDestructiveOrHeavyCommand(cmd) {
|
|
1733
|
+
if (typeof cmd !== "string" || cmd.trim() === "") return false;
|
|
1734
|
+
if (isCatastrophicCommand(cmd) || isInlineInterpreterEvalCommand(cmd)) return true;
|
|
1735
|
+
const c = normalizeSlashes(cmd.trim());
|
|
1736
|
+
return DESTRUCTIVE_PATTERNS.some((re) => re.test(c));
|
|
1737
|
+
}
|
|
1738
|
+
var FORK_BOMB, MKFS, DD_TO_DEVICE, POWER_STATE, INIT_RUNLEVEL, OVERWRITE_DEVICE, DISKPART, DISK_CMDLET, AWK_CODE_EXEC, WRAPPER_LEADER, PASSTHROUGH_LEADERS, DESTRUCTIVE_PATTERNS;
|
|
1739
|
+
var init_catastrophic_command = __esm({
|
|
1740
|
+
"../../packages/shared/src/catastrophic-command.ts"() {
|
|
1741
|
+
"use strict";
|
|
1742
|
+
FORK_BOMB = /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:?\s*&\s*\}\s*;\s*:/;
|
|
1743
|
+
MKFS = /\bmkfs(\.\w+)?\b/i;
|
|
1744
|
+
DD_TO_DEVICE = /\bdd\b[\s\S]*?\bof=\/dev\/[a-z]/i;
|
|
1745
|
+
POWER_STATE = /\b(shutdown|reboot|halt|poweroff|stop-computer|restart-computer)\b/i;
|
|
1746
|
+
INIT_RUNLEVEL = /\binit\s+[06]\b/;
|
|
1747
|
+
OVERWRITE_DEVICE = />\s*\/dev\/(sd[a-z]|nvme\d|disk\d|hd[a-z])/i;
|
|
1748
|
+
DISKPART = /\bdiskpart\b/i;
|
|
1749
|
+
DISK_CMDLET = /\b(format-volume|clear-disk|initialize-disk)\b/i;
|
|
1750
|
+
AWK_CODE_EXEC = /\bawk\b[\s\S]*?(\bsystem\s*\(|\bgetline\b[^;]*\||["'][^"']*\|\s*["']?\s*(?:sh|bash|cmd))/i;
|
|
1751
|
+
WRAPPER_LEADER = /* @__PURE__ */ new Set([
|
|
1752
|
+
"cmd",
|
|
1753
|
+
"cmd.exe",
|
|
1754
|
+
"powershell",
|
|
1755
|
+
"powershell.exe",
|
|
1756
|
+
"pwsh",
|
|
1757
|
+
"pwsh.exe",
|
|
1758
|
+
"sh",
|
|
1759
|
+
"bash",
|
|
1760
|
+
"sudo"
|
|
1761
|
+
]);
|
|
1762
|
+
PASSTHROUGH_LEADERS = /* @__PURE__ */ new Set(["sudo", "env", "cmd", "cmd.exe"]);
|
|
1763
|
+
DESTRUCTIVE_PATTERNS = [
|
|
1764
|
+
/\b(rm|rmdir|unlink|shred)\b/i,
|
|
1765
|
+
// delete (unix)
|
|
1766
|
+
/\bdel\s|\bRemove-Item\b|\brd\s+\/s/i,
|
|
1767
|
+
// delete (windows/ps)
|
|
1768
|
+
/\bfind\b[^\n]*-delete\b/i,
|
|
1769
|
+
// find … -delete
|
|
1770
|
+
/\b(pip3?|npm|pnpm|yarn|apt|apt-get|yum|dnf|brew|pacman|choco|winget|uvx|pipx|cargo|gem|conda|comfy)\b[^\n]*\binstall\b/i,
|
|
1771
|
+
// pkg install
|
|
1772
|
+
/\bgo\s+install\b|\bcomfy\b[^\n]*\bmodel\s+download\b|\bpip3?\b[^\n]*\bdownload\b/i,
|
|
1773
|
+
// go install / model dl
|
|
1774
|
+
/\bwget\b|\bgit\s+clone\b|\bcurl\b[^\n]*\s-[oO]\b|\bInvoke-WebRequest\b|\biwr\b[^\n]*-OutFile/i,
|
|
1775
|
+
// large download / clone
|
|
1776
|
+
/\b(kill|pkill|killall|taskkill)\b|\bStop-Process\b|\bStop-Service\b/i,
|
|
1777
|
+
// process kill
|
|
1778
|
+
/\bsystemctl\b|\bsc\s+(stop|delete)\b|\bservice\b[^\n]*\b(stop|restart)\b/i,
|
|
1779
|
+
// service control
|
|
1780
|
+
/\bmkfs(\.\w+)?\b|\bdd\b[^\n]*\bof=|\b(format|fdisk|parted|diskpart)\b/i,
|
|
1781
|
+
// disk ops
|
|
1782
|
+
/\bchmod\s+-\S*R|\bchown\s+-\S*R|\bicacls\b/i,
|
|
1783
|
+
// recursive perms/ownership
|
|
1784
|
+
/\bgit\s+push\b[^\n]*(--force|-f\b)|\bgit\s+reset\s+--hard\b|\bgit\s+clean\s+-\S*f|\bgit\s+branch\s+-D\b/i
|
|
1785
|
+
// destructive VCS
|
|
1786
|
+
];
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
// ../../packages/shared/src/approval-impact.ts
|
|
1791
|
+
function commandBinaries(cmd) {
|
|
1792
|
+
const names = [];
|
|
1793
|
+
for (const segment of cmd.split(/\||&&|\|\||;/)) {
|
|
1794
|
+
const tokens = segment.trim().match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
1795
|
+
let head = "";
|
|
1796
|
+
for (const t of tokens) {
|
|
1797
|
+
if (t === "" || t === "sudo" || /^[A-Za-z_][A-Za-z0-9_]*=/.test(t)) continue;
|
|
1798
|
+
head = t;
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
if (!head) continue;
|
|
1802
|
+
const base = head.replace(/^["']|["']$/g, "").split(/[\\/]/).pop();
|
|
1803
|
+
if (base) names.push(base.replace(/\.(exe|cmd|bat)$/i, ""));
|
|
1804
|
+
if (names.length >= 3) break;
|
|
1805
|
+
}
|
|
1806
|
+
return names;
|
|
1807
|
+
}
|
|
1808
|
+
function describeCommandImpact(cmd) {
|
|
1809
|
+
const bins = commandBinaries(cmd);
|
|
1810
|
+
const ran = bins.length > 0 ? `Runs \`${bins.join("` \u2192 `")}\`` : "Runs a shell command";
|
|
1811
|
+
if (isCatastrophicCommand(cmd)) {
|
|
1812
|
+
return `${ran} \u2014 MACHINE-WIDE DESTRUCTIVE: refused even if approved (hardline floor).`;
|
|
1813
|
+
}
|
|
1814
|
+
if (isInlineInterpreterEvalCommand(cmd)) {
|
|
1815
|
+
return `${ran} \u2014 executes arbitrary inline code through an interpreter.`;
|
|
1816
|
+
}
|
|
1817
|
+
if (isDestructiveOrHeavyCommand(cmd)) {
|
|
1818
|
+
return `${ran} \u2014 destructive or heavy: deletes/moves files, installs software, or changes system state.`;
|
|
1819
|
+
}
|
|
1820
|
+
return `${ran} \u2014 no destructive pattern detected (likely read/inspect).`;
|
|
1821
|
+
}
|
|
1822
|
+
function computeApprovalImpactLine(toolName, toolInput) {
|
|
1823
|
+
const input = toolInput ?? {};
|
|
1824
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : "?";
|
|
1825
|
+
switch (toolName) {
|
|
1826
|
+
case "file_write":
|
|
1827
|
+
case "file_edit":
|
|
1828
|
+
return `Overwrites the existing file "${str(input["path"])}" in the shared workspace.`;
|
|
1829
|
+
case "run_command": {
|
|
1830
|
+
const cmd = input["command"];
|
|
1831
|
+
return typeof cmd === "string" && cmd.trim().length > 0 ? describeCommandImpact(cmd) : "Runs a shell command on the host.";
|
|
1832
|
+
}
|
|
1833
|
+
case "run_skill_script":
|
|
1834
|
+
return `Runs script "${str(input["script"])}" from skill "${str(input["skill"])}".`;
|
|
1835
|
+
case "skill_file_write":
|
|
1836
|
+
return `Writes a file into skill "${str(input["skill"])}"'s bundle.`;
|
|
1837
|
+
default:
|
|
1838
|
+
return `${toolName}: irreversible or destructive action.`;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
var init_approval_impact = __esm({
|
|
1842
|
+
"../../packages/shared/src/approval-impact.ts"() {
|
|
1843
|
+
"use strict";
|
|
1844
|
+
init_catastrophic_command();
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1506
1848
|
// ../../packages/shared/src/index.ts
|
|
1507
1849
|
var init_src = __esm({
|
|
1508
1850
|
"../../packages/shared/src/index.ts"() {
|
|
@@ -1513,6 +1855,7 @@ var init_src = __esm({
|
|
|
1513
1855
|
init_user();
|
|
1514
1856
|
init_agent();
|
|
1515
1857
|
init_job();
|
|
1858
|
+
init_job_classification();
|
|
1516
1859
|
init_task();
|
|
1517
1860
|
init_credential();
|
|
1518
1861
|
init_connector();
|
|
@@ -1532,6 +1875,9 @@ var init_src = __esm({
|
|
|
1532
1875
|
init_model_catalog();
|
|
1533
1876
|
init_mcp_catalog();
|
|
1534
1877
|
init_timezone();
|
|
1878
|
+
init_redact_secrets();
|
|
1879
|
+
init_approval_impact();
|
|
1880
|
+
init_catastrophic_command();
|
|
1535
1881
|
}
|
|
1536
1882
|
});
|
|
1537
1883
|
|
|
@@ -1672,7 +2018,7 @@ var init_entities = __esm({
|
|
|
1672
2018
|
});
|
|
1673
2019
|
|
|
1674
2020
|
// ../../packages/db/src/schema/llm_keys.ts
|
|
1675
|
-
import { pgTable as pgTable3, text as text3, uuid as uuid3, boolean as boolean3, timestamp as timestamp3, index as index2 } from "drizzle-orm/pg-core";
|
|
2021
|
+
import { pgTable as pgTable3, text as text3, uuid as uuid3, boolean as boolean3, integer, timestamp as timestamp3, index as index2 } from "drizzle-orm/pg-core";
|
|
1676
2022
|
var entityLlmKeys;
|
|
1677
2023
|
var init_llm_keys = __esm({
|
|
1678
2024
|
"../../packages/db/src/schema/llm_keys.ts"() {
|
|
@@ -1693,6 +2039,11 @@ var init_llm_keys = __esm({
|
|
|
1693
2039
|
apiKeyLast4: text3("api_key_last4").notNull().default(""),
|
|
1694
2040
|
baseUrl: text3("base_url"),
|
|
1695
2041
|
nickname: text3("nickname"),
|
|
2042
|
+
// Real context window (tokens) for a custom/local model the catalog can't
|
|
2043
|
+
// know (É-3). Auto-detected from the endpoint at save time or set by the
|
|
2044
|
+
// user; NULL ⇒ fall back to the catalogued value or DEFAULT_CONTEXT_WINDOW.
|
|
2045
|
+
// Used window-relative to trigger compaction before the model overflows.
|
|
2046
|
+
contextWindow: integer("context_window"),
|
|
1696
2047
|
isActive: boolean3("is_active").notNull().default(true),
|
|
1697
2048
|
createdAt: timestamp3("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
1698
2049
|
updatedAt: timestamp3("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -1708,7 +2059,7 @@ import {
|
|
|
1708
2059
|
text as text4,
|
|
1709
2060
|
uuid as uuid4,
|
|
1710
2061
|
boolean as boolean4,
|
|
1711
|
-
integer,
|
|
2062
|
+
integer as integer2,
|
|
1712
2063
|
bigint,
|
|
1713
2064
|
timestamp as timestamp4,
|
|
1714
2065
|
jsonb as jsonb2,
|
|
@@ -1758,16 +2109,16 @@ var init_agents = __esm({
|
|
|
1758
2109
|
taskContextTemplate: text4("task_context_template"),
|
|
1759
2110
|
avatarUrl: text4("avatar_url"),
|
|
1760
2111
|
systemAgent: boolean4("system_agent").default(false),
|
|
1761
|
-
maxTokensPerJob:
|
|
2112
|
+
maxTokensPerJob: integer2("max_tokens_per_job").default(0).notNull(),
|
|
1762
2113
|
// Cap on characters of agent_memory injected into the system prompt per job
|
|
1763
2114
|
// (Memory Sprint 2). Pure char budget — token estimation done at call site
|
|
1764
2115
|
// (length/4). 1500 chars ≈ ~375 tokens, similar to Hermes' 2200+1375 split.
|
|
1765
|
-
memoryTokenBudget:
|
|
2116
|
+
memoryTokenBudget: integer2("memory_token_budget").default(1500).notNull(),
|
|
1766
2117
|
// User-controlled order on the /agents page (Brique A, migration 0019).
|
|
1767
2118
|
// Default 0 — ties are broken by `name ASC` in the list query. Newly
|
|
1768
2119
|
// created agents land at the front of their group by default; the user
|
|
1769
2120
|
// can adjust via the ↑/↓ buttons in the UI.
|
|
1770
|
-
position:
|
|
2121
|
+
position: integer2("position").default(0).notNull(),
|
|
1771
2122
|
createdAt: timestamp4("created_at", { withTimezone: true }).defaultNow(),
|
|
1772
2123
|
updatedAt: timestamp4("updated_at", { withTimezone: true }).defaultNow()
|
|
1773
2124
|
},
|
|
@@ -1823,9 +2174,9 @@ var init_agents = __esm({
|
|
|
1823
2174
|
entityId: uuid4("entity_id").references(() => entities.id, { onDelete: "cascade" }),
|
|
1824
2175
|
dailyTokenLimit: bigint("daily_token_limit", { mode: "number" }).default(0),
|
|
1825
2176
|
monthlyTokenLimit: bigint("monthly_token_limit", { mode: "number" }).default(0),
|
|
1826
|
-
alertThresholdPct:
|
|
2177
|
+
alertThresholdPct: integer2("alert_threshold_pct").default(80),
|
|
1827
2178
|
autoPause: boolean4("auto_pause").default(false),
|
|
1828
|
-
maxJobTokens:
|
|
2179
|
+
maxJobTokens: integer2("max_job_tokens").default(15e4),
|
|
1829
2180
|
createdAt: timestamp4("created_at", { withTimezone: true }).defaultNow(),
|
|
1830
2181
|
updatedAt: timestamp4("updated_at", { withTimezone: true }).defaultNow()
|
|
1831
2182
|
},
|
|
@@ -1845,7 +2196,7 @@ import {
|
|
|
1845
2196
|
pgTable as pgTable5,
|
|
1846
2197
|
text as text5,
|
|
1847
2198
|
uuid as uuid5,
|
|
1848
|
-
integer as
|
|
2199
|
+
integer as integer3,
|
|
1849
2200
|
real,
|
|
1850
2201
|
jsonb as jsonb3,
|
|
1851
2202
|
timestamp as timestamp5,
|
|
@@ -1870,6 +2221,18 @@ var init_jobs = __esm({
|
|
|
1870
2221
|
task: text5("task").notNull(),
|
|
1871
2222
|
originalTask: text5("original_task"),
|
|
1872
2223
|
chatId: text5("chat_id"),
|
|
2224
|
+
/**
|
|
2225
|
+
* Groups jobs from the same conversational thread for the Jobs page UI
|
|
2226
|
+
* (migration 0059). Purely a reporting/grouping concern — the runtime
|
|
2227
|
+
* execution path (delegation, cron, task-board) never reads this column.
|
|
2228
|
+
* NULL for non-conversational jobs (cron/schedule/webhook with no parent).
|
|
2229
|
+
* Stamped at creation by apps/runner/src/job/conversation-id.ts, reusing
|
|
2230
|
+
* the exact idle-reset session boundary thread-history.ts already uses
|
|
2231
|
+
* for chat continuity — see that file for the gap rule. Delegated/
|
|
2232
|
+
* task-board children inherit their creator's value; nothing else
|
|
2233
|
+
* mutates it after insert.
|
|
2234
|
+
*/
|
|
2235
|
+
conversationId: uuid5("conversation_id"),
|
|
1873
2236
|
systemPrompt: text5("system_prompt"),
|
|
1874
2237
|
messages: jsonb3("messages").default(sql3`'[]'::jsonb`),
|
|
1875
2238
|
/**
|
|
@@ -1881,16 +2244,16 @@ var init_jobs = __esm({
|
|
|
1881
2244
|
*/
|
|
1882
2245
|
searchText: text5("search_text"),
|
|
1883
2246
|
toolsUsed: text5("tools_used").array().default(sql3`'{}'::text[]`),
|
|
1884
|
-
turn:
|
|
2247
|
+
turn: integer3("turn").default(0),
|
|
1885
2248
|
result: text5("result"),
|
|
1886
2249
|
error: text5("error"),
|
|
1887
|
-
chainCount:
|
|
2250
|
+
chainCount: integer3("chain_count").default(0),
|
|
1888
2251
|
requestId: text5("request_id"),
|
|
1889
2252
|
parentJobId: uuid5("parent_job_id"),
|
|
1890
2253
|
parentRequestId: text5("parent_request_id"),
|
|
1891
|
-
totalDurationMs:
|
|
1892
|
-
inputTokens:
|
|
1893
|
-
outputTokens:
|
|
2254
|
+
totalDurationMs: integer3("total_duration_ms").default(0),
|
|
2255
|
+
inputTokens: integer3("input_tokens").default(0),
|
|
2256
|
+
outputTokens: integer3("output_tokens").default(0),
|
|
1894
2257
|
/**
|
|
1895
2258
|
* Cumulative EFFECTIVE (non-cached) input tokens = Σ(inputTokens −
|
|
1896
2259
|
* cachedInputTokens) per turn. The token budget (Guard 1a) measures this,
|
|
@@ -1899,7 +2262,7 @@ var init_jobs = __esm({
|
|
|
1899
2262
|
* Persisted alongside input_tokens so the budget stays cumulative across
|
|
1900
2263
|
* self-chain resumes.
|
|
1901
2264
|
*/
|
|
1902
|
-
effectiveInputTokens:
|
|
2265
|
+
effectiveInputTokens: integer3("effective_input_tokens").default(0),
|
|
1903
2266
|
/**
|
|
1904
2267
|
* Cumulative real dollar cost billed to the provider across all turns of
|
|
1905
2268
|
* this job (sum of per-call costs reported by the provider). Populated only
|
|
@@ -1922,7 +2285,7 @@ var init_jobs = __esm({
|
|
|
1922
2285
|
* identity (Anthropic, Ollama, etc.) or when the field was absent.
|
|
1923
2286
|
*/
|
|
1924
2287
|
servedProvider: text5("served_provider"),
|
|
1925
|
-
delegationDepth:
|
|
2288
|
+
delegationDepth: integer3("delegation_depth").default(0),
|
|
1926
2289
|
/**
|
|
1927
2290
|
* The slug of the last delegated child that failed on this parent job.
|
|
1928
2291
|
* Set by `resumeDelegated` when a child returns `{status:'failed'}`, cleared
|
|
@@ -1952,6 +2315,9 @@ var init_jobs = __esm({
|
|
|
1952
2315
|
sql3`${table.createdAt} DESC`
|
|
1953
2316
|
),
|
|
1954
2317
|
index4("idx_agent_jobs_parent_job_id").on(table.parentJobId),
|
|
2318
|
+
// Migration 0059: the Jobs page groups conversational jobs by
|
|
2319
|
+
// (entity_id, conversation_id) — this index serves that lookup directly.
|
|
2320
|
+
index4("idx_agent_jobs_entity_conversation").on(table.entityId, table.conversationId),
|
|
1955
2321
|
// idx_jobs_parent (F-20, audit #2) was an exact duplicate of
|
|
1956
2322
|
// idx_agent_jobs_parent_job_id above — same column, same order, both from
|
|
1957
2323
|
// migration 0000. Dropped in migration 0054; kept this one (matches the
|
|
@@ -1983,7 +2349,7 @@ import {
|
|
|
1983
2349
|
pgTable as pgTable6,
|
|
1984
2350
|
text as text6,
|
|
1985
2351
|
uuid as uuid6,
|
|
1986
|
-
integer as
|
|
2352
|
+
integer as integer4,
|
|
1987
2353
|
jsonb as jsonb4,
|
|
1988
2354
|
timestamp as timestamp6,
|
|
1989
2355
|
numeric,
|
|
@@ -2016,8 +2382,8 @@ var init_tasks = __esm({
|
|
|
2016
2382
|
assignedAgentId: uuid6("assigned_agent_id").references(() => agents.id, {
|
|
2017
2383
|
onDelete: "cascade"
|
|
2018
2384
|
}),
|
|
2019
|
-
inputTokens:
|
|
2020
|
-
outputTokens:
|
|
2385
|
+
inputTokens: integer4("input_tokens").default(0),
|
|
2386
|
+
outputTokens: integer4("output_tokens").default(0),
|
|
2021
2387
|
costUsd: numeric("cost_usd", { precision: 10, scale: 6 }).default("0"),
|
|
2022
2388
|
dependsOn: uuid6("depends_on").array().default(sql4`'{}'::uuid[]`),
|
|
2023
2389
|
context: jsonb4("context").default(sql4`'{}'::jsonb`),
|
|
@@ -2105,7 +2471,10 @@ var init_connectors = __esm({
|
|
|
2105
2471
|
name: text8("name").notNull(),
|
|
2106
2472
|
slug: text8("slug").notNull(),
|
|
2107
2473
|
baseUrl: text8("base_url"),
|
|
2108
|
-
//
|
|
2474
|
+
// audit#2026-07-07 SEC-5: this comment used to claim pgp_sym_encrypt (pgcrypto),
|
|
2475
|
+
// which is not used anywhere. Real encryption is application-level AES-256-GCM
|
|
2476
|
+
// via @nodal-agents/secrets before the value ever reaches this column — the DB
|
|
2477
|
+
// column itself just holds ciphertext as opaque text. Raw value never returned in select.
|
|
2109
2478
|
apiKey: text8("api_key"),
|
|
2110
2479
|
active: boolean5("active").default(true),
|
|
2111
2480
|
authType: text8("auth_type").notNull().default("api_key"),
|
|
@@ -2132,7 +2501,7 @@ var init_connectors = __esm({
|
|
|
2132
2501
|
});
|
|
2133
2502
|
|
|
2134
2503
|
// ../../packages/db/src/schema/tool_calls.ts
|
|
2135
|
-
import { pgTable as pgTable9, text as text9, uuid as uuid9, integer as
|
|
2504
|
+
import { pgTable as pgTable9, text as text9, uuid as uuid9, integer as integer5, jsonb as jsonb5, timestamp as timestamp9, index as index8 } from "drizzle-orm/pg-core";
|
|
2136
2505
|
import { sql as sql7 } from "drizzle-orm";
|
|
2137
2506
|
var toolCalls;
|
|
2138
2507
|
var init_tool_calls = __esm({
|
|
@@ -2149,8 +2518,8 @@ var init_tool_calls = __esm({
|
|
|
2149
2518
|
toolName: text9("tool_name").notNull(),
|
|
2150
2519
|
toolInput: jsonb5("tool_input"),
|
|
2151
2520
|
toolOutput: text9("tool_output"),
|
|
2152
|
-
durationMs:
|
|
2153
|
-
turn:
|
|
2521
|
+
durationMs: integer5("duration_ms"),
|
|
2522
|
+
turn: integer5("turn"),
|
|
2154
2523
|
createdAt: timestamp9("created_at", { withTimezone: true }).defaultNow()
|
|
2155
2524
|
},
|
|
2156
2525
|
(table) => [
|
|
@@ -2245,7 +2614,7 @@ import {
|
|
|
2245
2614
|
pgTable as pgTable11,
|
|
2246
2615
|
text as text11,
|
|
2247
2616
|
uuid as uuid11,
|
|
2248
|
-
integer as
|
|
2617
|
+
integer as integer6,
|
|
2249
2618
|
boolean as boolean6,
|
|
2250
2619
|
timestamp as timestamp11,
|
|
2251
2620
|
customType,
|
|
@@ -2288,7 +2657,7 @@ var init_memory2 = __esm({
|
|
|
2288
2657
|
*/
|
|
2289
2658
|
fact: text11("fact").notNull(),
|
|
2290
2659
|
category: text11("category").default("context"),
|
|
2291
|
-
importance:
|
|
2660
|
+
importance: integer6("importance").default(3),
|
|
2292
2661
|
source: text11("source").default("agent"),
|
|
2293
2662
|
skillTags: text11("skill_tags").array().default(sql9`'{}'::text[]`),
|
|
2294
2663
|
memoryLayer: text11("memory_layer"),
|
|
@@ -2304,7 +2673,7 @@ var init_memory2 = __esm({
|
|
|
2304
2673
|
*/
|
|
2305
2674
|
importanceLocked: boolean6("importance_locked").notNull().default(false),
|
|
2306
2675
|
lastAccessedAt: timestamp11("last_accessed_at", { withTimezone: true }).defaultNow(),
|
|
2307
|
-
accessCount:
|
|
2676
|
+
accessCount: integer6("access_count").default(0),
|
|
2308
2677
|
createdAt: timestamp11("created_at", { withTimezone: true }).defaultNow(),
|
|
2309
2678
|
updatedAt: timestamp11("updated_at", { withTimezone: true }).defaultNow()
|
|
2310
2679
|
},
|
|
@@ -2332,7 +2701,7 @@ var init_memory2 = __esm({
|
|
|
2332
2701
|
});
|
|
2333
2702
|
|
|
2334
2703
|
// ../../packages/db/src/schema/webhooks.ts
|
|
2335
|
-
import { pgTable as pgTable12, text as text12, uuid as uuid12, boolean as boolean7, integer as
|
|
2704
|
+
import { pgTable as pgTable12, text as text12, uuid as uuid12, boolean as boolean7, integer as integer7, timestamp as timestamp12, index as index11 } from "drizzle-orm/pg-core";
|
|
2336
2705
|
var webhookTriggers;
|
|
2337
2706
|
var init_webhooks = __esm({
|
|
2338
2707
|
"../../packages/db/src/schema/webhooks.ts"() {
|
|
@@ -2352,7 +2721,7 @@ var init_webhooks = __esm({
|
|
|
2352
2721
|
// default: random 32-char hex (generated in app layer for Nodal-Agents)
|
|
2353
2722
|
secret: text12("secret"),
|
|
2354
2723
|
lastTriggeredAt: timestamp12("last_triggered_at", { withTimezone: true }),
|
|
2355
|
-
triggerCount:
|
|
2724
|
+
triggerCount: integer7("trigger_count").default(0),
|
|
2356
2725
|
createdAt: timestamp12("created_at", { withTimezone: true }).defaultNow(),
|
|
2357
2726
|
updatedAt: timestamp12("updated_at", { withTimezone: true }).defaultNow()
|
|
2358
2727
|
},
|
|
@@ -2367,7 +2736,7 @@ import {
|
|
|
2367
2736
|
text as text13,
|
|
2368
2737
|
uuid as uuid13,
|
|
2369
2738
|
boolean as boolean8,
|
|
2370
|
-
integer as
|
|
2739
|
+
integer as integer8,
|
|
2371
2740
|
jsonb as jsonb7,
|
|
2372
2741
|
timestamp as timestamp13,
|
|
2373
2742
|
index as index12,
|
|
@@ -2419,7 +2788,7 @@ var init_skills = __esm({
|
|
|
2419
2788
|
}),
|
|
2420
2789
|
state: text13("state").notNull().default("active"),
|
|
2421
2790
|
lastUsedAt: timestamp13("last_used_at", { withTimezone: true }),
|
|
2422
|
-
patchCount:
|
|
2791
|
+
patchCount: integer8("patch_count").notNull().default(0),
|
|
2423
2792
|
archivedAt: timestamp13("archived_at", { withTimezone: true }),
|
|
2424
2793
|
createdAt: timestamp13("created_at", { withTimezone: true }).defaultNow(),
|
|
2425
2794
|
updatedAt: timestamp13("updated_at", { withTimezone: true }).defaultNow()
|
|
@@ -2449,7 +2818,7 @@ var init_skills = __esm({
|
|
|
2449
2818
|
id: uuid13("id").primaryKey().defaultRandom(),
|
|
2450
2819
|
entityId: uuid13("entity_id").references(() => entities.id, { onDelete: "cascade" }),
|
|
2451
2820
|
skillId: uuid13("skill_id").references(() => agentSkills.id, { onDelete: "cascade" }),
|
|
2452
|
-
version:
|
|
2821
|
+
version: integer8("version").notNull(),
|
|
2453
2822
|
content: text13("content").notNull(),
|
|
2454
2823
|
name: text13("name").notNull(),
|
|
2455
2824
|
createdAt: timestamp13("created_at", { withTimezone: true }).defaultNow()
|
|
@@ -2664,7 +3033,7 @@ import {
|
|
|
2664
3033
|
text as text16,
|
|
2665
3034
|
uuid as uuid16,
|
|
2666
3035
|
boolean as boolean11,
|
|
2667
|
-
integer as
|
|
3036
|
+
integer as integer9,
|
|
2668
3037
|
jsonb as jsonb9,
|
|
2669
3038
|
timestamp as timestamp16,
|
|
2670
3039
|
index as index15,
|
|
@@ -2686,7 +3055,7 @@ var init_misc = __esm({
|
|
|
2686
3055
|
agentId: uuid16("agent_id").references(() => agents.id, { onDelete: "cascade" }),
|
|
2687
3056
|
messages: jsonb9("messages").notNull().default(sql13`'[]'::jsonb`),
|
|
2688
3057
|
status: text16("status").notNull().default("active"),
|
|
2689
|
-
turnCount:
|
|
3058
|
+
turnCount: integer9("turn_count").notNull().default(0),
|
|
2690
3059
|
createdAt: timestamp16("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
2691
3060
|
updatedAt: timestamp16("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
2692
3061
|
},
|
|
@@ -2731,7 +3100,7 @@ var init_misc = __esm({
|
|
|
2731
3100
|
);
|
|
2732
3101
|
rateLimits = pgTable16("rate_limits", {
|
|
2733
3102
|
key: text16("key").primaryKey(),
|
|
2734
|
-
count:
|
|
3103
|
+
count: integer9("count").default(1),
|
|
2735
3104
|
windowStart: timestamp16("window_start", { withTimezone: true }).defaultNow()
|
|
2736
3105
|
});
|
|
2737
3106
|
}
|
|
@@ -2811,7 +3180,7 @@ var init_agent_connector_assignments = __esm({
|
|
|
2811
3180
|
});
|
|
2812
3181
|
|
|
2813
3182
|
// ../../packages/db/src/schema/agent-workspaces.ts
|
|
2814
|
-
import { pgTable as pgTable19, text as text19, uuid as uuid19, integer as
|
|
3183
|
+
import { pgTable as pgTable19, text as text19, uuid as uuid19, integer as integer10, timestamp as timestamp19, uniqueIndex as uniqueIndex3, index as index17 } from "drizzle-orm/pg-core";
|
|
2815
3184
|
var agentWorkspaces;
|
|
2816
3185
|
var init_agent_workspaces = __esm({
|
|
2817
3186
|
"../../packages/db/src/schema/agent-workspaces.ts"() {
|
|
@@ -2833,7 +3202,7 @@ var init_agent_workspaces = __esm({
|
|
|
2833
3202
|
// Absolute filesystem path — validated to be absolute at write time.
|
|
2834
3203
|
path: text19("path").notNull(),
|
|
2835
3204
|
// UI display order; lower = appears first. Default 0.
|
|
2836
|
-
position:
|
|
3205
|
+
position: integer10("position").default(0).notNull(),
|
|
2837
3206
|
createdAt: timestamp19("created_at", { withTimezone: true }).defaultNow(),
|
|
2838
3207
|
updatedAt: timestamp19("updated_at", { withTimezone: true }).defaultNow()
|
|
2839
3208
|
},
|
|
@@ -5307,8 +5676,8 @@ var require_lib2 = __commonJS({
|
|
|
5307
5676
|
return this;
|
|
5308
5677
|
}
|
|
5309
5678
|
var p = this.constructor;
|
|
5310
|
-
return this.then(
|
|
5311
|
-
function
|
|
5679
|
+
return this.then(resolve4, reject2);
|
|
5680
|
+
function resolve4(value) {
|
|
5312
5681
|
function yes() {
|
|
5313
5682
|
return value;
|
|
5314
5683
|
}
|
|
@@ -5461,8 +5830,8 @@ var require_lib2 = __commonJS({
|
|
|
5461
5830
|
}
|
|
5462
5831
|
return out;
|
|
5463
5832
|
}
|
|
5464
|
-
Promise2.resolve =
|
|
5465
|
-
function
|
|
5833
|
+
Promise2.resolve = resolve3;
|
|
5834
|
+
function resolve3(value) {
|
|
5466
5835
|
if (value instanceof this) {
|
|
5467
5836
|
return value;
|
|
5468
5837
|
}
|
|
@@ -5993,10 +6362,10 @@ var require_utils = __commonJS({
|
|
|
5993
6362
|
var promise = external.Promise.resolve(inputData).then(function(data) {
|
|
5994
6363
|
var isBlob = support.blob && (data instanceof Blob || ["[object File]", "[object Blob]"].indexOf(Object.prototype.toString.call(data)) !== -1);
|
|
5995
6364
|
if (isBlob && typeof FileReader !== "undefined") {
|
|
5996
|
-
return new external.Promise(function(
|
|
6365
|
+
return new external.Promise(function(resolve3, reject) {
|
|
5997
6366
|
var reader = new FileReader();
|
|
5998
6367
|
reader.onload = function(e) {
|
|
5999
|
-
|
|
6368
|
+
resolve3(e.target.result);
|
|
6000
6369
|
};
|
|
6001
6370
|
reader.onerror = function(e) {
|
|
6002
6371
|
reject(e.target.error);
|
|
@@ -6551,7 +6920,7 @@ var require_StreamHelper = __commonJS({
|
|
|
6551
6920
|
}
|
|
6552
6921
|
}
|
|
6553
6922
|
function accumulate(helper, updateCallback) {
|
|
6554
|
-
return new external.Promise(function(
|
|
6923
|
+
return new external.Promise(function(resolve3, reject) {
|
|
6555
6924
|
var dataArray = [];
|
|
6556
6925
|
var chunkType = helper._internalType, resultType = helper._outputType, mimeType = helper._mimeType;
|
|
6557
6926
|
helper.on("data", function(data, meta) {
|
|
@@ -6565,7 +6934,7 @@ var require_StreamHelper = __commonJS({
|
|
|
6565
6934
|
}).on("end", function() {
|
|
6566
6935
|
try {
|
|
6567
6936
|
var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType);
|
|
6568
|
-
|
|
6937
|
+
resolve3(result);
|
|
6569
6938
|
} catch (e) {
|
|
6570
6939
|
reject(e);
|
|
6571
6940
|
}
|
|
@@ -12678,7 +13047,7 @@ var require_load = __commonJS({
|
|
|
12678
13047
|
var Crc32Probe = require_Crc32Probe();
|
|
12679
13048
|
var nodejsUtils = require_nodejsUtils();
|
|
12680
13049
|
function checkEntryCRC32(zipEntry) {
|
|
12681
|
-
return new external.Promise(function(
|
|
13050
|
+
return new external.Promise(function(resolve3, reject) {
|
|
12682
13051
|
var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe());
|
|
12683
13052
|
worker.on("error", function(e) {
|
|
12684
13053
|
reject(e);
|
|
@@ -12686,7 +13055,7 @@ var require_load = __commonJS({
|
|
|
12686
13055
|
if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) {
|
|
12687
13056
|
reject(new Error("Corrupted zip : CRC32 mismatch"));
|
|
12688
13057
|
} else {
|
|
12689
|
-
|
|
13058
|
+
resolve3();
|
|
12690
13059
|
}
|
|
12691
13060
|
}).resume();
|
|
12692
13061
|
});
|
|
@@ -15794,7 +16163,7 @@ var require_compile = __commonJS({
|
|
|
15794
16163
|
const schOrFunc = root.refs[ref];
|
|
15795
16164
|
if (schOrFunc)
|
|
15796
16165
|
return schOrFunc;
|
|
15797
|
-
let _sch =
|
|
16166
|
+
let _sch = resolve3.call(this, root, ref);
|
|
15798
16167
|
if (_sch === void 0) {
|
|
15799
16168
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
15800
16169
|
const { schemaId } = this.opts;
|
|
@@ -15821,7 +16190,7 @@ var require_compile = __commonJS({
|
|
|
15821
16190
|
function sameSchemaEnv(s1, s2) {
|
|
15822
16191
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
15823
16192
|
}
|
|
15824
|
-
function
|
|
16193
|
+
function resolve3(root, ref) {
|
|
15825
16194
|
let sch;
|
|
15826
16195
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
15827
16196
|
ref = sch;
|
|
@@ -16452,7 +16821,7 @@ var require_fast_uri = __commonJS({
|
|
|
16452
16821
|
}
|
|
16453
16822
|
return uri;
|
|
16454
16823
|
}
|
|
16455
|
-
function
|
|
16824
|
+
function resolve3(baseURI, relativeURI, options) {
|
|
16456
16825
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
16457
16826
|
const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
16458
16827
|
schemelessOptions.skipEscape = true;
|
|
@@ -16710,7 +17079,7 @@ var require_fast_uri = __commonJS({
|
|
|
16710
17079
|
var fastUri = {
|
|
16711
17080
|
SCHEMES,
|
|
16712
17081
|
normalize,
|
|
16713
|
-
resolve:
|
|
17082
|
+
resolve: resolve3,
|
|
16714
17083
|
resolveComponent,
|
|
16715
17084
|
equal,
|
|
16716
17085
|
serialize,
|
|
@@ -19793,12 +20162,12 @@ var require_isexe = __commonJS({
|
|
|
19793
20162
|
if (typeof Promise !== "function") {
|
|
19794
20163
|
throw new TypeError("callback not provided");
|
|
19795
20164
|
}
|
|
19796
|
-
return new Promise(function(
|
|
20165
|
+
return new Promise(function(resolve3, reject) {
|
|
19797
20166
|
isexe(path, options || {}, function(er, is) {
|
|
19798
20167
|
if (er) {
|
|
19799
20168
|
reject(er);
|
|
19800
20169
|
} else {
|
|
19801
|
-
|
|
20170
|
+
resolve3(is);
|
|
19802
20171
|
}
|
|
19803
20172
|
});
|
|
19804
20173
|
});
|
|
@@ -19864,27 +20233,27 @@ var require_which = __commonJS({
|
|
|
19864
20233
|
opt = {};
|
|
19865
20234
|
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
19866
20235
|
const found = [];
|
|
19867
|
-
const step = (i) => new Promise((
|
|
20236
|
+
const step = (i) => new Promise((resolve3, reject) => {
|
|
19868
20237
|
if (i === pathEnv.length)
|
|
19869
|
-
return opt.all && found.length ?
|
|
20238
|
+
return opt.all && found.length ? resolve3(found) : reject(getNotFoundError(cmd));
|
|
19870
20239
|
const ppRaw = pathEnv[i];
|
|
19871
20240
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
19872
20241
|
const pCmd = path.join(pathPart, cmd);
|
|
19873
20242
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
19874
|
-
|
|
20243
|
+
resolve3(subStep(p, i, 0));
|
|
19875
20244
|
});
|
|
19876
|
-
const subStep = (p, i, ii) => new Promise((
|
|
20245
|
+
const subStep = (p, i, ii) => new Promise((resolve3, reject) => {
|
|
19877
20246
|
if (ii === pathExt.length)
|
|
19878
|
-
return
|
|
20247
|
+
return resolve3(step(i + 1));
|
|
19879
20248
|
const ext = pathExt[ii];
|
|
19880
20249
|
isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
|
|
19881
20250
|
if (!er && is) {
|
|
19882
20251
|
if (opt.all)
|
|
19883
20252
|
found.push(p + ext);
|
|
19884
20253
|
else
|
|
19885
|
-
return
|
|
20254
|
+
return resolve3(p + ext);
|
|
19886
20255
|
}
|
|
19887
|
-
return
|
|
20256
|
+
return resolve3(subStep(p, i, ii + 1));
|
|
19888
20257
|
});
|
|
19889
20258
|
});
|
|
19890
20259
|
return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
|
|
@@ -27653,7 +28022,17 @@ var envSchema = z.object({
|
|
|
27653
28022
|
//
|
|
27654
28023
|
// Set this ONLY if you explicitly want to cap DB growth. Deleting job history is
|
|
27655
28024
|
// irreversible. Start with a large value (e.g. 90) and reduce over time.
|
|
27656
|
-
RETENTION_DAYS: z.coerce.number().default(0)
|
|
28025
|
+
RETENTION_DAYS: z.coerce.number().default(0),
|
|
28026
|
+
// ─── Approval grace window (Lot A1) ───────────────────────────────────────────
|
|
28027
|
+
// A gated tool normally suspends the job to `awaiting_approval` the instant it
|
|
28028
|
+
// fires — resuming later pays a full executeJob restart (~80-105s measured:
|
|
28029
|
+
// agent/skill/tool reload, thread history, system prompt). A human watching
|
|
28030
|
+
// the screen typically approves in 5-30s, so that whole restart is wasted. This
|
|
28031
|
+
// caps an in-process wait (poll approval_requests) BEFORE actually suspending:
|
|
28032
|
+
// if the decision lands inside the window, the runner executes it inline and
|
|
28033
|
+
// keeps looping — no suspend, no restart. 0 = disabled (suspend immediately,
|
|
28034
|
+
// the pre-existing behavior).
|
|
28035
|
+
NODALAI_APPROVAL_GRACE_MS: z.coerce.number().min(0).default(12e4)
|
|
27657
28036
|
});
|
|
27658
28037
|
var _env;
|
|
27659
28038
|
function parseEnv(raw = process.env) {
|
|
@@ -27723,8 +28102,22 @@ import { eq, sql as sql16 } from "drizzle-orm";
|
|
|
27723
28102
|
// ../../packages/secrets/src/index.ts
|
|
27724
28103
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
27725
28104
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
28105
|
+
import { execFileSync } from "node:child_process";
|
|
27726
28106
|
import { homedir } from "node:os";
|
|
27727
28107
|
import { dirname, join } from "node:path";
|
|
28108
|
+
function restrictFileToOwner(path) {
|
|
28109
|
+
try {
|
|
28110
|
+
chmodSync(path, 384);
|
|
28111
|
+
} catch {
|
|
28112
|
+
}
|
|
28113
|
+
if (process.platform !== "win32") return;
|
|
28114
|
+
const user = process.env["USERNAME"] || process.env["USER"];
|
|
28115
|
+
if (!user) return;
|
|
28116
|
+
try {
|
|
28117
|
+
execFileSync("icacls", [path, "/inheritance:r", "/grant:r", `${user}:F`], { stdio: "ignore" });
|
|
28118
|
+
} catch {
|
|
28119
|
+
}
|
|
28120
|
+
}
|
|
27728
28121
|
var VERSION = "v1";
|
|
27729
28122
|
var ALGO = "aes-256-gcm";
|
|
27730
28123
|
var KEY_BYTES = 32;
|
|
@@ -27750,10 +28143,7 @@ function loadOrCreateMasterKey(path = masterKeyPath()) {
|
|
|
27750
28143
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
27751
28144
|
const buf = randomBytes(KEY_BYTES);
|
|
27752
28145
|
writeFileSync(path, buf.toString("base64"), { mode: 384 });
|
|
27753
|
-
|
|
27754
|
-
chmodSync(path, 384);
|
|
27755
|
-
} catch {
|
|
27756
|
-
}
|
|
28146
|
+
restrictFileToOwner(path);
|
|
27757
28147
|
cachedMasterKey = buf;
|
|
27758
28148
|
return buf;
|
|
27759
28149
|
}
|
|
@@ -27931,6 +28321,8 @@ async function getDecryptedCredentialById(db, credentialId) {
|
|
|
27931
28321
|
// ../../packages/db/src/queries/master-key-guard.ts
|
|
27932
28322
|
init_credentials();
|
|
27933
28323
|
init_llm_keys();
|
|
28324
|
+
init_connectors();
|
|
28325
|
+
init_mcp();
|
|
27934
28326
|
|
|
27935
28327
|
// ../../packages/db/src/repos/agents.ts
|
|
27936
28328
|
init_src();
|
|
@@ -28321,6 +28713,11 @@ var ProviderConfigError = class extends Error {
|
|
|
28321
28713
|
detail;
|
|
28322
28714
|
code = "provider_config_error";
|
|
28323
28715
|
};
|
|
28716
|
+
function isContextOverflowError(err) {
|
|
28717
|
+
const msg = (err instanceof Error ? err.message : String(err ?? "")).toLowerCase();
|
|
28718
|
+
if (!msg) return false;
|
|
28719
|
+
return msg.includes("context length") || msg.includes("context window") || msg.includes("context_length_exceeded") || msg.includes("maximum context") || msg.includes("exceed context") || msg.includes("exceeds context") || msg.includes("prompt is too long") || msg.includes("too many tokens") || msg.includes("reduce") && msg.includes("length") && msg.includes("token");
|
|
28720
|
+
}
|
|
28324
28721
|
|
|
28325
28722
|
// ../../packages/llm/src/client.ts
|
|
28326
28723
|
import { generateText, streamText, generateObject } from "ai";
|
|
@@ -28616,7 +29013,7 @@ function logAttempt({
|
|
|
28616
29013
|
console.warn(`[llm-attempt-failed] ${parts.join(" ")}`);
|
|
28617
29014
|
}
|
|
28618
29015
|
function sleep(ms) {
|
|
28619
|
-
return new Promise((
|
|
29016
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
28620
29017
|
}
|
|
28621
29018
|
|
|
28622
29019
|
// ../../packages/llm/src/tool-choice-floor.ts
|
|
@@ -29658,192 +30055,8 @@ function createToolRegistry() {
|
|
|
29658
30055
|
};
|
|
29659
30056
|
}
|
|
29660
30057
|
|
|
29661
|
-
// ../../packages/tools/src/catastrophic-command.ts
|
|
29662
|
-
var FORK_BOMB = /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:?\s*&\s*\}\s*;\s*:/;
|
|
29663
|
-
var MKFS = /\bmkfs(\.\w+)?\b/i;
|
|
29664
|
-
var DD_TO_DEVICE = /\bdd\b[\s\S]*?\bof=\/dev\/[a-z]/i;
|
|
29665
|
-
var POWER_STATE = /\b(shutdown|reboot|halt|poweroff|stop-computer|restart-computer)\b/i;
|
|
29666
|
-
var INIT_RUNLEVEL = /\binit\s+[06]\b/;
|
|
29667
|
-
var OVERWRITE_DEVICE = />\s*\/dev\/(sd[a-z]|nvme\d|disk\d|hd[a-z])/i;
|
|
29668
|
-
var DISKPART = /\bdiskpart\b/i;
|
|
29669
|
-
var DISK_CMDLET = /\b(format-volume|clear-disk|initialize-disk)\b/i;
|
|
29670
|
-
var AWK_CODE_EXEC = /\bawk\b[\s\S]*?(\bsystem\s*\(|\bgetline\b[^;]*\||["'][^"']*\|\s*["']?\s*(?:sh|bash|cmd))/i;
|
|
29671
|
-
function stripQuotes(token) {
|
|
29672
|
-
return token.replace(/^["']|["']$/g, "");
|
|
29673
|
-
}
|
|
29674
|
-
function normalizeSlashes(s) {
|
|
29675
|
-
return s.replace(/\/{2,}/g, "/").replace(/\\{2,}/g, "\\");
|
|
29676
|
-
}
|
|
29677
|
-
var WRAPPER_LEADER = /* @__PURE__ */ new Set([
|
|
29678
|
-
"cmd",
|
|
29679
|
-
"cmd.exe",
|
|
29680
|
-
"powershell",
|
|
29681
|
-
"powershell.exe",
|
|
29682
|
-
"pwsh",
|
|
29683
|
-
"pwsh.exe",
|
|
29684
|
-
"sh",
|
|
29685
|
-
"bash",
|
|
29686
|
-
"sudo"
|
|
29687
|
-
]);
|
|
29688
|
-
function stripWrapperPrefix(tokens) {
|
|
29689
|
-
let i = 0;
|
|
29690
|
-
while (i < tokens.length && WRAPPER_LEADER.has((tokens[i] ?? "").toLowerCase())) {
|
|
29691
|
-
i += 1;
|
|
29692
|
-
if (i < tokens.length && /^[-/]/.test(tokens[i] ?? "")) {
|
|
29693
|
-
i += 1;
|
|
29694
|
-
}
|
|
29695
|
-
}
|
|
29696
|
-
return tokens.slice(i);
|
|
29697
|
-
}
|
|
29698
|
-
var PASSTHROUGH_LEADERS = /* @__PURE__ */ new Set(["sudo", "env", "cmd", "cmd.exe"]);
|
|
29699
|
-
function interpreterBasename(token) {
|
|
29700
|
-
const t = stripQuotes(token);
|
|
29701
|
-
const base = t.split(/[\\/]/).pop() ?? t;
|
|
29702
|
-
return base.replace(/\.(exe|com)$/i, "").toLowerCase();
|
|
29703
|
-
}
|
|
29704
|
-
function skipPassthroughLeaders(tokens) {
|
|
29705
|
-
let i = 0;
|
|
29706
|
-
while (i < tokens.length && PASSTHROUGH_LEADERS.has(interpreterBasename(tokens[i] ?? ""))) {
|
|
29707
|
-
i += 1;
|
|
29708
|
-
while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i] ?? "")) i += 1;
|
|
29709
|
-
if (i < tokens.length && /^[-/]/.test(tokens[i] ?? "")) i += 1;
|
|
29710
|
-
}
|
|
29711
|
-
return tokens.slice(i);
|
|
29712
|
-
}
|
|
29713
|
-
function interpreterKind(name) {
|
|
29714
|
-
if (name === "py" || /^python[0-9.]*$/.test(name)) return "python";
|
|
29715
|
-
if (/^node(js)?$/.test(name)) return "node";
|
|
29716
|
-
if (/^perl[0-9.]*$/.test(name)) return "perl";
|
|
29717
|
-
if (/^ruby[0-9.]*$/.test(name)) return "ruby";
|
|
29718
|
-
if (/^php[0-9.]*$/.test(name)) return "php";
|
|
29719
|
-
if (["sh", "bash", "zsh", "ksh", "dash", "ash"].includes(name)) return "shell";
|
|
29720
|
-
if (name === "powershell" || name === "pwsh") return "powershell";
|
|
29721
|
-
return null;
|
|
29722
|
-
}
|
|
29723
|
-
function isInlineEvalFlag(kind, flag) {
|
|
29724
|
-
switch (kind) {
|
|
29725
|
-
case "python":
|
|
29726
|
-
return flag === "-c";
|
|
29727
|
-
case "node":
|
|
29728
|
-
return flag === "-e" || flag === "--eval" || flag === "-p" || flag === "--print";
|
|
29729
|
-
case "perl":
|
|
29730
|
-
case "ruby":
|
|
29731
|
-
return flag === "-e";
|
|
29732
|
-
case "php":
|
|
29733
|
-
return flag === "-r";
|
|
29734
|
-
case "shell":
|
|
29735
|
-
return flag === "-c";
|
|
29736
|
-
case "powershell":
|
|
29737
|
-
return /^-com/.test(flag) || /^-enc/.test(flag);
|
|
29738
|
-
default:
|
|
29739
|
-
return false;
|
|
29740
|
-
}
|
|
29741
|
-
}
|
|
29742
|
-
function hasInlineInterpreterEval(tokens) {
|
|
29743
|
-
const rest = skipPassthroughLeaders(tokens);
|
|
29744
|
-
if (rest.length === 0) return false;
|
|
29745
|
-
const kind = interpreterKind(interpreterBasename(rest[0] ?? ""));
|
|
29746
|
-
if (!kind) return false;
|
|
29747
|
-
return rest.slice(1).some((t) => isInlineEvalFlag(kind, stripQuotes(t).toLowerCase()));
|
|
29748
|
-
}
|
|
29749
|
-
function hasPipeIntoBareInterpreter(c) {
|
|
29750
|
-
const parts = c.split("|");
|
|
29751
|
-
for (let i = 1; i < parts.length; i++) {
|
|
29752
|
-
const toks = (parts[i] ?? "").trim().split(/\s+/).map(stripQuotes).filter((t) => t.length > 0);
|
|
29753
|
-
const rest = skipPassthroughLeaders(toks);
|
|
29754
|
-
if (rest.length === 0) continue;
|
|
29755
|
-
if (!interpreterKind(interpreterBasename(rest[0] ?? ""))) continue;
|
|
29756
|
-
const hasScriptArg = rest.slice(1).some((t) => !t.startsWith("-"));
|
|
29757
|
-
if (!hasScriptArg) return true;
|
|
29758
|
-
}
|
|
29759
|
-
return false;
|
|
29760
|
-
}
|
|
29761
|
-
function isWindowsRootOrWildcardTarget(token) {
|
|
29762
|
-
const t = stripQuotes(token);
|
|
29763
|
-
if (t === "*" || t === "\\" || t === "/") return true;
|
|
29764
|
-
if (/^[a-z]:\\?\*?$/i.test(t)) return true;
|
|
29765
|
-
if (/^%(systemdrive|systemroot|userprofile)%\\?\*?$/i.test(t)) return true;
|
|
29766
|
-
if (/^\$env:(systemdrive|systemroot|userprofile)\\?\*?$/i.test(t)) return true;
|
|
29767
|
-
return false;
|
|
29768
|
-
}
|
|
29769
|
-
function isCatastrophicCommand(cmd) {
|
|
29770
|
-
if (typeof cmd !== "string" || cmd.trim() === "") return false;
|
|
29771
|
-
const c = normalizeSlashes(cmd.trim());
|
|
29772
|
-
if (FORK_BOMB.test(c) || MKFS.test(c) || DD_TO_DEVICE.test(c) || POWER_STATE.test(c) || INIT_RUNLEVEL.test(c) || OVERWRITE_DEVICE.test(c) || DISKPART.test(c) || DISK_CMDLET.test(c) || AWK_CODE_EXEC.test(c) || hasPipeIntoBareInterpreter(c)) {
|
|
29773
|
-
return true;
|
|
29774
|
-
}
|
|
29775
|
-
for (const seg of c.split(/[;&|\n\r]+/)) {
|
|
29776
|
-
const s = seg.trim();
|
|
29777
|
-
if (!s) continue;
|
|
29778
|
-
const tokens = s.split(/\s+/).map(stripQuotes);
|
|
29779
|
-
if (hasInlineInterpreterEval(tokens)) return true;
|
|
29780
|
-
const cmdTokens = stripWrapperPrefix(tokens);
|
|
29781
|
-
const cmdWord = cmdTokens[0] ?? "";
|
|
29782
|
-
if (/^rm$/i.test(cmdWord)) {
|
|
29783
|
-
const recursive = /\s-\S*r/i.test(s) || /\s--recursive\b/i.test(s);
|
|
29784
|
-
const force = /\s-\S*f/i.test(s) || /\s--force\b/i.test(s);
|
|
29785
|
-
if (recursive && force) {
|
|
29786
|
-
if (/\s--no-preserve-root\b/i.test(s)) return true;
|
|
29787
|
-
if (/(\s|=)(\/|\/\*|~|~\/\*?|\$HOME\/?\*?|\*)(\s|$|"|')/.test(s)) return true;
|
|
29788
|
-
if (tokens.some((t) => isWindowsRootOrWildcardTarget(t))) return true;
|
|
29789
|
-
}
|
|
29790
|
-
}
|
|
29791
|
-
if (/^format(\.(com|exe))?$/i.test(cmdWord) && cmdTokens.slice(1).some((d) => /^[a-z]:([\\/]\*?)?$/i.test(d))) {
|
|
29792
|
-
return true;
|
|
29793
|
-
}
|
|
29794
|
-
if (/^(ri|remove-item|del|erase|rd|rmdir)$/i.test(cmdWord)) {
|
|
29795
|
-
const psRecursiveForce = /(^|\s)-r(ecurse)?\b/i.test(s) && /(^|\s)-f(orce)?\b/i.test(s);
|
|
29796
|
-
const cmdRecursiveForce = /\/s\b/i.test(s) && /\/q\b/i.test(s);
|
|
29797
|
-
if (psRecursiveForce || cmdRecursiveForce) {
|
|
29798
|
-
if (tokens.some((t) => isWindowsRootOrWildcardTarget(t))) return true;
|
|
29799
|
-
}
|
|
29800
|
-
}
|
|
29801
|
-
}
|
|
29802
|
-
return false;
|
|
29803
|
-
}
|
|
29804
|
-
function isInlineInterpreterEvalCommand(cmd) {
|
|
29805
|
-
if (typeof cmd !== "string" || cmd.trim() === "") return false;
|
|
29806
|
-
const c = normalizeSlashes(cmd.trim());
|
|
29807
|
-
for (const seg of c.split(/[;&|\n\r]+/)) {
|
|
29808
|
-
const s = seg.trim();
|
|
29809
|
-
if (!s) continue;
|
|
29810
|
-
const tokens = s.split(/\s+/).map(stripQuotes);
|
|
29811
|
-
if (hasInlineInterpreterEval(tokens)) return true;
|
|
29812
|
-
}
|
|
29813
|
-
return false;
|
|
29814
|
-
}
|
|
29815
|
-
var DESTRUCTIVE_PATTERNS = [
|
|
29816
|
-
/\b(rm|rmdir|unlink|shred)\b/i,
|
|
29817
|
-
// delete (unix)
|
|
29818
|
-
/\bdel\s|\bRemove-Item\b|\brd\s+\/s/i,
|
|
29819
|
-
// delete (windows/ps)
|
|
29820
|
-
/\bfind\b[^\n]*-delete\b/i,
|
|
29821
|
-
// find … -delete
|
|
29822
|
-
/\b(pip3?|npm|pnpm|yarn|apt|apt-get|yum|dnf|brew|pacman|choco|winget|uvx|pipx|cargo|gem|conda|comfy)\b[^\n]*\binstall\b/i,
|
|
29823
|
-
// pkg install
|
|
29824
|
-
/\bgo\s+install\b|\bcomfy\b[^\n]*\bmodel\s+download\b|\bpip3?\b[^\n]*\bdownload\b/i,
|
|
29825
|
-
// go install / model dl
|
|
29826
|
-
/\bwget\b|\bgit\s+clone\b|\bcurl\b[^\n]*\s-[oO]\b|\bInvoke-WebRequest\b|\biwr\b[^\n]*-OutFile/i,
|
|
29827
|
-
// large download / clone
|
|
29828
|
-
/\b(kill|pkill|killall|taskkill)\b|\bStop-Process\b|\bStop-Service\b/i,
|
|
29829
|
-
// process kill
|
|
29830
|
-
/\bsystemctl\b|\bsc\s+(stop|delete)\b|\bservice\b[^\n]*\b(stop|restart)\b/i,
|
|
29831
|
-
// service control
|
|
29832
|
-
/\bmkfs(\.\w+)?\b|\bdd\b[^\n]*\bof=|\b(format|fdisk|parted|diskpart)\b/i,
|
|
29833
|
-
// disk ops
|
|
29834
|
-
/\bchmod\s+-\S*R|\bchown\s+-\S*R|\bicacls\b/i,
|
|
29835
|
-
// recursive perms/ownership
|
|
29836
|
-
/\bgit\s+push\b[^\n]*(--force|-f\b)|\bgit\s+reset\s+--hard\b|\bgit\s+clean\s+-\S*f|\bgit\s+branch\s+-D\b/i
|
|
29837
|
-
// destructive VCS
|
|
29838
|
-
];
|
|
29839
|
-
function isDestructiveOrHeavyCommand(cmd) {
|
|
29840
|
-
if (typeof cmd !== "string" || cmd.trim() === "") return false;
|
|
29841
|
-
if (isCatastrophicCommand(cmd)) return true;
|
|
29842
|
-
const c = normalizeSlashes(cmd.trim());
|
|
29843
|
-
return DESTRUCTIVE_PATTERNS.some((re) => re.test(c));
|
|
29844
|
-
}
|
|
29845
|
-
|
|
29846
30058
|
// ../../packages/tools/src/execute.ts
|
|
30059
|
+
init_src();
|
|
29847
30060
|
async function executeTool(tool, rawInput, ctx, opts) {
|
|
29848
30061
|
const startMs = Date.now();
|
|
29849
30062
|
const parsed = tool.inputSchema.safeParse(rawInput);
|
|
@@ -29864,12 +30077,18 @@ async function executeTool(tool, rawInput, ctx, opts) {
|
|
|
29864
30077
|
effectiveAction = "auto_approve";
|
|
29865
30078
|
} else if (opts.autonomy === "destructive_gate") {
|
|
29866
30079
|
const command = String(validatedInput?.command ?? "");
|
|
30080
|
+
const mcpTransport = String(validatedInput?.transport ?? "");
|
|
29867
30081
|
let isHeavy;
|
|
29868
30082
|
if (tool.name === "run_command") isHeavy = isDestructiveOrHeavyCommand(command);
|
|
30083
|
+
else if (tool.name === "create_mcp" && mcpTransport === "stdio") isHeavy = true;
|
|
29869
30084
|
else isHeavy = tool.riskLevel === "destructive";
|
|
29870
30085
|
if (!isHeavy) effectiveAction = "auto_approve";
|
|
29871
30086
|
}
|
|
29872
30087
|
}
|
|
30088
|
+
if (!matchedRule && effectiveAction !== "block" && effectiveAction !== "require_approval" && tool.computeApproval && opts.autonomy !== "fully_autonomous") {
|
|
30089
|
+
const dynamic = await tool.computeApproval(validatedInput, ctx);
|
|
30090
|
+
if (dynamic === "require_approval") effectiveAction = "require_approval";
|
|
30091
|
+
}
|
|
29873
30092
|
if (tool.name === "run_command" && effectiveAction !== "block" && effectiveAction !== "require_approval" && isCatastrophicCommand(String(validatedInput?.command ?? ""))) {
|
|
29874
30093
|
effectiveAction = "require_approval";
|
|
29875
30094
|
}
|
|
@@ -29890,6 +30109,13 @@ async function executeTool(tool, rawInput, ctx, opts) {
|
|
|
29890
30109
|
jobId: ctx.jobId,
|
|
29891
30110
|
agentId: ctx.agentId,
|
|
29892
30111
|
toolName: tool.name,
|
|
30112
|
+
// NOUVEAU-1: unlike tool_calls, this column is NOT redacted at rest —
|
|
30113
|
+
// the approval re-exec path (apps/runner/src/job/execute.ts) reads it
|
|
30114
|
+
// back verbatim to re-run the approved call, so a redacted apiKey here
|
|
30115
|
+
// would store '***' as the real secret. Instead, every DISPLAY of this
|
|
30116
|
+
// row is redacted at load (web approvals loader + Telegram notify).
|
|
30117
|
+
// Encrypting the secret fields at rest here (and decrypting on re-exec)
|
|
30118
|
+
// is the tracked follow-up.
|
|
29893
30119
|
toolInput: validatedInput,
|
|
29894
30120
|
status: "pending"
|
|
29895
30121
|
}).returning();
|
|
@@ -29970,7 +30196,12 @@ async function _writeToolCall(ctx, toolName, input, output, durationMs) {
|
|
|
29970
30196
|
entityId: ctx.entityId,
|
|
29971
30197
|
jobId: ctx.jobId,
|
|
29972
30198
|
toolName,
|
|
29973
|
-
|
|
30199
|
+
// NOUVEAU-1: the audit trail is never re-executed, so we store a
|
|
30200
|
+
// secret-redacted copy — create_connector/create_mcp API keys and stdio
|
|
30201
|
+
// env values must not sit in cleartext in tool_calls or render to the
|
|
30202
|
+
// logs dashboard (LogsTable). The real value already reached the tool's
|
|
30203
|
+
// execute() above; only this copy is masked.
|
|
30204
|
+
toolInput: redactSecretsForAudit(input),
|
|
29974
30205
|
toolOutput: output,
|
|
29975
30206
|
durationMs
|
|
29976
30207
|
});
|
|
@@ -29978,6 +30209,9 @@ async function _writeToolCall(ctx, toolName, input, output, durationMs) {
|
|
|
29978
30209
|
}
|
|
29979
30210
|
}
|
|
29980
30211
|
|
|
30212
|
+
// ../../packages/tools/src/index.ts
|
|
30213
|
+
init_src();
|
|
30214
|
+
|
|
29981
30215
|
// ../../packages/tools/src/tool-choice.ts
|
|
29982
30216
|
function computeToolChoice(cfg) {
|
|
29983
30217
|
const { isOrchestrator, turn, hasAdapterTools, modelSupportsForcedToolChoice = true } = cfg;
|
|
@@ -30309,7 +30543,7 @@ async function backfillEmbeddings(db, embeddingClient) {
|
|
|
30309
30543
|
console.warn(`[memory] backfill: row ${row.id} failed (will retry next run):`, err);
|
|
30310
30544
|
}
|
|
30311
30545
|
if ((i + 1) % BATCH_SIZE === 0 && i + 1 < rows.length) {
|
|
30312
|
-
await new Promise((
|
|
30546
|
+
await new Promise((resolve3) => setTimeout(resolve3, BATCH_PAUSE_MS));
|
|
30313
30547
|
}
|
|
30314
30548
|
}
|
|
30315
30549
|
return { candidates: rows.length, embedded };
|
|
@@ -30533,20 +30767,28 @@ async function selectMemoriesForInjection(db, opts) {
|
|
|
30533
30767
|
);
|
|
30534
30768
|
const rawQuery = opts.query ?? "";
|
|
30535
30769
|
const tokens = tokenizeQuery(rawQuery);
|
|
30770
|
+
let packed;
|
|
30536
30771
|
if (tokens.length > 0) {
|
|
30537
30772
|
const tsv = sql20`"agent_memory"."search_tsv"`;
|
|
30538
|
-
const tsq = sql20`
|
|
30539
|
-
const
|
|
30773
|
+
const tsq = sql20`to_tsquery('english', ${tokens.join(" | ")})`;
|
|
30774
|
+
const candidates = await db.select().from(agentMemory).where(baseConditions).orderBy(
|
|
30540
30775
|
sql20`ts_rank(${tsv}, ${tsq}) DESC`,
|
|
30541
30776
|
desc(agentMemory.importance),
|
|
30542
30777
|
desc(agentMemory.lastAccessedAt)
|
|
30543
30778
|
).limit(MAX_CANDIDATES);
|
|
30544
|
-
const
|
|
30545
|
-
|
|
30779
|
+
const memories = candidates.map((row) => rowToMemory(row));
|
|
30780
|
+
packed = packUnderBudget(memories, opts.maxChars);
|
|
30781
|
+
} else {
|
|
30782
|
+
const candidates = await db.select().from(agentMemory).where(baseConditions).orderBy(desc(agentMemory.importance), desc(agentMemory.lastAccessedAt)).limit(MAX_CANDIDATES);
|
|
30783
|
+
const memories = candidates.map((row) => rowToMemory(row));
|
|
30784
|
+
packed = selectMemoriesUnderBudget(memories, opts.maxChars, opts.query);
|
|
30546
30785
|
}
|
|
30547
|
-
|
|
30548
|
-
|
|
30549
|
-
|
|
30786
|
+
await touchMemories(
|
|
30787
|
+
db,
|
|
30788
|
+
packed.map((m) => m.id),
|
|
30789
|
+
opts.entityId
|
|
30790
|
+
);
|
|
30791
|
+
return packed;
|
|
30550
30792
|
}
|
|
30551
30793
|
|
|
30552
30794
|
// ../../packages/memory/src/find.ts
|
|
@@ -31067,6 +31309,28 @@ function getWorkspaceRootForDisplay(ctx, relativeOrLabeledPath) {
|
|
|
31067
31309
|
}
|
|
31068
31310
|
return list[0].path;
|
|
31069
31311
|
}
|
|
31312
|
+
async function isPathInSharedWorkspace(ctx, resolvedPath) {
|
|
31313
|
+
const shared = ctx.workspaces?.find((ws) => ws.label === SHARED_WORKSPACE_LABEL);
|
|
31314
|
+
if (!shared) return false;
|
|
31315
|
+
const realShared = await realpath(shared.path).catch(() => void 0);
|
|
31316
|
+
if (!realShared) return false;
|
|
31317
|
+
const rootWithSep = realShared.endsWith(sep) ? realShared : realShared + sep;
|
|
31318
|
+
return resolvedPath === realShared || resolvedPath.startsWith(rootWithSep);
|
|
31319
|
+
}
|
|
31320
|
+
async function computeSharedOverwriteApproval(ctx, requestedPath) {
|
|
31321
|
+
let resolved;
|
|
31322
|
+
try {
|
|
31323
|
+
resolved = await resolveAndCheckPath(ctx, requestedPath);
|
|
31324
|
+
} catch {
|
|
31325
|
+
return void 0;
|
|
31326
|
+
}
|
|
31327
|
+
if (!await isPathInSharedWorkspace(ctx, resolved)) return void 0;
|
|
31328
|
+
const exists = await stat(resolved).then(
|
|
31329
|
+
() => true,
|
|
31330
|
+
() => false
|
|
31331
|
+
);
|
|
31332
|
+
return exists ? "require_approval" : void 0;
|
|
31333
|
+
}
|
|
31070
31334
|
|
|
31071
31335
|
// ../../packages/tools/src/builtin/file-ops/read-lines.ts
|
|
31072
31336
|
import { createReadStream } from "node:fs";
|
|
@@ -31210,13 +31474,20 @@ import { z as z28 } from "zod";
|
|
|
31210
31474
|
var FileWriteInputSchema = z28.object({
|
|
31211
31475
|
path: z28.string().min(1).describe("Path to the file, relative to the agent workspace root. Created if missing."),
|
|
31212
31476
|
content: z28.string().describe("Full file contents to write. Overwrites any existing file."),
|
|
31213
|
-
create_dirs: z28.boolean().optional().default(false).describe("If true, create missing parent directories.")
|
|
31477
|
+
create_dirs: z28.boolean().optional().default(false).describe("If true, create missing parent directories."),
|
|
31478
|
+
purpose: z28.string().optional().describe(
|
|
31479
|
+
"OPTIONAL. One short sentence on WHY this write is needed. Shown first on the approval card when this call requires human review (e.g. overwriting an existing file in the shared workspace) \u2014 say why in plain language, not what you already say in `path`/`content`."
|
|
31480
|
+
)
|
|
31214
31481
|
});
|
|
31215
31482
|
var fileWriteTool = {
|
|
31216
31483
|
name: "file_write",
|
|
31217
31484
|
description: "Write (or overwrite) a file in the agent workspace. Atomic: writes to a tempfile then renames over the target, so partial-write failures never corrupt the original. Use `file_edit` instead when you only need to change part of an existing file \u2014 it preserves lines you do not touch. Max 1 MiB per write.",
|
|
31218
31485
|
inputSchema: FileWriteInputSchema,
|
|
31219
31486
|
riskLevel: "write",
|
|
31487
|
+
// D1: gate ONLY the destructive case — overwriting a file that already
|
|
31488
|
+
// exists in the entity-wide SHARED workspace. A brand-new file, or a write
|
|
31489
|
+
// into an attached/private workspace, never gates (see computeSharedOverwriteApproval).
|
|
31490
|
+
computeApproval: (input, ctx) => computeSharedOverwriteApproval(ctx, input.path),
|
|
31220
31491
|
execute: async (input, ctx) => {
|
|
31221
31492
|
try {
|
|
31222
31493
|
const bytes = Buffer.byteLength(input.content, "utf8");
|
|
@@ -31265,13 +31536,20 @@ var FileEditInputSchema = z29.object({
|
|
|
31265
31536
|
"Exact substring to replace. Must appear in the file verbatim \u2014 whitespace and line endings count. Must be unique unless replace_all:true."
|
|
31266
31537
|
),
|
|
31267
31538
|
new_string: z29.string().describe('Replacement text. Pass "" to delete the matched substring.'),
|
|
31268
|
-
replace_all: z29.boolean().optional().default(false).describe("Replace every occurrence (default false: require exactly one match).")
|
|
31539
|
+
replace_all: z29.boolean().optional().default(false).describe("Replace every occurrence (default false: require exactly one match)."),
|
|
31540
|
+
purpose: z29.string().optional().describe(
|
|
31541
|
+
"OPTIONAL. One short sentence on WHY this edit is needed. Shown first on the approval card when this call requires human review (e.g. editing a file in the shared workspace) \u2014 say why in plain language, not what you already say in `path`."
|
|
31542
|
+
)
|
|
31269
31543
|
});
|
|
31270
31544
|
var fileEditTool = {
|
|
31271
31545
|
name: "file_edit",
|
|
31272
31546
|
description: "Edit an existing file by replacing an exact substring. Quote the chunk to change verbatim (including whitespace). Fails loud when the substring is missing (you misquoted) or matches multiple places (ambiguous \u2014 narrow the quote, or pass replace_all:true). Use this instead of file_write when you only want to change part of a file \u2014 preserves untouched lines.",
|
|
31273
31547
|
inputSchema: FileEditInputSchema,
|
|
31274
31548
|
riskLevel: "write",
|
|
31549
|
+
// D1: an edit always targets an EXISTING file (file_edit fails loud on a
|
|
31550
|
+
// missing one — see execute() below), so the only thing left to check is
|
|
31551
|
+
// whether that file lives in the shared workspace. Same gate as file_write.
|
|
31552
|
+
computeApproval: (input, ctx) => computeSharedOverwriteApproval(ctx, input.path),
|
|
31275
31553
|
execute: async (input, ctx) => {
|
|
31276
31554
|
try {
|
|
31277
31555
|
const path = await resolveAndCheckPath(ctx, input.path);
|
|
@@ -31595,7 +31873,9 @@ async function validateOfficeZipBomb(buf, maxInflatedBytes = MAX_OFFICE_INFLATED
|
|
|
31595
31873
|
nodeStream.on("error", (err) => {
|
|
31596
31874
|
if (settled) return;
|
|
31597
31875
|
settled = true;
|
|
31598
|
-
reject(
|
|
31876
|
+
reject(
|
|
31877
|
+
new Error(`Corrupt entry "${entry.name}" inside the Office archive: ${err.message}`)
|
|
31878
|
+
);
|
|
31599
31879
|
});
|
|
31600
31880
|
});
|
|
31601
31881
|
if (violation) return { ok: false, reason: violation };
|
|
@@ -34428,7 +34708,10 @@ async function resolveSkillId(db, entityId, slugOrName) {
|
|
|
34428
34708
|
const [row] = await db.select({ id: agentSkills.id }).from(agentSkills).where(
|
|
34429
34709
|
and4(
|
|
34430
34710
|
eq5(agentSkills.entityId, entityId),
|
|
34431
|
-
or3(
|
|
34711
|
+
or3(
|
|
34712
|
+
eq5(agentSkills.slug, slugOrName),
|
|
34713
|
+
ilike2(agentSkills.name, escapeLikePattern(slugOrName))
|
|
34714
|
+
)
|
|
34432
34715
|
)
|
|
34433
34716
|
).limit(1);
|
|
34434
34717
|
return row?.id ?? null;
|
|
@@ -35503,7 +35786,7 @@ var runCommandTool = {
|
|
|
35503
35786
|
}
|
|
35504
35787
|
};
|
|
35505
35788
|
function runInShell(command, cwd, timeoutMs) {
|
|
35506
|
-
return new Promise((
|
|
35789
|
+
return new Promise((resolve3) => {
|
|
35507
35790
|
const isWindows = process.platform === "win32";
|
|
35508
35791
|
const child = spawn(command, {
|
|
35509
35792
|
cwd,
|
|
@@ -35566,7 +35849,7 @@ function runInShell(command, cwd, timeoutMs) {
|
|
|
35566
35849
|
settled = true;
|
|
35567
35850
|
clearTimeout(timer);
|
|
35568
35851
|
if (graceTimer) clearTimeout(graceTimer);
|
|
35569
|
-
|
|
35852
|
+
resolve3({ exitCode, stdout, stderr, timedOut, truncated, cwd });
|
|
35570
35853
|
};
|
|
35571
35854
|
const timer = setTimeout(() => {
|
|
35572
35855
|
timedOut = true;
|
|
@@ -35655,7 +35938,7 @@ var runSkillScriptTool = {
|
|
|
35655
35938
|
}
|
|
35656
35939
|
};
|
|
35657
35940
|
function runScript(interpreter, argv, cwd, timeoutMs, scriptLabel) {
|
|
35658
|
-
return new Promise((
|
|
35941
|
+
return new Promise((resolve3) => {
|
|
35659
35942
|
const isWindows = process.platform === "win32";
|
|
35660
35943
|
const child = spawn2(interpreter, argv, {
|
|
35661
35944
|
cwd,
|
|
@@ -35715,7 +35998,7 @@ function runScript(interpreter, argv, cwd, timeoutMs, scriptLabel) {
|
|
|
35715
35998
|
settled = true;
|
|
35716
35999
|
clearTimeout(timer);
|
|
35717
36000
|
if (graceTimer) clearTimeout(graceTimer);
|
|
35718
|
-
|
|
36001
|
+
resolve3({ exitCode, stdout, stderr, timedOut, truncated, interpreter, script: scriptLabel });
|
|
35719
36002
|
};
|
|
35720
36003
|
const timer = setTimeout(() => {
|
|
35721
36004
|
timedOut = true;
|
|
@@ -35983,7 +36266,7 @@ ${line}` : line;
|
|
|
35983
36266
|
return chunks;
|
|
35984
36267
|
}
|
|
35985
36268
|
function sleep2(ms) {
|
|
35986
|
-
return new Promise((
|
|
36269
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
35987
36270
|
}
|
|
35988
36271
|
var MAX_RATE_LIMIT_RETRIES = 3;
|
|
35989
36272
|
var DEFAULT_RATE_LIMIT_BACKOFF_MS = 1e3;
|
|
@@ -36461,8 +36744,11 @@ Fail conditions:
|
|
|
36461
36744
|
err.name = "telegram_no_recipient";
|
|
36462
36745
|
throw err;
|
|
36463
36746
|
}
|
|
36464
|
-
|
|
36465
|
-
|
|
36747
|
+
let botToken = ctx.resolvedTelegramBotToken;
|
|
36748
|
+
if (botToken === void 0) {
|
|
36749
|
+
const agentRows = await ctx.db.select({ telegramBotToken: agents.telegramBotToken }).from(agents).where(eq5(agents.id, ctx.agentId)).limit(1);
|
|
36750
|
+
botToken = agentRows[0]?.telegramBotToken ?? void 0;
|
|
36751
|
+
}
|
|
36466
36752
|
if (!botToken) {
|
|
36467
36753
|
const err = new Error("telegram_no_bot_token");
|
|
36468
36754
|
err.name = "telegram_no_bot_token";
|
|
@@ -36527,8 +36813,11 @@ Fail conditions:
|
|
|
36527
36813
|
err.name = "no_recipient";
|
|
36528
36814
|
throw err;
|
|
36529
36815
|
}
|
|
36530
|
-
|
|
36531
|
-
|
|
36816
|
+
let botToken = ctx.resolvedTelegramBotToken;
|
|
36817
|
+
if (botToken === void 0) {
|
|
36818
|
+
const agentRows = await ctx.db.select({ telegramBotToken: agents.telegramBotToken }).from(agents).where(eq5(agents.id, ctx.agentId)).limit(1);
|
|
36819
|
+
botToken = agentRows[0]?.telegramBotToken ?? void 0;
|
|
36820
|
+
}
|
|
36532
36821
|
if (!botToken) {
|
|
36533
36822
|
const err = new Error("no_bot_token");
|
|
36534
36823
|
err.name = "no_bot_token";
|
|
@@ -36638,8 +36927,11 @@ Fail conditions:
|
|
|
36638
36927
|
err.name = "no_recipient";
|
|
36639
36928
|
throw err;
|
|
36640
36929
|
}
|
|
36641
|
-
|
|
36642
|
-
|
|
36930
|
+
let botToken = ctx.resolvedTelegramBotToken;
|
|
36931
|
+
if (botToken === void 0) {
|
|
36932
|
+
const agentRows = await ctx.db.select({ telegramBotToken: agents.telegramBotToken }).from(agents).where(eq5(agents.id, ctx.agentId)).limit(1);
|
|
36933
|
+
botToken = agentRows[0]?.telegramBotToken ?? void 0;
|
|
36934
|
+
}
|
|
36643
36935
|
if (!botToken) {
|
|
36644
36936
|
const err = new Error("no_bot_token");
|
|
36645
36937
|
err.name = "no_bot_token";
|
|
@@ -36725,8 +37017,11 @@ function makeSendMediaTool(spec) {
|
|
|
36725
37017
|
err.name = "no_recipient";
|
|
36726
37018
|
throw err;
|
|
36727
37019
|
}
|
|
36728
|
-
|
|
36729
|
-
|
|
37020
|
+
let botToken = ctx.resolvedTelegramBotToken;
|
|
37021
|
+
if (botToken === void 0) {
|
|
37022
|
+
const agentRows = await ctx.db.select({ telegramBotToken: agents.telegramBotToken }).from(agents).where(eq5(agents.id, ctx.agentId)).limit(1);
|
|
37023
|
+
botToken = agentRows[0]?.telegramBotToken ?? void 0;
|
|
37024
|
+
}
|
|
36730
37025
|
if (!botToken) {
|
|
36731
37026
|
const err = new Error("no_bot_token");
|
|
36732
37027
|
err.name = "no_bot_token";
|
|
@@ -36847,6 +37142,16 @@ Size cap: 50 MB \u2192 throws \`voice_too_large\`. Other failures: \`no_recipien
|
|
|
36847
37142
|
});
|
|
36848
37143
|
}
|
|
36849
37144
|
|
|
37145
|
+
// ../../packages/tools/src/communication/index.ts
|
|
37146
|
+
var DELIVERY_TOOL_NAMES = [
|
|
37147
|
+
"telegram_send_message",
|
|
37148
|
+
"send_image",
|
|
37149
|
+
"send_file",
|
|
37150
|
+
"send_video",
|
|
37151
|
+
"send_audio",
|
|
37152
|
+
"send_voice"
|
|
37153
|
+
];
|
|
37154
|
+
|
|
36850
37155
|
// ../../packages/auth/src/types.ts
|
|
36851
37156
|
var AuthError = class extends Error {
|
|
36852
37157
|
code = "AUTH_REQUIRED";
|
|
@@ -36981,7 +37286,8 @@ async function healthRoute(c, deps) {
|
|
|
36981
37286
|
await deps.db.execute(sql20`SELECT 1`);
|
|
36982
37287
|
result.db = "ok";
|
|
36983
37288
|
} catch (err) {
|
|
36984
|
-
|
|
37289
|
+
console.error("[health] DB ping failed:", err instanceof Error ? err.message : err);
|
|
37290
|
+
result.dbError = "unavailable";
|
|
36985
37291
|
}
|
|
36986
37292
|
result.ok = result.db === "ok";
|
|
36987
37293
|
return c.json(result, result.ok ? 200 : 503);
|
|
@@ -37040,14 +37346,16 @@ async function agentRoute(c, deps, runnerEnv) {
|
|
|
37040
37346
|
).limit(1);
|
|
37041
37347
|
agentId = defaultAgentRows[0]?.id ?? null;
|
|
37042
37348
|
}
|
|
37349
|
+
let conversationId = null;
|
|
37043
37350
|
if (parentJobId) {
|
|
37044
|
-
const [parentJob] = await deps.db.select({ entityId: agentJobs.entityId }).from(agentJobs).where(eq5(agentJobs.id, parentJobId)).limit(1);
|
|
37351
|
+
const [parentJob] = await deps.db.select({ entityId: agentJobs.entityId, conversationId: agentJobs.conversationId }).from(agentJobs).where(eq5(agentJobs.id, parentJobId)).limit(1);
|
|
37045
37352
|
if (!parentJob) {
|
|
37046
37353
|
return c.json({ error: "parent_job_not_found" }, 400);
|
|
37047
37354
|
}
|
|
37048
37355
|
if (entityId && parentJob.entityId !== entityId) {
|
|
37049
37356
|
return c.json({ error: "parent_job_entity_mismatch" }, 403);
|
|
37050
37357
|
}
|
|
37358
|
+
conversationId = parentJob.conversationId;
|
|
37051
37359
|
}
|
|
37052
37360
|
const [job] = await deps.db.insert(agentJobs).values({
|
|
37053
37361
|
entityId: entityId ?? void 0,
|
|
@@ -37056,6 +37364,7 @@ async function agentRoute(c, deps, runnerEnv) {
|
|
|
37056
37364
|
task,
|
|
37057
37365
|
chatId: chatId ?? void 0,
|
|
37058
37366
|
parentJobId: parentJobId ?? void 0,
|
|
37367
|
+
conversationId: conversationId ?? void 0,
|
|
37059
37368
|
status: "pending",
|
|
37060
37369
|
messages: [{ role: "user", content: task }]
|
|
37061
37370
|
}).returning({ id: agentJobs.id });
|
|
@@ -38492,7 +38801,7 @@ var MAX_RATE_LIMIT_RETRIES2 = 3;
|
|
|
38492
38801
|
var DEFAULT_RATE_LIMIT_BACKOFF_MS2 = 1e3;
|
|
38493
38802
|
var MAX_RETRY_AFTER_MS2 = 6e4;
|
|
38494
38803
|
function sleep3(ms) {
|
|
38495
|
-
return new Promise((
|
|
38804
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
38496
38805
|
}
|
|
38497
38806
|
function createAirtableClient(getAccessToken, options = {}) {
|
|
38498
38807
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -38946,13 +39255,14 @@ function createAirtableTools(opts) {
|
|
|
38946
39255
|
|
|
38947
39256
|
// ../../packages/adapters/google-drive/src/client.ts
|
|
38948
39257
|
import { google } from "googleapis";
|
|
39258
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
38949
39259
|
function createDriveClient(getAccessToken) {
|
|
38950
39260
|
const auth2 = new google.auth.OAuth2();
|
|
38951
39261
|
auth2.refreshHandler = async () => ({
|
|
38952
39262
|
access_token: await getAccessToken(),
|
|
38953
39263
|
expiry_date: Date.now() - 1
|
|
38954
39264
|
});
|
|
38955
|
-
return google.drive({ version: "v3", auth: auth2 });
|
|
39265
|
+
return google.drive({ version: "v3", auth: auth2, timeout: DEFAULT_TIMEOUT_MS2 });
|
|
38956
39266
|
}
|
|
38957
39267
|
|
|
38958
39268
|
// ../../packages/adapters/google-drive/src/tools/list-files.ts
|
|
@@ -39772,13 +40082,14 @@ function createDriveTools(opts) {
|
|
|
39772
40082
|
|
|
39773
40083
|
// ../../packages/adapters/gmail/src/client.ts
|
|
39774
40084
|
import { google as google2 } from "googleapis";
|
|
40085
|
+
var DEFAULT_TIMEOUT_MS3 = 3e4;
|
|
39775
40086
|
function createGmailClient(getAccessToken) {
|
|
39776
40087
|
const auth2 = new google2.auth.OAuth2();
|
|
39777
40088
|
auth2.refreshHandler = async () => ({
|
|
39778
40089
|
access_token: await getAccessToken(),
|
|
39779
40090
|
expiry_date: Date.now() - 1
|
|
39780
40091
|
});
|
|
39781
|
-
return google2.gmail({ version: "v1", auth: auth2 });
|
|
40092
|
+
return google2.gmail({ version: "v1", auth: auth2, timeout: DEFAULT_TIMEOUT_MS3 });
|
|
39782
40093
|
}
|
|
39783
40094
|
|
|
39784
40095
|
// ../../packages/adapters/gmail/src/tools/messages.ts
|
|
@@ -39854,6 +40165,19 @@ async function paginateGmail(fetcher, pageSize = 100, maxItems = 100) {
|
|
|
39854
40165
|
return items.slice(0, cappedMax);
|
|
39855
40166
|
}
|
|
39856
40167
|
|
|
40168
|
+
// ../../packages/adapters/gmail/src/helpers/concurrency.ts
|
|
40169
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
40170
|
+
const results = new Array(items.length);
|
|
40171
|
+
for (let start = 0; start < items.length; start += limit) {
|
|
40172
|
+
const chunk = items.slice(start, start + limit);
|
|
40173
|
+
const chunkResults = await Promise.all(chunk.map((item, i) => fn(item, start + i)));
|
|
40174
|
+
for (let i = 0; i < chunkResults.length; i++) {
|
|
40175
|
+
results[start + i] = chunkResults[i];
|
|
40176
|
+
}
|
|
40177
|
+
}
|
|
40178
|
+
return results;
|
|
40179
|
+
}
|
|
40180
|
+
|
|
39857
40181
|
// ../../packages/adapters/gmail/src/helpers/parse-payload.ts
|
|
39858
40182
|
function decodeBase64Url(encoded) {
|
|
39859
40183
|
if (!encoded) return "";
|
|
@@ -39933,6 +40257,7 @@ function parseGmailHeaders(payload) {
|
|
|
39933
40257
|
}
|
|
39934
40258
|
|
|
39935
40259
|
// ../../packages/adapters/gmail/src/helpers/rfc2822.ts
|
|
40260
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
39936
40261
|
var MIME_OVERRIDES = {
|
|
39937
40262
|
".html": "text/html",
|
|
39938
40263
|
".htm": "text/html",
|
|
@@ -39953,6 +40278,14 @@ function inferMimeType(filename) {
|
|
|
39953
40278
|
const ext = filename.includes(".") ? "." + filename.split(".").pop().toLowerCase() : "";
|
|
39954
40279
|
return MIME_OVERRIDES[ext] ?? "application/octet-stream";
|
|
39955
40280
|
}
|
|
40281
|
+
function assertNoHeaderInjection(fieldName, value) {
|
|
40282
|
+
if (/[\r\n]/.test(value)) {
|
|
40283
|
+
throw new GmailAdapterError(
|
|
40284
|
+
"gmail_validation_error",
|
|
40285
|
+
`'${fieldName}' contains a line break (CR/LF), which is not allowed in an email header.`
|
|
40286
|
+
);
|
|
40287
|
+
}
|
|
40288
|
+
}
|
|
39956
40289
|
function encodeHeader(value) {
|
|
39957
40290
|
const hasNonAscii = [...value].some((ch) => ch.charCodeAt(0) > 127);
|
|
39958
40291
|
if (!hasNonAscii) return value;
|
|
@@ -39960,10 +40293,21 @@ function encodeHeader(value) {
|
|
|
39960
40293
|
return `=?utf-8?B?${encoded}?=`;
|
|
39961
40294
|
}
|
|
39962
40295
|
function makeBoundary() {
|
|
39963
|
-
return `nodalai_${Date.now().toString(36)}_${
|
|
40296
|
+
return `nodalai_${Date.now().toString(36)}_${randomBytes5(12).toString("hex")}`;
|
|
39964
40297
|
}
|
|
39965
40298
|
function buildRfc2822Message(opts) {
|
|
39966
40299
|
const { to, subject, body, from, cc, bcc, replyTo, inReplyTo, references, attachments } = opts;
|
|
40300
|
+
assertNoHeaderInjection("to", to);
|
|
40301
|
+
assertNoHeaderInjection("subject", subject);
|
|
40302
|
+
if (from) assertNoHeaderInjection("from", from);
|
|
40303
|
+
if (cc) assertNoHeaderInjection("cc", cc);
|
|
40304
|
+
if (bcc) assertNoHeaderInjection("bcc", bcc);
|
|
40305
|
+
if (replyTo) assertNoHeaderInjection("replyTo", replyTo);
|
|
40306
|
+
if (inReplyTo) assertNoHeaderInjection("inReplyTo", inReplyTo);
|
|
40307
|
+
if (references) assertNoHeaderInjection("references", references);
|
|
40308
|
+
for (const att of attachments ?? []) {
|
|
40309
|
+
assertNoHeaderInjection("attachment.filename", att.filename);
|
|
40310
|
+
}
|
|
39967
40311
|
const hasAttachments = attachments && attachments.length > 0;
|
|
39968
40312
|
const isHtml = body.includes("<") && body.includes(">") && /<[a-zA-Z][^>]*>/.test(body);
|
|
39969
40313
|
let rawMessage;
|
|
@@ -40039,6 +40383,7 @@ function buildRfc2822Message(opts) {
|
|
|
40039
40383
|
|
|
40040
40384
|
// ../../packages/adapters/gmail/src/tools/messages.ts
|
|
40041
40385
|
var BODY_CHAR_CAP = 1e4;
|
|
40386
|
+
var HYDRATE_CONCURRENCY = 15;
|
|
40042
40387
|
var AttachmentItemSchema = z82.object({
|
|
40043
40388
|
filename: z82.string().describe("File name shown in the email, e.g. 'report.html'."),
|
|
40044
40389
|
content: z82.string().describe("File content as a string (plain text by default, or base64 if encoding is base64)."),
|
|
@@ -40122,39 +40467,39 @@ function createListMessagesTool(gmail) {
|
|
|
40122
40467
|
Math.min(maxResults, 100),
|
|
40123
40468
|
maxResults
|
|
40124
40469
|
);
|
|
40125
|
-
const detailed = await
|
|
40126
|
-
|
|
40127
|
-
|
|
40128
|
-
|
|
40129
|
-
|
|
40130
|
-
|
|
40131
|
-
|
|
40132
|
-
|
|
40133
|
-
|
|
40134
|
-
|
|
40135
|
-
|
|
40136
|
-
|
|
40137
|
-
|
|
40138
|
-
|
|
40139
|
-
|
|
40140
|
-
|
|
40141
|
-
|
|
40142
|
-
|
|
40143
|
-
|
|
40144
|
-
|
|
40145
|
-
|
|
40146
|
-
|
|
40147
|
-
|
|
40148
|
-
|
|
40149
|
-
|
|
40150
|
-
|
|
40151
|
-
|
|
40152
|
-
|
|
40153
|
-
|
|
40154
|
-
|
|
40155
|
-
|
|
40156
|
-
);
|
|
40157
|
-
return { total: detailed.length, messages: detailed };
|
|
40470
|
+
const detailed = await mapWithConcurrency(messages, HYDRATE_CONCURRENCY, async (stub) => {
|
|
40471
|
+
try {
|
|
40472
|
+
const res = await gmail.users.messages.get({
|
|
40473
|
+
userId: "me",
|
|
40474
|
+
id: stub.id,
|
|
40475
|
+
format: "metadata",
|
|
40476
|
+
metadataHeaders: ["Subject", "From", "Date"]
|
|
40477
|
+
});
|
|
40478
|
+
const headers = parseGmailHeaders(res.data.payload ?? void 0);
|
|
40479
|
+
return {
|
|
40480
|
+
messageId: res.data.id ?? stub.id ?? "",
|
|
40481
|
+
threadId: res.data.threadId ?? stub.threadId ?? "",
|
|
40482
|
+
from: headers["from"] ?? "",
|
|
40483
|
+
subject: headers["subject"] ?? "",
|
|
40484
|
+
date: headers["date"] ?? "",
|
|
40485
|
+
snippet: res.data.snippet ?? "",
|
|
40486
|
+
labelIds: res.data.labelIds ?? []
|
|
40487
|
+
};
|
|
40488
|
+
} catch (err) {
|
|
40489
|
+
return {
|
|
40490
|
+
messageId: stub.id ?? "",
|
|
40491
|
+
threadId: stub.threadId ?? "",
|
|
40492
|
+
from: "",
|
|
40493
|
+
subject: "",
|
|
40494
|
+
date: "",
|
|
40495
|
+
snippet: "",
|
|
40496
|
+
labelIds: [],
|
|
40497
|
+
error: err instanceof Error ? err.message : "gmail_get_message_failed"
|
|
40498
|
+
};
|
|
40499
|
+
}
|
|
40500
|
+
});
|
|
40501
|
+
const errors = detailed.filter((m) => m.error !== void 0).length;
|
|
40502
|
+
return { total: detailed.length, errors, messages: detailed };
|
|
40158
40503
|
} catch (err) {
|
|
40159
40504
|
throw mapGmailError(err);
|
|
40160
40505
|
}
|
|
@@ -40424,6 +40769,7 @@ function createDeleteMessageTool(gmail) {
|
|
|
40424
40769
|
// ../../packages/adapters/gmail/src/tools/threads.ts
|
|
40425
40770
|
import { z as z83 } from "zod";
|
|
40426
40771
|
var BODY_CHAR_CAP2 = 5e3;
|
|
40772
|
+
var MAX_MESSAGES_PER_THREAD = 50;
|
|
40427
40773
|
var ListThreadsInput = z83.object({
|
|
40428
40774
|
query: z83.string().optional().describe("Gmail search query. Examples: 'is:unread', 'from:alice@example.com label:work'."),
|
|
40429
40775
|
max_results: z83.number().int().min(1).max(500).optional().describe("Max threads to return (1\u2013500, default 20)."),
|
|
@@ -40476,7 +40822,7 @@ var GetThreadInput = z83.object({
|
|
|
40476
40822
|
function createGetThreadTool(gmail) {
|
|
40477
40823
|
return {
|
|
40478
40824
|
name: "gmail_get_thread",
|
|
40479
|
-
description:
|
|
40825
|
+
description: `Fetch a full Gmail conversation thread with all messages, decoded bodies and headers. Capped at ${MAX_MESSAGES_PER_THREAD} messages (oldest first) \u2014 see the truncated flag.`,
|
|
40480
40826
|
inputSchema: GetThreadInput,
|
|
40481
40827
|
riskLevel: "read",
|
|
40482
40828
|
async execute(input) {
|
|
@@ -40487,7 +40833,9 @@ function createGetThreadTool(gmail) {
|
|
|
40487
40833
|
format: "full"
|
|
40488
40834
|
});
|
|
40489
40835
|
const thread = res.data;
|
|
40490
|
-
const
|
|
40836
|
+
const allMessages = thread.messages ?? [];
|
|
40837
|
+
const truncated = allMessages.length > MAX_MESSAGES_PER_THREAD;
|
|
40838
|
+
const messages = allMessages.slice(0, MAX_MESSAGES_PER_THREAD).map((msg) => {
|
|
40491
40839
|
const payload = msg.payload ?? void 0;
|
|
40492
40840
|
const headers = parseGmailHeaders(payload);
|
|
40493
40841
|
let body = extractTextFromPayload(payload).trim();
|
|
@@ -40505,7 +40853,7 @@ function createGetThreadTool(gmail) {
|
|
|
40505
40853
|
labelIds: msg.labelIds ?? []
|
|
40506
40854
|
};
|
|
40507
40855
|
});
|
|
40508
|
-
return { threadId: thread.id ?? "", messages };
|
|
40856
|
+
return { threadId: thread.id ?? "", messages, truncated };
|
|
40509
40857
|
} catch (err) {
|
|
40510
40858
|
throw mapGmailError(err);
|
|
40511
40859
|
}
|
|
@@ -40715,6 +41063,7 @@ function createDeleteLabelTool(gmail) {
|
|
|
40715
41063
|
|
|
40716
41064
|
// ../../packages/adapters/gmail/src/tools/drafts.ts
|
|
40717
41065
|
import { z as z85 } from "zod";
|
|
41066
|
+
var HYDRATE_CONCURRENCY2 = 15;
|
|
40718
41067
|
var AttachmentItemSchema2 = z85.object({
|
|
40719
41068
|
filename: z85.string().describe("File name shown in the email, e.g. 'report.html'."),
|
|
40720
41069
|
content: z85.string().describe("File content as a string (plain text by default)."),
|
|
@@ -40751,36 +41100,36 @@ function createListDraftsTool(gmail) {
|
|
|
40751
41100
|
Math.min(maxResults, 100),
|
|
40752
41101
|
maxResults
|
|
40753
41102
|
);
|
|
40754
|
-
const drafts = await
|
|
40755
|
-
|
|
40756
|
-
|
|
40757
|
-
|
|
40758
|
-
|
|
40759
|
-
|
|
40760
|
-
|
|
40761
|
-
|
|
40762
|
-
|
|
40763
|
-
|
|
40764
|
-
|
|
40765
|
-
|
|
40766
|
-
|
|
40767
|
-
|
|
40768
|
-
|
|
40769
|
-
|
|
40770
|
-
|
|
40771
|
-
|
|
40772
|
-
|
|
40773
|
-
|
|
40774
|
-
|
|
40775
|
-
|
|
40776
|
-
|
|
40777
|
-
|
|
40778
|
-
|
|
40779
|
-
|
|
40780
|
-
|
|
40781
|
-
|
|
40782
|
-
);
|
|
40783
|
-
return { total: drafts.length, drafts };
|
|
41103
|
+
const drafts = await mapWithConcurrency(draftStubs, HYDRATE_CONCURRENCY2, async (stub) => {
|
|
41104
|
+
const draftId = stub.id ?? "";
|
|
41105
|
+
try {
|
|
41106
|
+
const res = await gmail.users.drafts.get({
|
|
41107
|
+
userId: "me",
|
|
41108
|
+
id: draftId,
|
|
41109
|
+
format: "metadata"
|
|
41110
|
+
});
|
|
41111
|
+
const msg = res.data.message ?? {};
|
|
41112
|
+
const headers = parseGmailHeaders(msg.payload ?? void 0);
|
|
41113
|
+
return {
|
|
41114
|
+
draftId,
|
|
41115
|
+
messageId: msg.id ?? "",
|
|
41116
|
+
to: headers["to"] ?? "",
|
|
41117
|
+
subject: headers["subject"] ?? "",
|
|
41118
|
+
snippet: msg.snippet ?? ""
|
|
41119
|
+
};
|
|
41120
|
+
} catch (err) {
|
|
41121
|
+
return {
|
|
41122
|
+
draftId,
|
|
41123
|
+
messageId: stub.message?.id ?? "",
|
|
41124
|
+
to: "",
|
|
41125
|
+
subject: "",
|
|
41126
|
+
snippet: "",
|
|
41127
|
+
error: err instanceof Error ? err.message : "gmail_get_draft_failed"
|
|
41128
|
+
};
|
|
41129
|
+
}
|
|
41130
|
+
});
|
|
41131
|
+
const errors = drafts.filter((d) => d.error !== void 0).length;
|
|
41132
|
+
return { total: drafts.length, errors, drafts };
|
|
40784
41133
|
} catch (err) {
|
|
40785
41134
|
throw mapGmailError(err);
|
|
40786
41135
|
}
|
|
@@ -41264,13 +41613,14 @@ function createGmailTools(opts) {
|
|
|
41264
41613
|
|
|
41265
41614
|
// ../../packages/adapters/google-calendar/src/client.ts
|
|
41266
41615
|
import { google as google3 } from "googleapis";
|
|
41616
|
+
var DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
41267
41617
|
function createGoogleCalendarClient(getAccessToken) {
|
|
41268
41618
|
const auth2 = new google3.auth.OAuth2();
|
|
41269
41619
|
auth2.refreshHandler = async () => ({
|
|
41270
41620
|
access_token: await getAccessToken(),
|
|
41271
41621
|
expiry_date: Date.now() - 1
|
|
41272
41622
|
});
|
|
41273
|
-
return google3.calendar({ version: "v3", auth: auth2 });
|
|
41623
|
+
return google3.calendar({ version: "v3", auth: auth2, timeout: DEFAULT_TIMEOUT_MS4 });
|
|
41274
41624
|
}
|
|
41275
41625
|
|
|
41276
41626
|
// ../../packages/adapters/google-calendar/src/tools/events.ts
|
|
@@ -41661,13 +42011,14 @@ function createGoogleCalendarTools(opts) {
|
|
|
41661
42011
|
|
|
41662
42012
|
// ../../packages/adapters/google-sheets/src/client.ts
|
|
41663
42013
|
import { google as google4 } from "googleapis";
|
|
42014
|
+
var DEFAULT_TIMEOUT_MS5 = 3e4;
|
|
41664
42015
|
function createSheetsClient(getAccessToken) {
|
|
41665
42016
|
const auth2 = new google4.auth.OAuth2();
|
|
41666
42017
|
auth2.refreshHandler = async () => ({
|
|
41667
42018
|
access_token: await getAccessToken(),
|
|
41668
42019
|
expiry_date: Date.now() - 1
|
|
41669
42020
|
});
|
|
41670
|
-
return google4.sheets({ version: "v4", auth: auth2 });
|
|
42021
|
+
return google4.sheets({ version: "v4", auth: auth2, timeout: DEFAULT_TIMEOUT_MS5 });
|
|
41671
42022
|
}
|
|
41672
42023
|
|
|
41673
42024
|
// ../../packages/adapters/google-sheets/src/tools/values.ts
|
|
@@ -41783,9 +42134,36 @@ function parseRange(range) {
|
|
|
41783
42134
|
}
|
|
41784
42135
|
return { sheetName, cellRange, canonical };
|
|
41785
42136
|
}
|
|
42137
|
+
function estimateRowSpan(cellRange) {
|
|
42138
|
+
const parts = cellRange.split(":");
|
|
42139
|
+
if (parts.length > 2) return null;
|
|
42140
|
+
const rowOf = (part) => {
|
|
42141
|
+
const m = /^\$?[A-Za-z]*\$?(\d+)$/.exec(part);
|
|
42142
|
+
return m?.[1] !== void 0 ? Number(m[1]) : null;
|
|
42143
|
+
};
|
|
42144
|
+
const first = parts[0] ?? "";
|
|
42145
|
+
const second = parts[1];
|
|
42146
|
+
if (second === void 0) {
|
|
42147
|
+
const row = rowOf(first);
|
|
42148
|
+
return row !== null ? 1 : null;
|
|
42149
|
+
}
|
|
42150
|
+
const startRow = rowOf(first);
|
|
42151
|
+
const endRow = rowOf(second);
|
|
42152
|
+
if (startRow === null || endRow === null) return null;
|
|
42153
|
+
return Math.abs(endRow - startRow) + 1;
|
|
42154
|
+
}
|
|
41786
42155
|
|
|
41787
42156
|
// ../../packages/adapters/google-sheets/src/tools/values.ts
|
|
41788
42157
|
var ROW_CAP = 1e4;
|
|
42158
|
+
function rejectIfObviouslyTooLarge(cellRange, fullRange) {
|
|
42159
|
+
const span = estimateRowSpan(cellRange);
|
|
42160
|
+
if (span !== null && span > ROW_CAP) {
|
|
42161
|
+
throw new SheetsAdapterError(
|
|
42162
|
+
"sheets_range_too_large",
|
|
42163
|
+
`Range '${fullRange}' spans ${span} rows which exceeds the ${ROW_CAP}-row cap. Use a smaller range.`
|
|
42164
|
+
);
|
|
42165
|
+
}
|
|
42166
|
+
}
|
|
41789
42167
|
var ReadRangeInput = z90.object({
|
|
41790
42168
|
spreadsheet_id: z90.string().describe("The spreadsheet ID from the URL (between /d/ and /edit)."),
|
|
41791
42169
|
range: z90.string().describe("A1-notation range, e.g. 'Sheet1!A1:D10' or 'A:A' for a full column."),
|
|
@@ -41801,7 +42179,8 @@ function createReadRangeTool(sheets) {
|
|
|
41801
42179
|
riskLevel: "read",
|
|
41802
42180
|
async execute(input) {
|
|
41803
42181
|
try {
|
|
41804
|
-
parseRange(input.range);
|
|
42182
|
+
const parsed = parseRange(input.range);
|
|
42183
|
+
rejectIfObviouslyTooLarge(parsed.cellRange, input.range);
|
|
41805
42184
|
const res = await sheets.spreadsheets.values.get({
|
|
41806
42185
|
spreadsheetId: input.spreadsheet_id,
|
|
41807
42186
|
range: input.range,
|
|
@@ -41831,7 +42210,12 @@ var ReadAllInput = z90.object({
|
|
|
41831
42210
|
function createReadAllTool(sheets) {
|
|
41832
42211
|
return {
|
|
41833
42212
|
name: "sheets_read_all",
|
|
41834
|
-
|
|
42213
|
+
// audit#2026-07-07 F10: unlike sheets_read_range, this always targets the
|
|
42214
|
+
// whole sheet tab — there is no explicit numeric row bound to check
|
|
42215
|
+
// up front (estimateRowSpan has nothing to work with), so the ROW_CAP
|
|
42216
|
+
// check below necessarily runs AFTER the full sheet has been fetched.
|
|
42217
|
+
// Prefer sheets_read_range with an explicit bounded range on large sheets.
|
|
42218
|
+
description: "Read all values from a sheet tab. Returns the full 2D array. Capped at 10,000 rows \u2014 use sheets_read_range for partial reads on large sheets. Note: because this reads a whole tab, the cap is only enforced AFTER the full sheet is fetched \u2014 prefer sheets_read_range with an explicit row-bounded range for very large sheets.",
|
|
41835
42219
|
inputSchema: ReadAllInput,
|
|
41836
42220
|
riskLevel: "read",
|
|
41837
42221
|
async execute(input) {
|
|
@@ -42928,13 +43312,14 @@ function createSheetsTools(opts) {
|
|
|
42928
43312
|
|
|
42929
43313
|
// ../../packages/adapters/google-docs/src/client.ts
|
|
42930
43314
|
import { google as google5 } from "googleapis";
|
|
43315
|
+
var DEFAULT_TIMEOUT_MS6 = 3e4;
|
|
42931
43316
|
function createDocsClient(getAccessToken) {
|
|
42932
43317
|
const auth2 = new google5.auth.OAuth2();
|
|
42933
43318
|
auth2.refreshHandler = async () => ({
|
|
42934
43319
|
access_token: await getAccessToken(),
|
|
42935
43320
|
expiry_date: Date.now() - 1
|
|
42936
43321
|
});
|
|
42937
|
-
return google5.docs({ version: "v1", auth: auth2 });
|
|
43322
|
+
return google5.docs({ version: "v1", auth: auth2, timeout: DEFAULT_TIMEOUT_MS6 });
|
|
42938
43323
|
}
|
|
42939
43324
|
|
|
42940
43325
|
// ../../packages/adapters/google-docs/src/tools/lifecycle.ts
|
|
@@ -43127,6 +43512,7 @@ function buildUpdateTextStyleRequest(startIndex, endIndex, textStyle, fields) {
|
|
|
43127
43512
|
|
|
43128
43513
|
// ../../packages/adapters/google-docs/src/tools/lifecycle.ts
|
|
43129
43514
|
var CHAR_CAP3 = 1e5;
|
|
43515
|
+
var BODY_JSON_CHAR_CAP = 5e5;
|
|
43130
43516
|
var CreateDocInput = z94.object({
|
|
43131
43517
|
title: z94.string().describe("Title for the new document."),
|
|
43132
43518
|
content: z94.string().optional().describe("Optional initial text content to insert into the document body.")
|
|
@@ -43188,6 +43574,13 @@ function createGetDocTool(docs) {
|
|
|
43188
43574
|
`Document body contains ${text23.length} characters which exceeds the ${CHAR_CAP3}-character cap.`
|
|
43189
43575
|
);
|
|
43190
43576
|
}
|
|
43577
|
+
const bodyJsonLength = JSON.stringify(doc.body).length;
|
|
43578
|
+
if (bodyJsonLength > BODY_JSON_CHAR_CAP) {
|
|
43579
|
+
throw new DocsAdapterError(
|
|
43580
|
+
"docs_document_too_large",
|
|
43581
|
+
`Document body JSON is ${bodyJsonLength} characters which exceeds the ${BODY_JSON_CHAR_CAP}-character raw-JSON cap (docs_get returns the full Google Docs JSON structure, which can be much heavier than its flattened text \u2014 use docs_get_text instead).`
|
|
43582
|
+
);
|
|
43583
|
+
}
|
|
43191
43584
|
}
|
|
43192
43585
|
const headers = Object.entries(doc.headers ?? {}).map(([id, h]) => ({
|
|
43193
43586
|
headerId: id,
|
|
@@ -44025,6 +44418,7 @@ function makeFirecrawlSearchTool(client) {
|
|
|
44025
44418
|
|
|
44026
44419
|
// ../../packages/adapters/firecrawl/src/tools/crawl.ts
|
|
44027
44420
|
import { z as z99 } from "zod";
|
|
44421
|
+
var MAX_CRAWL_DOCS_PER_CALL = 50;
|
|
44028
44422
|
var CrawlStartInput = z99.object({
|
|
44029
44423
|
url: z99.string().url().describe("The root URL to crawl."),
|
|
44030
44424
|
limit: z99.number().int().min(1).max(100).optional().describe("Maximum number of pages to crawl (1\u2013100).")
|
|
@@ -44056,18 +44450,20 @@ var CrawlStatusInput = z99.object({
|
|
|
44056
44450
|
function makeFirecrawlCrawlStatusTool(client) {
|
|
44057
44451
|
return {
|
|
44058
44452
|
name: "firecrawl_crawl_status",
|
|
44059
|
-
description:
|
|
44453
|
+
description: `Get the current status and partial results of a Firecrawl crawl job. Poll this until status is "completed", "failed", or "cancelled". Capped at ${MAX_CRAWL_DOCS_PER_CALL} documents per call \u2014 see the dataTruncated flag.`,
|
|
44060
44454
|
inputSchema: CrawlStatusInput,
|
|
44061
44455
|
riskLevel: "read",
|
|
44062
44456
|
async execute(input) {
|
|
44063
44457
|
try {
|
|
44064
44458
|
const job = await client.getCrawlStatus(input.id);
|
|
44459
|
+
const rawData = job.data ?? [];
|
|
44460
|
+
const dataTruncated = rawData.length > MAX_CRAWL_DOCS_PER_CALL;
|
|
44065
44461
|
return {
|
|
44066
44462
|
id: job.id,
|
|
44067
44463
|
status: job.status,
|
|
44068
44464
|
total: job.total,
|
|
44069
44465
|
completed: job.completed,
|
|
44070
|
-
data: (
|
|
44466
|
+
data: rawData.slice(0, MAX_CRAWL_DOCS_PER_CALL).map((doc) => {
|
|
44071
44467
|
const md = capField(doc.markdown);
|
|
44072
44468
|
const html = capField(doc.html);
|
|
44073
44469
|
return {
|
|
@@ -44076,7 +44472,8 @@ function makeFirecrawlCrawlStatusTool(client) {
|
|
|
44076
44472
|
...html.content !== void 0 && { html: html.content },
|
|
44077
44473
|
truncated: md.truncated || html.truncated
|
|
44078
44474
|
};
|
|
44079
|
-
})
|
|
44475
|
+
}),
|
|
44476
|
+
dataTruncated
|
|
44080
44477
|
};
|
|
44081
44478
|
} catch (err) {
|
|
44082
44479
|
throw wrapFirecrawlError(err);
|
|
@@ -44158,6 +44555,8 @@ var WebBrowseInput = z100.object({
|
|
|
44158
44555
|
"For a search query, how many results to return (default 3). Ignored for a single URL."
|
|
44159
44556
|
)
|
|
44160
44557
|
});
|
|
44558
|
+
var WEB_BROWSE_WAIT_SECS = 120;
|
|
44559
|
+
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED", "ABORTED", "TIMED-OUT"]);
|
|
44161
44560
|
function sanitizeScraped(s) {
|
|
44162
44561
|
return Array.from(s).filter((ch) => {
|
|
44163
44562
|
const x = ch.charCodeAt(0);
|
|
@@ -44184,17 +44583,22 @@ function makeApifyWebBrowseTool(client) {
|
|
|
44184
44583
|
riskLevel: "read",
|
|
44185
44584
|
async execute(input) {
|
|
44186
44585
|
try {
|
|
44187
|
-
const run = await client.actor("apify/rag-web-browser").call(
|
|
44188
|
-
|
|
44189
|
-
|
|
44190
|
-
|
|
44191
|
-
|
|
44192
|
-
|
|
44193
|
-
|
|
44194
|
-
|
|
44586
|
+
const run = await client.actor("apify/rag-web-browser").call(
|
|
44587
|
+
{
|
|
44588
|
+
query: input.query,
|
|
44589
|
+
// `text` is the proven, lighter output the legacy skill used. Markdown is
|
|
44590
|
+
// heavier and special-char-dense — an unnecessary deviation that changed
|
|
44591
|
+
// the request/response load profile. Stay on the format that always worked.
|
|
44592
|
+
outputFormats: ["text"],
|
|
44593
|
+
maxResults: input.maxResults ?? 3
|
|
44594
|
+
},
|
|
44595
|
+
// audit#2026-07-07 F1: bound the wait — see WEB_BROWSE_WAIT_SECS above.
|
|
44596
|
+
{ waitSecs: WEB_BROWSE_WAIT_SECS }
|
|
44597
|
+
);
|
|
44195
44598
|
const dataset = await client.dataset(run.defaultDatasetId).listItems({ clean: true });
|
|
44196
44599
|
const results = (dataset.items ?? []).map(normalizeBrowseItem).filter((r) => r.content);
|
|
44197
|
-
|
|
44600
|
+
const timedOut = !TERMINAL_RUN_STATUSES.has(run.status);
|
|
44601
|
+
return { query: input.query, count: results.length, results, timedOut };
|
|
44198
44602
|
} catch (err) {
|
|
44199
44603
|
throw wrapApifyError(err);
|
|
44200
44604
|
}
|
|
@@ -44271,6 +44675,19 @@ function makeApifyGetRunTool(client) {
|
|
|
44271
44675
|
// ../../packages/adapters/apify/src/tools/datasets.ts
|
|
44272
44676
|
init_errors();
|
|
44273
44677
|
import { z as z101 } from "zod";
|
|
44678
|
+
var ITEM_CHAR_CAP = 15e3;
|
|
44679
|
+
function capItem(item) {
|
|
44680
|
+
const json = JSON.stringify(item) ?? "";
|
|
44681
|
+
if (json.length <= ITEM_CHAR_CAP) return { item, truncated: false };
|
|
44682
|
+
return {
|
|
44683
|
+
item: {
|
|
44684
|
+
__truncated: true,
|
|
44685
|
+
preview: json.slice(0, ITEM_CHAR_CAP),
|
|
44686
|
+
originalCharCount: json.length
|
|
44687
|
+
},
|
|
44688
|
+
truncated: true
|
|
44689
|
+
};
|
|
44690
|
+
}
|
|
44274
44691
|
var ListDatasetsInput = z101.object({
|
|
44275
44692
|
limit: z101.number().int().min(1).max(1e3).optional().default(100).describe("Maximum number of datasets to return (default 100, max 1000)."),
|
|
44276
44693
|
offset: z101.number().int().min(0).optional().default(0).describe("Number of datasets to skip (for pagination). Default 0.")
|
|
@@ -44315,7 +44732,7 @@ var GetDatasetItemsInput = z101.object({
|
|
|
44315
44732
|
function makeApifyGetDatasetItemsTool(client) {
|
|
44316
44733
|
return {
|
|
44317
44734
|
name: "apify_get_dataset_items",
|
|
44318
|
-
description:
|
|
44735
|
+
description: `Retrieve items from an Apify dataset. Use the datasetId returned by apify_run_actor or from apify_list_datasets. Supports limit/offset pagination (max 1000 items per call). Each item is capped at ${ITEM_CHAR_CAP} chars of JSON \u2014 see the truncated flag.`,
|
|
44319
44736
|
inputSchema: GetDatasetItemsInput,
|
|
44320
44737
|
riskLevel: "read",
|
|
44321
44738
|
async execute(input) {
|
|
@@ -44324,11 +44741,13 @@ function makeApifyGetDatasetItemsTool(client) {
|
|
|
44324
44741
|
limit: input.limit ?? 100,
|
|
44325
44742
|
offset: input.offset ?? 0
|
|
44326
44743
|
});
|
|
44744
|
+
const capped = result.items.map(capItem);
|
|
44327
44745
|
return {
|
|
44328
|
-
items:
|
|
44746
|
+
items: capped.map((c) => c.item),
|
|
44329
44747
|
total: result.total,
|
|
44330
44748
|
count: result.count,
|
|
44331
|
-
offset: result.offset
|
|
44749
|
+
offset: result.offset,
|
|
44750
|
+
truncated: capped.some((c) => c.truncated)
|
|
44332
44751
|
};
|
|
44333
44752
|
} catch (err) {
|
|
44334
44753
|
throw wrapApifyError(err);
|
|
@@ -44668,7 +45087,7 @@ function wrapPoyoError(err) {
|
|
|
44668
45087
|
|
|
44669
45088
|
// ../../packages/adapters/poyo/src/client.ts
|
|
44670
45089
|
var DEFAULT_BASE_URL = "https://api.poyo.ai";
|
|
44671
|
-
var
|
|
45090
|
+
var DEFAULT_TIMEOUT_MS7 = 3e4;
|
|
44672
45091
|
async function fetchWithTimeout(doFetch, url2, init, timeoutMs) {
|
|
44673
45092
|
try {
|
|
44674
45093
|
return await doFetch(url2, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -44685,7 +45104,7 @@ async function fetchWithTimeout(doFetch, url2, init, timeoutMs) {
|
|
|
44685
45104
|
function createPoyoClient(accessToken, options = {}) {
|
|
44686
45105
|
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
44687
45106
|
const doFetch = options.fetchImpl ?? fetch;
|
|
44688
|
-
const timeoutMs = options.timeoutMs ??
|
|
45107
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS7;
|
|
44689
45108
|
const authHeader = { Authorization: `Bearer ${accessToken}` };
|
|
44690
45109
|
return {
|
|
44691
45110
|
async submit(model, input) {
|
|
@@ -44756,7 +45175,7 @@ var GenerateImageInput = z103.object({
|
|
|
44756
45175
|
),
|
|
44757
45176
|
size: z103.string().min(1).optional().describe('Aspect ratio of the image, e.g. "1:1", "16:9", "9:16", "4:3". Default "1:1".')
|
|
44758
45177
|
});
|
|
44759
|
-
var sleep4 = (ms) => new Promise((
|
|
45178
|
+
var sleep4 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
44760
45179
|
function makePoyoGenerateImageTool(client, poll = DEFAULT_POLL) {
|
|
44761
45180
|
return {
|
|
44762
45181
|
name: "poyo_generate_image",
|
|
@@ -44907,6 +45326,10 @@ var ADAPTER_REGISTRY = {
|
|
|
44907
45326
|
}
|
|
44908
45327
|
};
|
|
44909
45328
|
|
|
45329
|
+
// ../../packages/adapters/mcp/src/client.ts
|
|
45330
|
+
import { lookup as dnsLookupCb } from "node:dns";
|
|
45331
|
+
import { promisify } from "node:util";
|
|
45332
|
+
|
|
44910
45333
|
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
44911
45334
|
import * as z3rt from "zod/v3";
|
|
44912
45335
|
import * as z4mini from "zod/v4-mini";
|
|
@@ -47034,7 +47457,7 @@ var Protocol = class {
|
|
|
47034
47457
|
return;
|
|
47035
47458
|
}
|
|
47036
47459
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
47037
|
-
await new Promise((
|
|
47460
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
47038
47461
|
options?.signal?.throwIfAborted();
|
|
47039
47462
|
}
|
|
47040
47463
|
} catch (error) {
|
|
@@ -47051,7 +47474,7 @@ var Protocol = class {
|
|
|
47051
47474
|
*/
|
|
47052
47475
|
request(request, resultSchema, options) {
|
|
47053
47476
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
47054
|
-
return new Promise((
|
|
47477
|
+
return new Promise((resolve3, reject) => {
|
|
47055
47478
|
const earlyReject = (error) => {
|
|
47056
47479
|
reject(error);
|
|
47057
47480
|
};
|
|
@@ -47129,7 +47552,7 @@ var Protocol = class {
|
|
|
47129
47552
|
if (!parseResult.success) {
|
|
47130
47553
|
reject(parseResult.error);
|
|
47131
47554
|
} else {
|
|
47132
|
-
|
|
47555
|
+
resolve3(parseResult.data);
|
|
47133
47556
|
}
|
|
47134
47557
|
} catch (error) {
|
|
47135
47558
|
reject(error);
|
|
@@ -47390,12 +47813,12 @@ var Protocol = class {
|
|
|
47390
47813
|
}
|
|
47391
47814
|
} catch {
|
|
47392
47815
|
}
|
|
47393
|
-
return new Promise((
|
|
47816
|
+
return new Promise((resolve3, reject) => {
|
|
47394
47817
|
if (signal.aborted) {
|
|
47395
47818
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
47396
47819
|
return;
|
|
47397
47820
|
}
|
|
47398
|
-
const timeoutId = setTimeout(
|
|
47821
|
+
const timeoutId = setTimeout(resolve3, interval);
|
|
47399
47822
|
signal.addEventListener("abort", () => {
|
|
47400
47823
|
clearTimeout(timeoutId);
|
|
47401
47824
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -48305,8 +48728,8 @@ async function random(size) {
|
|
|
48305
48728
|
const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
|
|
48306
48729
|
let result = "";
|
|
48307
48730
|
while (result.length < size) {
|
|
48308
|
-
const
|
|
48309
|
-
for (const randomByte of
|
|
48731
|
+
const randomBytes6 = await getRandomValues(size - result.length);
|
|
48732
|
+
for (const randomByte of randomBytes6) {
|
|
48310
48733
|
if (randomByte < evenDistCutoff) {
|
|
48311
48734
|
result += mask[randomByte % mask.length];
|
|
48312
48735
|
}
|
|
@@ -49802,7 +50225,7 @@ var StdioClientTransport = class {
|
|
|
49802
50225
|
if (this._process) {
|
|
49803
50226
|
throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
49804
50227
|
}
|
|
49805
|
-
return new Promise((
|
|
50228
|
+
return new Promise((resolve3, reject) => {
|
|
49806
50229
|
this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
|
|
49807
50230
|
// merge default env with server env because mcp server needs some env vars
|
|
49808
50231
|
env: {
|
|
@@ -49819,7 +50242,7 @@ var StdioClientTransport = class {
|
|
|
49819
50242
|
this.onerror?.(error);
|
|
49820
50243
|
});
|
|
49821
50244
|
this._process.on("spawn", () => {
|
|
49822
|
-
|
|
50245
|
+
resolve3();
|
|
49823
50246
|
});
|
|
49824
50247
|
this._process.on("close", (_code) => {
|
|
49825
50248
|
this._process = void 0;
|
|
@@ -49878,22 +50301,22 @@ var StdioClientTransport = class {
|
|
|
49878
50301
|
if (this._process) {
|
|
49879
50302
|
const processToClose = this._process;
|
|
49880
50303
|
this._process = void 0;
|
|
49881
|
-
const closePromise = new Promise((
|
|
50304
|
+
const closePromise = new Promise((resolve3) => {
|
|
49882
50305
|
processToClose.once("close", () => {
|
|
49883
|
-
|
|
50306
|
+
resolve3();
|
|
49884
50307
|
});
|
|
49885
50308
|
});
|
|
49886
50309
|
try {
|
|
49887
50310
|
processToClose.stdin?.end();
|
|
49888
50311
|
} catch {
|
|
49889
50312
|
}
|
|
49890
|
-
await Promise.race([closePromise, new Promise((
|
|
50313
|
+
await Promise.race([closePromise, new Promise((resolve3) => setTimeout(resolve3, 2e3).unref())]);
|
|
49891
50314
|
if (processToClose.exitCode === null) {
|
|
49892
50315
|
try {
|
|
49893
50316
|
processToClose.kill("SIGTERM");
|
|
49894
50317
|
} catch {
|
|
49895
50318
|
}
|
|
49896
|
-
await Promise.race([closePromise, new Promise((
|
|
50319
|
+
await Promise.race([closePromise, new Promise((resolve3) => setTimeout(resolve3, 2e3).unref())]);
|
|
49897
50320
|
}
|
|
49898
50321
|
if (processToClose.exitCode === null) {
|
|
49899
50322
|
try {
|
|
@@ -49905,21 +50328,54 @@ var StdioClientTransport = class {
|
|
|
49905
50328
|
this._readBuffer.clear();
|
|
49906
50329
|
}
|
|
49907
50330
|
send(message) {
|
|
49908
|
-
return new Promise((
|
|
50331
|
+
return new Promise((resolve3) => {
|
|
49909
50332
|
if (!this._process?.stdin) {
|
|
49910
50333
|
throw new Error("Not connected");
|
|
49911
50334
|
}
|
|
49912
50335
|
const json = serializeMessage(message);
|
|
49913
50336
|
if (this._process.stdin.write(json)) {
|
|
49914
|
-
|
|
50337
|
+
resolve3();
|
|
49915
50338
|
} else {
|
|
49916
|
-
this._process.stdin.once("drain",
|
|
50339
|
+
this._process.stdin.once("drain", resolve3);
|
|
49917
50340
|
}
|
|
49918
50341
|
});
|
|
49919
50342
|
}
|
|
49920
50343
|
};
|
|
49921
50344
|
|
|
49922
50345
|
// ../../packages/adapters/mcp/src/client.ts
|
|
50346
|
+
var dnsLookup = promisify(dnsLookupCb);
|
|
50347
|
+
var BLOCKED_MCP_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
50348
|
+
function isBlockedMcpAddress(addr) {
|
|
50349
|
+
const a = addr.toLowerCase();
|
|
50350
|
+
if (/^169\.254\./.test(a)) return true;
|
|
50351
|
+
if (a === "100.100.100.100") return true;
|
|
50352
|
+
if (a.startsWith("fe80:")) return true;
|
|
50353
|
+
if (a === "fd00:ec2::254") return true;
|
|
50354
|
+
return false;
|
|
50355
|
+
}
|
|
50356
|
+
function isIpLiteral(host) {
|
|
50357
|
+
return /^\d{1,3}(\.\d{1,3}){3}$/.test(host) || host.includes(":");
|
|
50358
|
+
}
|
|
50359
|
+
async function assertMcpHttpUrlSafe(url2) {
|
|
50360
|
+
const hostname = url2.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
50361
|
+
if (BLOCKED_MCP_HOSTNAMES.has(hostname) || isBlockedMcpAddress(hostname)) {
|
|
50362
|
+
throw new Error(`MCP URL blocked: "${hostname}" is a link-local / cloud-metadata endpoint.`);
|
|
50363
|
+
}
|
|
50364
|
+
if (isIpLiteral(hostname)) return;
|
|
50365
|
+
let resolved;
|
|
50366
|
+
try {
|
|
50367
|
+
resolved = await dnsLookup(hostname, { all: true });
|
|
50368
|
+
} catch {
|
|
50369
|
+
return;
|
|
50370
|
+
}
|
|
50371
|
+
for (const { address } of resolved) {
|
|
50372
|
+
if (isBlockedMcpAddress(address)) {
|
|
50373
|
+
throw new Error(
|
|
50374
|
+
`MCP URL blocked: "${hostname}" resolves to a link-local / cloud-metadata address.`
|
|
50375
|
+
);
|
|
50376
|
+
}
|
|
50377
|
+
}
|
|
50378
|
+
}
|
|
49923
50379
|
var DEFAULT_CONNECT_TIMEOUT_MS = 12e4;
|
|
49924
50380
|
function resolveConnectTimeoutMs() {
|
|
49925
50381
|
const raw = process.env["MCP_CONNECT_TIMEOUT_MS"];
|
|
@@ -49974,6 +50430,7 @@ async function connectMcp(opts) {
|
|
|
49974
50430
|
const connectTimeoutMs = resolveConnectTimeoutMs();
|
|
49975
50431
|
if (opts.transport === "http") {
|
|
49976
50432
|
const target = buildMcpRequest(opts);
|
|
50433
|
+
await assertMcpHttpUrlSafe(target.url);
|
|
49977
50434
|
const transport = new StreamableHTTPClientTransport(target.url, {
|
|
49978
50435
|
requestInit: { headers: target.headers }
|
|
49979
50436
|
});
|
|
@@ -49984,14 +50441,14 @@ async function connectMcp(opts) {
|
|
|
49984
50441
|
() => safeCloseTransport(transport)
|
|
49985
50442
|
);
|
|
49986
50443
|
} else {
|
|
49987
|
-
const
|
|
49988
|
-
for (const [k, v] of Object.entries(process.env)) {
|
|
49989
|
-
if (typeof v === "string")
|
|
50444
|
+
const scrubbedEnv = {};
|
|
50445
|
+
for (const [k, v] of Object.entries(buildChildEnv(process.env))) {
|
|
50446
|
+
if (typeof v === "string") scrubbedEnv[k] = v;
|
|
49990
50447
|
}
|
|
49991
50448
|
const transport = new StdioClientTransport({
|
|
49992
50449
|
command: opts.command,
|
|
49993
50450
|
args: opts.args,
|
|
49994
|
-
env: { ...
|
|
50451
|
+
env: { ...scrubbedEnv, ...opts.env }
|
|
49995
50452
|
});
|
|
49996
50453
|
await withTimeout(
|
|
49997
50454
|
client.connect(transport),
|
|
@@ -50082,6 +50539,22 @@ function describe(zodType, schema) {
|
|
|
50082
50539
|
|
|
50083
50540
|
// ../../packages/adapters/mcp/src/tools.ts
|
|
50084
50541
|
var MCP_CALL_TIMEOUT_MS = Number(process.env.MCP_CALL_TIMEOUT_MS) || 18e4;
|
|
50542
|
+
var MCP_RESULT_CHAR_CAP = Number(process.env.MCP_RESULT_CHAR_CAP) || 5e4;
|
|
50543
|
+
function capMcpResult(value) {
|
|
50544
|
+
if (typeof value === "string") {
|
|
50545
|
+
if (value.length <= MCP_RESULT_CHAR_CAP) return value;
|
|
50546
|
+
return value.slice(0, MCP_RESULT_CHAR_CAP) + `
|
|
50547
|
+
|
|
50548
|
+
[...truncated at ${MCP_RESULT_CHAR_CAP} chars \u2014 MCP tool result was larger]`;
|
|
50549
|
+
}
|
|
50550
|
+
const serialized = JSON.stringify(value) ?? "";
|
|
50551
|
+
if (serialized.length <= MCP_RESULT_CHAR_CAP) return value;
|
|
50552
|
+
return {
|
|
50553
|
+
truncated: true,
|
|
50554
|
+
originalLength: serialized.length,
|
|
50555
|
+
preview: serialized.slice(0, MCP_RESULT_CHAR_CAP)
|
|
50556
|
+
};
|
|
50557
|
+
}
|
|
50085
50558
|
function slugToPrefix2(slug) {
|
|
50086
50559
|
return slug.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
|
|
50087
50560
|
}
|
|
@@ -50096,7 +50569,29 @@ function extractText(content) {
|
|
|
50096
50569
|
(c) => typeof c === "object" && c !== null && c.type === "text" && typeof c.text === "string"
|
|
50097
50570
|
).map((c) => c.text).join("\n");
|
|
50098
50571
|
}
|
|
50099
|
-
function
|
|
50572
|
+
async function callMcpTool(client, originalName, input) {
|
|
50573
|
+
const result = await client.callTool(
|
|
50574
|
+
{ name: originalName, arguments: input },
|
|
50575
|
+
// Default result schema (CallToolResultSchema).
|
|
50576
|
+
void 0,
|
|
50577
|
+
// The MCP SDK's default per-request timeout is 60s — too short for heavy
|
|
50578
|
+
// tools (a Blender/KeyShot render, a long scrape). Raise it and reset the
|
|
50579
|
+
// clock whenever the server reports progress, so progress-streaming
|
|
50580
|
+
// servers can run even longer. Overridable via MCP_CALL_TIMEOUT_MS.
|
|
50581
|
+
{ timeout: MCP_CALL_TIMEOUT_MS, resetTimeoutOnProgress: true }
|
|
50582
|
+
);
|
|
50583
|
+
if (result.isError === true) {
|
|
50584
|
+
const detail = extractText(result.content);
|
|
50585
|
+
throw new Error(`MCP tool ${originalName} failed: ${detail || "unknown error"}`);
|
|
50586
|
+
}
|
|
50587
|
+
if (result.structuredContent != null) return capMcpResult(result.structuredContent);
|
|
50588
|
+
const content = result.content ?? [];
|
|
50589
|
+
if (Array.isArray(content) && content.length > 0 && content.every((c) => c.type === "text")) {
|
|
50590
|
+
return capMcpResult(extractText(content));
|
|
50591
|
+
}
|
|
50592
|
+
return capMcpResult(content);
|
|
50593
|
+
}
|
|
50594
|
+
function buildMcpToolDefinition(mcpTool, slug, getClient) {
|
|
50100
50595
|
const originalName = mcpTool.name;
|
|
50101
50596
|
return {
|
|
50102
50597
|
name: `${slugToPrefix2(slug)}__${originalName}`,
|
|
@@ -50104,38 +50599,66 @@ function mcpToolToToolDefinition(client, mcpTool, slug) {
|
|
|
50104
50599
|
inputSchema: jsonSchemaToZod(mcpTool.inputSchema),
|
|
50105
50600
|
riskLevel: riskFromAnnotations(mcpTool.annotations),
|
|
50106
50601
|
async execute(input) {
|
|
50107
|
-
const
|
|
50108
|
-
|
|
50109
|
-
name: originalName,
|
|
50110
|
-
arguments: input ?? {}
|
|
50111
|
-
},
|
|
50112
|
-
// Default result schema (CallToolResultSchema).
|
|
50113
|
-
void 0,
|
|
50114
|
-
// The MCP SDK's default per-request timeout is 60s — too short for heavy
|
|
50115
|
-
// tools (a Blender/KeyShot render, a long scrape). Raise it and reset the
|
|
50116
|
-
// clock whenever the server reports progress, so progress-streaming
|
|
50117
|
-
// servers can run even longer. Overridable via MCP_CALL_TIMEOUT_MS.
|
|
50118
|
-
{ timeout: MCP_CALL_TIMEOUT_MS, resetTimeoutOnProgress: true }
|
|
50119
|
-
);
|
|
50120
|
-
if (result.isError === true) {
|
|
50121
|
-
const detail = extractText(result.content);
|
|
50122
|
-
throw new Error(`MCP tool ${originalName} failed: ${detail || "unknown error"}`);
|
|
50123
|
-
}
|
|
50124
|
-
if (result.structuredContent != null) return result.structuredContent;
|
|
50125
|
-
const content = result.content ?? [];
|
|
50126
|
-
if (Array.isArray(content) && content.length > 0 && content.every((c) => c.type === "text")) {
|
|
50127
|
-
return extractText(content);
|
|
50128
|
-
}
|
|
50129
|
-
return content;
|
|
50602
|
+
const client = await getClient();
|
|
50603
|
+
return callMcpTool(client, originalName, input ?? {});
|
|
50130
50604
|
}
|
|
50131
50605
|
};
|
|
50132
50606
|
}
|
|
50607
|
+
function mcpToolToToolDefinition(client, mcpTool, slug) {
|
|
50608
|
+
return buildMcpToolDefinition(mcpTool, slug, () => Promise.resolve(client));
|
|
50609
|
+
}
|
|
50610
|
+
function mcpToolToLazyToolDefinition(getClient, mcpTool, slug) {
|
|
50611
|
+
return buildMcpToolDefinition(mcpTool, slug, getClient);
|
|
50612
|
+
}
|
|
50133
50613
|
|
|
50134
50614
|
// ../../packages/adapters/mcp/src/index.ts
|
|
50135
50615
|
async function createMcpTools(opts) {
|
|
50136
50616
|
const conn = await connectMcp(opts);
|
|
50137
50617
|
const tools = conn.tools.map((t) => mcpToolToToolDefinition(conn.client, t, opts.slug));
|
|
50138
|
-
return { tools, close: conn.close };
|
|
50618
|
+
return { tools, descriptors: conn.tools, close: conn.close };
|
|
50619
|
+
}
|
|
50620
|
+
function createLazyMcpTools(opts, cachedDescriptors, lazyOpts = {}) {
|
|
50621
|
+
let connectPromise = null;
|
|
50622
|
+
function ensureConnected() {
|
|
50623
|
+
if (!connectPromise) {
|
|
50624
|
+
connectPromise = connectMcp(opts).then((conn) => {
|
|
50625
|
+
if (lazyOpts.onConnected) {
|
|
50626
|
+
Promise.resolve(lazyOpts.onConnected(conn.tools)).catch((err) => {
|
|
50627
|
+
console.error(
|
|
50628
|
+
`[adapter-mcp] onConnected cache-refresh hook failed for '${opts.slug}':`,
|
|
50629
|
+
err instanceof Error ? err.message : err
|
|
50630
|
+
);
|
|
50631
|
+
});
|
|
50632
|
+
}
|
|
50633
|
+
return conn;
|
|
50634
|
+
}).catch((err) => {
|
|
50635
|
+
connectPromise = null;
|
|
50636
|
+
throw err;
|
|
50637
|
+
});
|
|
50638
|
+
}
|
|
50639
|
+
return connectPromise.then((conn) => conn.client);
|
|
50640
|
+
}
|
|
50641
|
+
const tools = cachedDescriptors.map(
|
|
50642
|
+
(d) => mcpToolToLazyToolDefinition(ensureConnected, d, opts.slug)
|
|
50643
|
+
);
|
|
50644
|
+
return {
|
|
50645
|
+
tools,
|
|
50646
|
+
descriptors: cachedDescriptors,
|
|
50647
|
+
close: async () => {
|
|
50648
|
+
const pending = connectPromise;
|
|
50649
|
+
if (!pending) return;
|
|
50650
|
+
const conn = await pending.catch(() => null);
|
|
50651
|
+
if (conn) await conn.close();
|
|
50652
|
+
}
|
|
50653
|
+
};
|
|
50654
|
+
}
|
|
50655
|
+
|
|
50656
|
+
// src/job/mcp-tool-cache.ts
|
|
50657
|
+
function isUsableMcpToolCache(availableTools) {
|
|
50658
|
+
if (!Array.isArray(availableTools) || availableTools.length === 0) return false;
|
|
50659
|
+
return availableTools.every(
|
|
50660
|
+
(t) => typeof t === "object" && t !== null && typeof t.name === "string" && t.inputSchema !== void 0
|
|
50661
|
+
);
|
|
50139
50662
|
}
|
|
50140
50663
|
|
|
50141
50664
|
// src/job/resolve-llm.ts
|
|
@@ -50174,7 +50697,10 @@ async function resolveAgentLlmClient(db, agent, onSkip) {
|
|
|
50174
50697
|
provider: row.provider,
|
|
50175
50698
|
model,
|
|
50176
50699
|
apiKey: plaintextKey || void 0,
|
|
50177
|
-
baseURL: row.baseUrl ?? void 0
|
|
50700
|
+
baseURL: row.baseUrl ?? void 0,
|
|
50701
|
+
// É-3: per-model context window for a custom/local endpoint the catalog
|
|
50702
|
+
// can't know — used as a fallback in modelContextWindow (catalog wins).
|
|
50703
|
+
contextWindow: row.contextWindow ?? void 0
|
|
50178
50704
|
});
|
|
50179
50705
|
}
|
|
50180
50706
|
if (configs.length === 0) return { ok: false, reason: "agent_no_llm_configured" };
|
|
@@ -50563,6 +51089,83 @@ async function generateAssignTools(parentAgentId, db) {
|
|
|
50563
51089
|
return tools;
|
|
50564
51090
|
}
|
|
50565
51091
|
|
|
51092
|
+
// ../../packages/orchestration/src/router/tool-availability.ts
|
|
51093
|
+
init_src();
|
|
51094
|
+
var KNOWN_FIXED_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
51095
|
+
...ALWAYS_ON_TOOLS,
|
|
51096
|
+
...DELIVERY_TOOL_NAMES,
|
|
51097
|
+
...META_TOOL_NAMES,
|
|
51098
|
+
"run_skill_script",
|
|
51099
|
+
"skill_file_write"
|
|
51100
|
+
]);
|
|
51101
|
+
var KNOWN_CONNECTOR_TOOL_NAMES = new Set(
|
|
51102
|
+
Object.values(ADAPTER_REGISTRY).flatMap((entry) => entry.operations.map((o) => o.slug))
|
|
51103
|
+
);
|
|
51104
|
+
var KNOWN_TOOL_NAME_UNIVERSE = /* @__PURE__ */ new Set([
|
|
51105
|
+
...KNOWN_FIXED_TOOL_NAMES,
|
|
51106
|
+
...KNOWN_CONNECTOR_TOOL_NAMES
|
|
51107
|
+
]);
|
|
51108
|
+
async function computeAgentToolNames(agentId, entityId, db, opts) {
|
|
51109
|
+
const names = new Set(ALWAYS_ON_TOOLS);
|
|
51110
|
+
const [agentRow] = await db.select({ telegramBotToken: agents.telegramBotToken }).from(agents).where(eq5(agents.id, agentId)).limit(1);
|
|
51111
|
+
if (opts.hasDeliveryRecipient) {
|
|
51112
|
+
let deliveryToken = agentRow?.telegramBotToken ?? null;
|
|
51113
|
+
if (!deliveryToken) {
|
|
51114
|
+
const [rootRow] = await db.select({ telegramBotToken: agents.telegramBotToken }).from(entities).innerJoin(agents, eq5(agents.id, entities.rootAgentId)).where(eq5(entities.id, entityId)).limit(1);
|
|
51115
|
+
deliveryToken = rootRow?.telegramBotToken ?? null;
|
|
51116
|
+
}
|
|
51117
|
+
if (deliveryToken) {
|
|
51118
|
+
for (const n of DELIVERY_TOOL_NAMES) names.add(n);
|
|
51119
|
+
}
|
|
51120
|
+
}
|
|
51121
|
+
const skillRows = await db.select({
|
|
51122
|
+
requiredBuiltins: agentSkills.requiredBuiltins,
|
|
51123
|
+
scriptsAuthorized: agentSkillAssignments.scriptsAuthorized,
|
|
51124
|
+
filesWritable: agentSkillAssignments.filesWritable
|
|
51125
|
+
}).from(agentSkillAssignments).innerJoin(agentSkills, eq5(agentSkills.id, agentSkillAssignments.skillId)).where(eq5(agentSkillAssignments.agentId, agentId));
|
|
51126
|
+
for (const r of skillRows) {
|
|
51127
|
+
for (const n of r.requiredBuiltins ?? []) names.add(n);
|
|
51128
|
+
if (r.scriptsAuthorized) names.add("run_skill_script");
|
|
51129
|
+
if (r.filesWritable) names.add("skill_file_write");
|
|
51130
|
+
}
|
|
51131
|
+
const connectorRows = await db.select({
|
|
51132
|
+
slug: connectors.slug,
|
|
51133
|
+
enabledOperations: agentConnectorAssignments.enabledOperations
|
|
51134
|
+
}).from(agentConnectorAssignments).innerJoin(connectors, eq5(connectors.id, agentConnectorAssignments.connectorId)).where(eq5(agentConnectorAssignments.agentId, agentId));
|
|
51135
|
+
for (const r of connectorRows) {
|
|
51136
|
+
const entry = ADAPTER_REGISTRY[r.slug];
|
|
51137
|
+
if (!entry) continue;
|
|
51138
|
+
const allToolNames = entry.operations.map((o) => o.slug);
|
|
51139
|
+
const kept = r.enabledOperations === null ? allToolNames : allToolNames.filter((n) => r.enabledOperations.includes(n));
|
|
51140
|
+
for (const n of kept) names.add(n);
|
|
51141
|
+
}
|
|
51142
|
+
const mcpRows = await db.select({
|
|
51143
|
+
serverSlug: mcpServers.slug,
|
|
51144
|
+
enabledTools: agentMcpServers.enabledTools,
|
|
51145
|
+
availableTools: mcpServers.availableTools
|
|
51146
|
+
}).from(agentMcpServers).innerJoin(mcpServers, eq5(mcpServers.id, agentMcpServers.mcpServerId)).where(eq5(agentMcpServers.agentId, agentId));
|
|
51147
|
+
for (const r of mcpRows) {
|
|
51148
|
+
const prefix = r.serverSlug.replace(/-/g, "_");
|
|
51149
|
+
const available = Array.isArray(r.availableTools) ? r.availableTools.map((t) => t && typeof t.name === "string" ? t.name : null).filter((n) => n !== null) : [];
|
|
51150
|
+
const enabled = Array.isArray(r.enabledTools) ? new Set(r.enabledTools.filter((n) => typeof n === "string")) : null;
|
|
51151
|
+
const kept = enabled === null ? available : available.filter((n) => enabled.has(n));
|
|
51152
|
+
for (const n of kept) names.add(`${prefix}__${n}`);
|
|
51153
|
+
}
|
|
51154
|
+
const [entityRow] = await db.select({ rootAgentId: entities.rootAgentId, rootGrants: entities.rootGrants }).from(entities).where(eq5(entities.id, entityId)).limit(1);
|
|
51155
|
+
if (entityRow?.rootAgentId === agentId) {
|
|
51156
|
+
for (const n of enabledMetaTools(parseRootGrants(entityRow.rootGrants))) names.add(n);
|
|
51157
|
+
}
|
|
51158
|
+
return names;
|
|
51159
|
+
}
|
|
51160
|
+
function findUnavailableToolMentions(briefText, availableToolNames) {
|
|
51161
|
+
const words = briefText.match(/\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b/g) ?? [];
|
|
51162
|
+
const missing = /* @__PURE__ */ new Set();
|
|
51163
|
+
for (const w of words) {
|
|
51164
|
+
if (KNOWN_TOOL_NAME_UNIVERSE.has(w) && !availableToolNames.has(w)) missing.add(w);
|
|
51165
|
+
}
|
|
51166
|
+
return [...missing];
|
|
51167
|
+
}
|
|
51168
|
+
|
|
50566
51169
|
// ../../packages/orchestration/src/router/delegate.ts
|
|
50567
51170
|
var TERMINAL_JOB_STATUSES = ["completed", "failed", "cancelled"];
|
|
50568
51171
|
async function handleDelegation(parentJob, childSlug, toolUseId, taskInput, sideToolResults, db) {
|
|
@@ -50594,6 +51197,9 @@ ${taskInput.data}` : taskInput.task;
|
|
|
50594
51197
|
status: "pending",
|
|
50595
51198
|
parentJobId: parentJob.id,
|
|
50596
51199
|
delegationDepth: childDepth,
|
|
51200
|
+
// Jobs page grouping (migration 0059): a delegated child is the SAME
|
|
51201
|
+
// conversation as its parent, not a new one.
|
|
51202
|
+
conversationId: parentJob.conversationId ?? void 0,
|
|
50597
51203
|
messages: [{ role: "user", content: childContent }]
|
|
50598
51204
|
}).returning();
|
|
50599
51205
|
if (!childJob) {
|
|
@@ -50907,6 +51513,20 @@ function generateTaskTools(orchestratorAgentId, db) {
|
|
|
50907
51513
|
const agentRows = await db.select({ id: agents.id }).from(agents).where(and4(eq5(agents.slug, input.assigned_to), eq5(agents.entityId, ctx.entityId))).limit(1);
|
|
50908
51514
|
assignedAgentId = agentRows[0]?.id ?? null;
|
|
50909
51515
|
}
|
|
51516
|
+
let warning;
|
|
51517
|
+
if (assignedAgentId) {
|
|
51518
|
+
const briefText = `${input.title} ${input.description ?? ""}`;
|
|
51519
|
+
const availableToolNames = await computeAgentToolNames(
|
|
51520
|
+
assignedAgentId,
|
|
51521
|
+
ctx.entityId,
|
|
51522
|
+
db,
|
|
51523
|
+
{ hasDeliveryRecipient: false }
|
|
51524
|
+
);
|
|
51525
|
+
const missing = findUnavailableToolMentions(briefText, availableToolNames);
|
|
51526
|
+
if (missing.length > 0) {
|
|
51527
|
+
warning = `warning: le brief mentionne l'outil '${missing[0]}' que l'agent cible '${input.assigned_to}' n'aura pas \u2014 prescris le r\xE9sultat attendu plut\xF4t que l'outil, ou retire la mention`;
|
|
51528
|
+
}
|
|
51529
|
+
}
|
|
50910
51530
|
const dependsOn = input.depends_on ?? [];
|
|
50911
51531
|
const newTaskId = randomUUID2();
|
|
50912
51532
|
await validateDependencies(newTaskId, dependsOn, ctx.entityId, db);
|
|
@@ -50927,7 +51547,7 @@ function generateTaskTools(orchestratorAgentId, db) {
|
|
|
50927
51547
|
if (!task) {
|
|
50928
51548
|
throw new Error("task_board_error: failed to insert task");
|
|
50929
51549
|
}
|
|
50930
|
-
return { taskId: task.id, title: task.title };
|
|
51550
|
+
return { taskId: task.id, title: task.title, ...warning ? { warning } : {} };
|
|
50931
51551
|
}
|
|
50932
51552
|
};
|
|
50933
51553
|
const listTasksTool = {
|
|
@@ -51146,13 +51766,29 @@ init_src();
|
|
|
51146
51766
|
// ../../packages/orchestration/src/agent-baseline.ts
|
|
51147
51767
|
var NEEDS_FIRMER_VERIFY = /deepseek|minimax|qwen|glm|gemma|kimi|mistral|llama/i;
|
|
51148
51768
|
var contentOfKind = (kind) => systemSkills.filter((s) => skillKind(s) === kind).map((s) => s.content.trim());
|
|
51149
|
-
|
|
51769
|
+
var MEMORY_DISCIPLINE_BLOCK = `## Memory discipline
|
|
51770
|
+
|
|
51771
|
+
### Correct what's wrong
|
|
51772
|
+
|
|
51773
|
+
If a fact from your Persistent memory block turns out to be false in practice \u2014 a file path that doesn't exist, an invalid ID, a procedure that fails the way the memory said it wouldn't \u2014 you MUST call \`mark_memory_outdated\` on it with the reason, then \`save_memory\` the corrected fact once you have verified it. Never silently keep reusing a fact you just found to be wrong.
|
|
51774
|
+
|
|
51775
|
+
### What's worth saving
|
|
51776
|
+
|
|
51777
|
+
A fact you save via \`save_memory\` must describe something VERIFIED \u2014 an exact path you confirmed, a real ID, a preference the user stated, a procedure that actually worked. Never save a micromanagement rule for another agent, and never save a discovery ban (e.g. "don't search for X", "don't explore Y") \u2014 every agent stays free to check things for itself when what it was given turns out to be wrong.`;
|
|
51778
|
+
var WORKER_DISCOVERY_BLOCK = `## Capitalize what you learn
|
|
51779
|
+
|
|
51780
|
+
When you discover something durable while working a task \u2014 the real path of a file or workflow, parameters that worked, a convention \u2014 save it via \`save_memory\` before you finish. One fact per call, short and verified.`;
|
|
51781
|
+
var DELEGATION_DISCIPLINE_BLOCK = `## Delegation discipline
|
|
51782
|
+
|
|
51783
|
+
When you delegate: (1) pass the PARAMETERS in the brief (paths, prompts, values) \u2014 do not do the prep work yourself that the worker can do with its own tools; (2) NEVER edit a shared or template file to encode a run's parameters \u2014 templates are immutable, values are passed as arguments; (3) only name a specific tool in a brief if you know the target agent has it \u2014 otherwise state the expected RESULT (the worker returns it via \`return_result\`) and deliver it yourself once it comes back; (4) a brief states the goal, the parameters, and the constraints \u2014 not a step-by-step procedure that forbids the worker from adapting.`;
|
|
51784
|
+
function buildBaselineBlock(model, opts = {}) {
|
|
51150
51785
|
const parts = contentOfKind("baseline");
|
|
51151
|
-
|
|
51152
|
-
const
|
|
51153
|
-
return `## How you work (always)
|
|
51786
|
+
const reinforcement = parts.length > 0 && NEEDS_FIRMER_VERIFY.test(model) ? "\n\n**Especially you \u2014 execution discipline:** Actually run or check your work before you say a task is done, and never write tool output you did not really get back. Be decisive: once a check passes (e.g. dependencies report ready), DO the action \u2014 do not keep re-verifying, re-listing, or running diagnostic commands. Use the tools, scripts, and exact file paths you were given (a skill loaded with skill_view ships run_skill_script and ready-made workflows/templates) instead of writing your own helper or conversion scripts, or rebuilding what already exists. Take the fewest steps that finish the task, then deliver the result with its output path." : "";
|
|
51787
|
+
const catalogBlock = parts.length > 0 ? `## How you work (always)
|
|
51154
51788
|
|
|
51155
|
-
${parts.join("\n\n")}${reinforcement}
|
|
51789
|
+
${parts.join("\n\n")}${reinforcement}` : "";
|
|
51790
|
+
const roleBlock = opts.role === "orchestrator" ? DELEGATION_DISCIPLINE_BLOCK : WORKER_DISCOVERY_BLOCK;
|
|
51791
|
+
return [catalogBlock, MEMORY_DISCIPLINE_BLOCK, roleBlock].filter(Boolean).join("\n\n");
|
|
51156
51792
|
}
|
|
51157
51793
|
function buildChannelBlock(opts) {
|
|
51158
51794
|
const onTelegram = opts.channel === "telegram" || opts.telegram === true;
|
|
@@ -51359,7 +51995,7 @@ ${skillIndex}` : "";
|
|
|
51359
51995
|
}) : [];
|
|
51360
51996
|
const memoryBlock = buildPersistentMemoryBlock(memoryRows);
|
|
51361
51997
|
const jobContextBlock = jobContext ? buildJobContextBlock(jobContext) : "";
|
|
51362
|
-
const baselineBlock = buildBaselineBlock(agent.model);
|
|
51998
|
+
const baselineBlock = buildBaselineBlock(agent.model, { role: agent.role });
|
|
51363
51999
|
const channelBlock = buildChannelBlock({
|
|
51364
52000
|
channel: jobContext?.origin,
|
|
51365
52001
|
telegram: Boolean(jobContext?.telegramChatId)
|
|
@@ -51792,6 +52428,7 @@ import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
|
51792
52428
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
51793
52429
|
|
|
51794
52430
|
// src/approvals/notify.ts
|
|
52431
|
+
init_src();
|
|
51795
52432
|
var APPROVAL_CALLBACK_PREFIX = "apr";
|
|
51796
52433
|
function approvalCallbackData(approvalRequestId, decision) {
|
|
51797
52434
|
return `${APPROVAL_CALLBACK_PREFIX}:${approvalRequestId}:${decision}`;
|
|
@@ -51818,7 +52455,7 @@ async function resolveTelegramDeliveryTarget(db, jobId) {
|
|
|
51818
52455
|
return null;
|
|
51819
52456
|
}
|
|
51820
52457
|
function describeGatedAction(toolName, toolInput) {
|
|
51821
|
-
const input = toolInput ?? {};
|
|
52458
|
+
const input = redactSecretsForAudit(toolInput ?? {});
|
|
51822
52459
|
const str = (v) => typeof v === "string" ? v : JSON.stringify(v ?? null);
|
|
51823
52460
|
switch (toolName) {
|
|
51824
52461
|
case "run_command":
|
|
@@ -51843,15 +52480,14 @@ async function notifyApprovalCreated(deps, req) {
|
|
|
51843
52480
|
const who = agent?.name ?? "An agent";
|
|
51844
52481
|
const input = req.toolInput ?? {};
|
|
51845
52482
|
const purpose = typeof input["purpose"] === "string" ? input["purpose"].trim() : "";
|
|
51846
|
-
const impact =
|
|
52483
|
+
const impact = computeApprovalImpactLine(req.toolName, req.toolInput);
|
|
51847
52484
|
const detail = describeGatedAction(req.toolName, req.toolInput);
|
|
51848
52485
|
const detailShort = detail.length > 500 ? detail.slice(0, 500) + "\n\u2026 (full detail on the dashboard)" : detail;
|
|
51849
52486
|
const text23 = `\u23F3 Approval needed \u2014 ${who}
|
|
51850
52487
|
|
|
51851
|
-
|
|
51852
|
-
|
|
51853
|
-
|
|
51854
|
-
` : "") + `
|
|
52488
|
+
\u27A4 ${purpose || "Purpose not specified by the agent."}
|
|
52489
|
+
\u26A0\uFE0F ${impact}
|
|
52490
|
+
|
|
51855
52491
|
Details:
|
|
51856
52492
|
${detailShort}
|
|
51857
52493
|
|
|
@@ -51876,7 +52512,7 @@ import { join as join9 } from "node:path";
|
|
|
51876
52512
|
|
|
51877
52513
|
// src/skills/install.ts
|
|
51878
52514
|
import { cp, rm as rm2, mkdir as mkdir4, readFile as readFile10 } from "node:fs/promises";
|
|
51879
|
-
import { dirname as dirname7, join as join8, basename as basename5, sep as sep5 } from "node:path";
|
|
52515
|
+
import { dirname as dirname7, join as join8, basename as basename5, sep as sep5, resolve as resolve2 } from "node:path";
|
|
51880
52516
|
|
|
51881
52517
|
// src/skills/source.ts
|
|
51882
52518
|
var SkillSourceError = class extends Error {
|
|
@@ -51887,6 +52523,11 @@ var SkillSourceError = class extends Error {
|
|
|
51887
52523
|
};
|
|
51888
52524
|
var SEGMENT = "[A-Za-z0-9._-]+";
|
|
51889
52525
|
var OWNER_REPO = new RegExp(`^(${SEGMENT})/(${SEGMENT})$`);
|
|
52526
|
+
var SEGMENT_RE = new RegExp(`^${SEGMENT}$`);
|
|
52527
|
+
function isSafeSubdir(subdir) {
|
|
52528
|
+
if (subdir.startsWith("/") || subdir.includes("\\")) return false;
|
|
52529
|
+
return !subdir.split("/").includes("..");
|
|
52530
|
+
}
|
|
51890
52531
|
var ALLOWED_HOSTS = /* @__PURE__ */ new Set([
|
|
51891
52532
|
"github.com",
|
|
51892
52533
|
"www.github.com",
|
|
@@ -52010,12 +52651,22 @@ function parseSkillSource(input) {
|
|
|
52010
52651
|
});
|
|
52011
52652
|
}
|
|
52012
52653
|
const segs = raw.split("/").filter(Boolean);
|
|
52013
|
-
if (segs.length >= 2 &&
|
|
52654
|
+
if (segs.length >= 2 && SEGMENT_RE.test(segs[0])) {
|
|
52655
|
+
const repo = stripGitSuffix(segs[1]);
|
|
52656
|
+
if (!SEGMENT_RE.test(repo)) {
|
|
52657
|
+
throw new SkillSourceError(`Invalid repository name in "${raw}".`);
|
|
52658
|
+
}
|
|
52659
|
+
const subdir = segs.slice(2).join("/") || null;
|
|
52660
|
+
if (subdir !== null && !isSafeSubdir(subdir)) {
|
|
52661
|
+
throw new SkillSourceError(
|
|
52662
|
+
`Unsafe path in skill source "${raw}": must not contain ".." or an absolute/backslash path.`
|
|
52663
|
+
);
|
|
52664
|
+
}
|
|
52014
52665
|
return github({
|
|
52015
52666
|
owner: segs[0],
|
|
52016
|
-
repo
|
|
52667
|
+
repo,
|
|
52017
52668
|
ref: null,
|
|
52018
|
-
subdir
|
|
52669
|
+
subdir,
|
|
52019
52670
|
skillName: null,
|
|
52020
52671
|
raw
|
|
52021
52672
|
});
|
|
@@ -52090,7 +52741,8 @@ async function fetchAllowlisted(url2, headers) {
|
|
|
52090
52741
|
const res = await fetch(current, { redirect: "manual", headers });
|
|
52091
52742
|
if (res.status >= 300 && res.status < 400) {
|
|
52092
52743
|
const location = res.headers.get("location");
|
|
52093
|
-
if (!location)
|
|
52744
|
+
if (!location)
|
|
52745
|
+
throw new SkillFetchError(`Redirect response (${res.status}) missing Location.`);
|
|
52094
52746
|
current = new URL(location, current).toString();
|
|
52095
52747
|
continue;
|
|
52096
52748
|
}
|
|
@@ -52152,7 +52804,9 @@ async function unzipBufferTo(buf, destDir) {
|
|
|
52152
52804
|
}
|
|
52153
52805
|
if (final && !violation) {
|
|
52154
52806
|
const fileData = Buffer.concat(chunks);
|
|
52155
|
-
pendingWrites.push(
|
|
52807
|
+
pendingWrites.push(
|
|
52808
|
+
mkdir3(dirname6(abs), { recursive: true }).then(() => writeFile5(abs, fileData))
|
|
52809
|
+
);
|
|
52156
52810
|
}
|
|
52157
52811
|
};
|
|
52158
52812
|
file.start();
|
|
@@ -52614,7 +53268,13 @@ function buildContent(slug, body, scripts) {
|
|
|
52614
53268
|
async function pickManifest(extractRoot, subdir, skillName) {
|
|
52615
53269
|
if (subdir) {
|
|
52616
53270
|
const rel = `${subdir.replace(/\/+$/, "")}/SKILL.md`;
|
|
52617
|
-
|
|
53271
|
+
const rootResolved = resolve2(extractRoot);
|
|
53272
|
+
const rootWithSep = rootResolved.endsWith(sep5) ? rootResolved : rootResolved + sep5;
|
|
53273
|
+
const abs = resolve2(join8(extractRoot, rel));
|
|
53274
|
+
if (abs !== rootResolved && !abs.startsWith(rootWithSep)) {
|
|
53275
|
+
throw new SkillInstallError(`Unsafe skill path: "${subdir}" escapes the repository.`);
|
|
53276
|
+
}
|
|
53277
|
+
if (await isFile(abs)) return rel;
|
|
52618
53278
|
throw new SkillInstallError(`No SKILL.md at "${subdir}" in the repository.`);
|
|
52619
53279
|
}
|
|
52620
53280
|
const manifests = await findSkillManifests(extractRoot);
|
|
@@ -53232,23 +53892,11 @@ async function resolveSearchBackend(db, agentId) {
|
|
|
53232
53892
|
}
|
|
53233
53893
|
return void 0;
|
|
53234
53894
|
}
|
|
53235
|
-
var
|
|
53236
|
-
"telegram_send_message",
|
|
53237
|
-
"send_image",
|
|
53238
|
-
"send_file",
|
|
53239
|
-
"send_video",
|
|
53240
|
-
"send_audio",
|
|
53241
|
-
"send_voice"
|
|
53242
|
-
]);
|
|
53895
|
+
var DELIVERY_TOOL_NAMES2 = new Set(DELIVERY_TOOL_NAMES);
|
|
53243
53896
|
var DELIVERY_OR_TERMINAL_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
53244
53897
|
"return_result",
|
|
53245
53898
|
"dashboard_publish",
|
|
53246
|
-
|
|
53247
|
-
"send_image",
|
|
53248
|
-
"send_file",
|
|
53249
|
-
"send_video",
|
|
53250
|
-
"send_audio",
|
|
53251
|
-
"send_voice"
|
|
53899
|
+
...DELIVERY_TOOL_NAMES
|
|
53252
53900
|
]);
|
|
53253
53901
|
var TOOL_ONLY_DELIVERY_CHANNELS = /* @__PURE__ */ new Set(["telegram"]);
|
|
53254
53902
|
function truncateForContext(value) {
|
|
@@ -53412,6 +54060,15 @@ async function maybeResumeParent(childJobId, outcome, deps, runnerEnv) {
|
|
|
53412
54060
|
void triggerWorker(parentJobId, runnerEnv);
|
|
53413
54061
|
}
|
|
53414
54062
|
}
|
|
54063
|
+
async function reviveJobIfApprovalResolvedDuringSuspend(db, jobId, runnerEnv) {
|
|
54064
|
+
const openRows = await db.select({ status: approvalRequests.status }).from(approvalRequests).where(and4(eq5(approvalRequests.jobId, jobId), isNull(approvalRequests.executedAt)));
|
|
54065
|
+
const hasResolved = openRows.some((r) => r.status === "approved" || r.status === "rejected");
|
|
54066
|
+
if (!hasResolved) return;
|
|
54067
|
+
const flipped = await db.update(agentJobs).set({ status: "pending", updatedAt: /* @__PURE__ */ new Date() }).where(and4(eq5(agentJobs.id, jobId), eq5(agentJobs.status, "awaiting_approval"))).returning({ id: agentJobs.id });
|
|
54068
|
+
if (flipped.length > 0 && runnerEnv) {
|
|
54069
|
+
void triggerWorker(jobId, runnerEnv);
|
|
54070
|
+
}
|
|
54071
|
+
}
|
|
53415
54072
|
async function runJob(jobId, deps, runnerEnv, opts) {
|
|
53416
54073
|
const { db, registry } = deps;
|
|
53417
54074
|
let llmClient;
|
|
@@ -53487,8 +54144,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53487
54144
|
const sharedPath = join10(homedir3(), ".nodalai", "workspaces", job.entityId, "shared");
|
|
53488
54145
|
try {
|
|
53489
54146
|
mkdirSync2(sharedPath, { recursive: true });
|
|
53490
|
-
if (!agentWorkspacesList.some((w) => w.label ===
|
|
53491
|
-
agentWorkspacesList.push({ label:
|
|
54147
|
+
if (!agentWorkspacesList.some((w) => w.label === SHARED_WORKSPACE_LABEL)) {
|
|
54148
|
+
agentWorkspacesList.push({ label: SHARED_WORKSPACE_LABEL, path: sharedPath });
|
|
53492
54149
|
}
|
|
53493
54150
|
} catch {
|
|
53494
54151
|
}
|
|
@@ -53555,7 +54212,13 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53555
54212
|
const fileWriteToolNames = fileWritableSkillSlugs.length > 0 ? ["skill_file_write"] : [];
|
|
53556
54213
|
const fileWriteToolDefs = fileWriteToolNames.map((n) => registry.get(n)).filter((t) => t !== void 0);
|
|
53557
54214
|
const capabilityTools = [];
|
|
53558
|
-
|
|
54215
|
+
let inheritedRootBotToken = null;
|
|
54216
|
+
if (!agentRow.telegramBotToken && job.parentJobId && job.chatId) {
|
|
54217
|
+
const [rootAgentTokenRow] = await db.select({ telegramBotToken: agents.telegramBotToken }).from(entities).innerJoin(agents, eq5(agents.id, entities.rootAgentId)).where(eq5(entities.id, job.entityId ?? "")).limit(1);
|
|
54218
|
+
inheritedRootBotToken = rootAgentTokenRow?.telegramBotToken ?? null;
|
|
54219
|
+
}
|
|
54220
|
+
const deliveryBotToken = agentRow.telegramBotToken ?? inheritedRootBotToken;
|
|
54221
|
+
if (deliveryBotToken && job.chatId) {
|
|
53559
54222
|
capabilityTools.push(createTelegramSendMessageTool());
|
|
53560
54223
|
capabilityTools.push(createSendImageTool());
|
|
53561
54224
|
capabilityTools.push(createSendFileTool());
|
|
@@ -53645,6 +54308,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53645
54308
|
capabilityTools.push(...filtered);
|
|
53646
54309
|
}
|
|
53647
54310
|
const mcpAssignments = await db.select({
|
|
54311
|
+
id: mcpServers.id,
|
|
53648
54312
|
slug: mcpServers.slug,
|
|
53649
54313
|
transport: mcpServers.transport,
|
|
53650
54314
|
url: mcpServers.url,
|
|
@@ -53654,10 +54318,21 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53654
54318
|
command: mcpServers.command,
|
|
53655
54319
|
args: mcpServers.args,
|
|
53656
54320
|
envVars: mcpServers.envVars,
|
|
54321
|
+
availableTools: mcpServers.availableTools,
|
|
53657
54322
|
enabledTools: agentMcpServers.enabledTools
|
|
53658
54323
|
}).from(agentMcpServers).innerJoin(mcpServers, eq5(mcpServers.id, agentMcpServers.mcpServerId)).where(eq5(agentMcpServers.agentId, agentRow.id));
|
|
54324
|
+
const refreshMcpToolCache = async (mcpServerId, liveTools) => {
|
|
54325
|
+
try {
|
|
54326
|
+
await db.update(mcpServers).set({ availableTools: liveTools }).where(eq5(mcpServers.id, mcpServerId));
|
|
54327
|
+
} catch (err) {
|
|
54328
|
+
console.error(
|
|
54329
|
+
`[execute] failed to refresh available_tools cache for MCP server '${mcpServerId}': ${err instanceof Error ? err.message : String(err)}`
|
|
54330
|
+
);
|
|
54331
|
+
}
|
|
54332
|
+
};
|
|
53659
54333
|
for (const ms of mcpAssignments) {
|
|
53660
54334
|
try {
|
|
54335
|
+
const availableTools = ms.availableTools;
|
|
53661
54336
|
let toolset;
|
|
53662
54337
|
if (ms.transport === "stdio") {
|
|
53663
54338
|
if (!ms.command) continue;
|
|
@@ -53670,13 +54345,23 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53670
54345
|
} catch {
|
|
53671
54346
|
continue;
|
|
53672
54347
|
}
|
|
53673
|
-
|
|
54348
|
+
const connectOpts = {
|
|
53674
54349
|
transport: "stdio",
|
|
53675
54350
|
slug: ms.slug,
|
|
53676
54351
|
command: ms.command,
|
|
53677
54352
|
args: ms.args ?? [],
|
|
53678
54353
|
env: decryptedEnv
|
|
53679
|
-
}
|
|
54354
|
+
};
|
|
54355
|
+
if (isUsableMcpToolCache(availableTools)) {
|
|
54356
|
+
toolset = createLazyMcpTools(connectOpts, availableTools, {
|
|
54357
|
+
onConnected: (liveTools) => refreshMcpToolCache(ms.id, liveTools)
|
|
54358
|
+
});
|
|
54359
|
+
} else {
|
|
54360
|
+
toolset = await createMcpTools(connectOpts);
|
|
54361
|
+
if (toolset.descriptors?.length) {
|
|
54362
|
+
await refreshMcpToolCache(ms.id, toolset.descriptors);
|
|
54363
|
+
}
|
|
54364
|
+
}
|
|
53680
54365
|
} else {
|
|
53681
54366
|
if (!ms.url || !ms.apiKey || !ms.authScheme || !ms.authParamName) continue;
|
|
53682
54367
|
let decryptedKey;
|
|
@@ -53688,14 +54373,24 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53688
54373
|
);
|
|
53689
54374
|
continue;
|
|
53690
54375
|
}
|
|
53691
|
-
|
|
54376
|
+
const connectOpts = {
|
|
53692
54377
|
transport: "http",
|
|
53693
54378
|
slug: ms.slug,
|
|
53694
54379
|
url: ms.url,
|
|
53695
54380
|
apiKey: decryptedKey,
|
|
53696
54381
|
authScheme: ms.authScheme,
|
|
53697
54382
|
authParamName: ms.authParamName
|
|
53698
|
-
}
|
|
54383
|
+
};
|
|
54384
|
+
if (isUsableMcpToolCache(availableTools)) {
|
|
54385
|
+
toolset = createLazyMcpTools(connectOpts, availableTools, {
|
|
54386
|
+
onConnected: (liveTools) => refreshMcpToolCache(ms.id, liveTools)
|
|
54387
|
+
});
|
|
54388
|
+
} else {
|
|
54389
|
+
toolset = await createMcpTools(connectOpts);
|
|
54390
|
+
if (toolset.descriptors?.length) {
|
|
54391
|
+
await refreshMcpToolCache(ms.id, toolset.descriptors);
|
|
54392
|
+
}
|
|
54393
|
+
}
|
|
53699
54394
|
}
|
|
53700
54395
|
mcpClosers.push(toolset.close);
|
|
53701
54396
|
const enabled = ms.enabledTools;
|
|
@@ -53744,7 +54439,13 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53744
54439
|
}));
|
|
53745
54440
|
const authMode = runnerEnv?.AUTH_MODE ?? process.env["AUTH_MODE"] ?? "local-trust";
|
|
53746
54441
|
if (authMode !== "local-trust") {
|
|
53747
|
-
const CODE_EXECUTION_TOOLS = [
|
|
54442
|
+
const CODE_EXECUTION_TOOLS = [
|
|
54443
|
+
"run_command",
|
|
54444
|
+
"run_skill_script",
|
|
54445
|
+
"skill_file_write",
|
|
54446
|
+
"create_mcp",
|
|
54447
|
+
"attach_mcp"
|
|
54448
|
+
];
|
|
53748
54449
|
const [yoloEntityRow] = await db.select({ lanCommandYolo: entities.lanCommandYolo }).from(entities).where(eq5(entities.id, job.entityId ?? "")).limit(1);
|
|
53749
54450
|
if (!yoloEntityRow?.lanCommandYolo) {
|
|
53750
54451
|
for (const codeTool of CODE_EXECUTION_TOOLS) {
|
|
@@ -53805,102 +54506,109 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53805
54506
|
if (serialized.length <= MAX_TOOL_RESULT_CHARS) return { type: "json", value: json };
|
|
53806
54507
|
return { type: "text", value: truncateForContext(serialized) };
|
|
53807
54508
|
};
|
|
53808
|
-
{
|
|
53809
|
-
|
|
53810
|
-
const resolvedRows = pendingExecRows.filter(
|
|
53811
|
-
(r) => r.status === "approved" || r.status === "rejected"
|
|
53812
|
-
);
|
|
54509
|
+
const executeResolvedApprovals = async (resolvedRows, msgsIn) => {
|
|
54510
|
+
let msgs = msgsIn;
|
|
53813
54511
|
let catastrophicRefusalMessage = null;
|
|
53814
|
-
|
|
53815
|
-
|
|
53816
|
-
|
|
53817
|
-
|
|
53818
|
-
|
|
53819
|
-
|
|
53820
|
-
|
|
53821
|
-
|
|
53822
|
-
|
|
53823
|
-
|
|
53824
|
-
|
|
53825
|
-
|
|
54512
|
+
for (const req of resolvedRows) {
|
|
54513
|
+
let replacementOutput;
|
|
54514
|
+
if (req.status === "approved") {
|
|
54515
|
+
const resumeCommand = String(
|
|
54516
|
+
req.toolInput?.command ?? ""
|
|
54517
|
+
);
|
|
54518
|
+
const isCatastrophicResume = req.toolName === "run_command" && isCatastrophicCommand(resumeCommand);
|
|
54519
|
+
if (isCatastrophicResume) {
|
|
54520
|
+
catastrophicRefusalMessage = "Cette commande est jug\xE9e catastrophique (destruction machine-wide : rm -rf /, mkfs, shutdown\u2026) et reste refus\xE9e m\xEAme apr\xE8s approbation, par s\xE9curit\xE9. Elle n'a pas \xE9t\xE9 ex\xE9cut\xE9e.";
|
|
54521
|
+
replacementOutput = toResultOutput({ error: catastrophicRefusalMessage });
|
|
54522
|
+
trace("resume_catastrophic_command_refused", { toolName: req.toolName });
|
|
54523
|
+
} else {
|
|
54524
|
+
const toolDef = toolMap.get(req.toolName);
|
|
54525
|
+
if (!toolDef) {
|
|
54526
|
+
replacementOutput = toResultOutput({
|
|
54527
|
+
error: `approved_tool_not_found:${req.toolName}`
|
|
54528
|
+
});
|
|
53826
54529
|
} else {
|
|
53827
|
-
const
|
|
53828
|
-
|
|
53829
|
-
|
|
53830
|
-
|
|
53831
|
-
|
|
53832
|
-
|
|
53833
|
-
|
|
53834
|
-
|
|
53835
|
-
|
|
53836
|
-
|
|
53837
|
-
|
|
53838
|
-
|
|
53839
|
-
|
|
53840
|
-
|
|
53841
|
-
|
|
53842
|
-
|
|
53843
|
-
|
|
53844
|
-
|
|
53845
|
-
|
|
53846
|
-
|
|
53847
|
-
|
|
53848
|
-
|
|
53849
|
-
|
|
53850
|
-
|
|
53851
|
-
|
|
53852
|
-
|
|
53853
|
-
|
|
53854
|
-
|
|
53855
|
-
|
|
53856
|
-
|
|
53857
|
-
|
|
53858
|
-
|
|
53859
|
-
},
|
|
53860
|
-
{
|
|
53861
|
-
approvalRules: resumeApprovalRules,
|
|
53862
|
-
autonomy: workspaceAutonomy,
|
|
53863
|
-
onApprovalRequired: (req2) => notifyApprovalCreated(deps, req2)
|
|
53864
|
-
}
|
|
53865
|
-
);
|
|
53866
|
-
if (execResult.outcome === "success") {
|
|
53867
|
-
replacementOutput = toResultOutput(execResult.output);
|
|
53868
|
-
} else if (execResult.outcome === "error") {
|
|
53869
|
-
replacementOutput = toResultOutput({ error: execResult.error });
|
|
53870
|
-
} else {
|
|
53871
|
-
replacementOutput = toResultOutput({ error: "unexpected_gate_on_approved_tool" });
|
|
54530
|
+
const resumeApprovalRules = [
|
|
54531
|
+
{
|
|
54532
|
+
id: "resume-bypass",
|
|
54533
|
+
entityId: job.entityId ?? "",
|
|
54534
|
+
agentId: null,
|
|
54535
|
+
toolName: req.toolName,
|
|
54536
|
+
action: "auto_approve"
|
|
54537
|
+
}
|
|
54538
|
+
];
|
|
54539
|
+
const execResult = await executeTool(
|
|
54540
|
+
toolDef,
|
|
54541
|
+
req.toolInput,
|
|
54542
|
+
{
|
|
54543
|
+
jobId,
|
|
54544
|
+
agentId: agentRow.id,
|
|
54545
|
+
entityId: job.entityId ?? "",
|
|
54546
|
+
db,
|
|
54547
|
+
jobChatId: job.chatId ?? null,
|
|
54548
|
+
resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
|
|
54549
|
+
embeddingClient: deps.embeddingClient,
|
|
54550
|
+
workspaces: agentWorkspacesList,
|
|
54551
|
+
skillStoreDir: skillStore,
|
|
54552
|
+
assignedSkillSlugs,
|
|
54553
|
+
scriptAuthorizedSkillSlugs,
|
|
54554
|
+
fileWritableSkillSlugs,
|
|
54555
|
+
provisioning: TOOL_PROVISIONING,
|
|
54556
|
+
searchBackend
|
|
54557
|
+
},
|
|
54558
|
+
{
|
|
54559
|
+
approvalRules: resumeApprovalRules,
|
|
54560
|
+
autonomy: workspaceAutonomy,
|
|
54561
|
+
onApprovalRequired: (r) => notifyApprovalCreated(deps, r)
|
|
53872
54562
|
}
|
|
54563
|
+
);
|
|
54564
|
+
if (execResult.outcome === "success") {
|
|
54565
|
+
replacementOutput = toResultOutput(execResult.output);
|
|
54566
|
+
} else if (execResult.outcome === "error") {
|
|
54567
|
+
replacementOutput = toResultOutput({ error: execResult.error });
|
|
54568
|
+
} else {
|
|
54569
|
+
replacementOutput = toResultOutput({ error: "unexpected_gate_on_approved_tool" });
|
|
53873
54570
|
}
|
|
53874
54571
|
}
|
|
53875
|
-
trace("resume_approved_tool_executed", { toolName: req.toolName });
|
|
53876
|
-
} else {
|
|
53877
|
-
const reason = req.notes ?? "no reason provided";
|
|
53878
|
-
replacementOutput = toResultOutput(
|
|
53879
|
-
`[REJECTED] Human reviewer rejected this action. Reason: ${reason}. Adapt your approach.`
|
|
53880
|
-
);
|
|
53881
|
-
trace("resume_rejected_tool_marker_replaced", { toolName: req.toolName });
|
|
53882
54572
|
}
|
|
53883
|
-
|
|
53884
|
-
|
|
53885
|
-
|
|
53886
|
-
|
|
53887
|
-
|
|
53888
|
-
|
|
53889
|
-
|
|
53890
|
-
|
|
53891
|
-
|
|
53892
|
-
|
|
53893
|
-
|
|
53894
|
-
|
|
53895
|
-
|
|
53896
|
-
|
|
53897
|
-
|
|
53898
|
-
|
|
53899
|
-
|
|
54573
|
+
trace("resume_approved_tool_executed", { toolName: req.toolName });
|
|
54574
|
+
} else {
|
|
54575
|
+
const reason = req.notes ?? "no reason provided";
|
|
54576
|
+
replacementOutput = toResultOutput(
|
|
54577
|
+
`[REJECTED] Human reviewer rejected this action. Reason: ${reason}. Adapt your approach.`
|
|
54578
|
+
);
|
|
54579
|
+
trace("resume_rejected_tool_marker_replaced", { toolName: req.toolName });
|
|
54580
|
+
}
|
|
54581
|
+
msgs = msgs.map((msg) => {
|
|
54582
|
+
if (typeof msg !== "object" || msg === null || msg.role !== "tool") {
|
|
54583
|
+
return msg;
|
|
54584
|
+
}
|
|
54585
|
+
const toolMsg = msg;
|
|
54586
|
+
if (!Array.isArray(toolMsg.content)) return msg;
|
|
54587
|
+
const updatedContent = toolMsg.content.map((block) => {
|
|
54588
|
+
if (typeof block !== "object" || block === null || block.type !== "tool-result") {
|
|
54589
|
+
return block;
|
|
54590
|
+
}
|
|
54591
|
+
const tb = block;
|
|
54592
|
+
if (tb.toolName !== req.toolName) return block;
|
|
54593
|
+
const outputText = tb.output.type === "text" ? tb.output.value : JSON.stringify(tb.output.value);
|
|
54594
|
+
if (!outputText.includes("[AWAITING_APPROVAL]")) return block;
|
|
54595
|
+
return { ...tb, output: replacementOutput };
|
|
53900
54596
|
});
|
|
53901
|
-
|
|
53902
|
-
}
|
|
53903
|
-
|
|
54597
|
+
return { ...toolMsg, content: updatedContent };
|
|
54598
|
+
});
|
|
54599
|
+
await db.update(approvalRequests).set({ executedAt: /* @__PURE__ */ new Date() }).where(eq5(approvalRequests.id, req.id));
|
|
54600
|
+
}
|
|
54601
|
+
return { messages: msgs, catastrophicRefusalMessage };
|
|
54602
|
+
};
|
|
54603
|
+
{
|
|
54604
|
+
const pendingExecRows = await db.select().from(approvalRequests).where(and4(eq5(approvalRequests.jobId, jobId), isNull(approvalRequests.executedAt))).orderBy(approvalRequests.requestedAt);
|
|
54605
|
+
const resolvedRows = pendingExecRows.filter(
|
|
54606
|
+
(r) => r.status === "approved" || r.status === "rejected"
|
|
54607
|
+
);
|
|
54608
|
+
if (resolvedRows.length > 0) {
|
|
54609
|
+
const executed = await executeResolvedApprovals(resolvedRows, messages);
|
|
54610
|
+
messages = executed.messages;
|
|
54611
|
+
if (executed.catastrophicRefusalMessage !== null) {
|
|
53904
54612
|
trace("resume_catastrophic_command_failed_job");
|
|
53905
54613
|
await failJob(
|
|
53906
54614
|
db,
|
|
@@ -53908,12 +54616,12 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53908
54616
|
"catastrophic_command_refused",
|
|
53909
54617
|
runStats(),
|
|
53910
54618
|
messages,
|
|
53911
|
-
catastrophicRefusalMessage
|
|
54619
|
+
executed.catastrophicRefusalMessage
|
|
53912
54620
|
);
|
|
53913
54621
|
return {
|
|
53914
54622
|
status: "failed",
|
|
53915
54623
|
error: "catastrophic_command_refused",
|
|
53916
|
-
result: catastrophicRefusalMessage
|
|
54624
|
+
result: executed.catastrophicRefusalMessage
|
|
53917
54625
|
};
|
|
53918
54626
|
}
|
|
53919
54627
|
await saveCheckpoint(db, jobId, {
|
|
@@ -53942,10 +54650,67 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53942
54650
|
const MAX_TELEGRAM_REDELIVERY_NUDGES = 2;
|
|
53943
54651
|
let telegramRedeliveryNudges = 0;
|
|
53944
54652
|
let telegramDelivered = false;
|
|
54653
|
+
let deliveredViaToolName = null;
|
|
53945
54654
|
const deliveryNudge = "[syst\xE8me] Tu es sur Telegram. Tu n'as pas encore livr\xE9 ta r\xE9ponse \xE0 l'utilisateur. Appelle `telegram_send_message` avec ta r\xE9ponse, PUIS `return_result`. Ne r\xE9ponds pas en texte simple \u2014 sur Telegram, seul un message envoy\xE9 via `telegram_send_message` est visible par l'utilisateur.";
|
|
54655
|
+
const withDeliveryNotice = (resultText) => {
|
|
54656
|
+
if (!job.parentJobId || !deliveredViaToolName) return resultText;
|
|
54657
|
+
const notice = `[livraison effectu\xE9e : ${deliveredViaToolName}${job.chatId ? ` \u2192 chat ${job.chatId}` : ""}]`;
|
|
54658
|
+
return resultText ? `${resultText}
|
|
54659
|
+
|
|
54660
|
+
${notice}` : notice;
|
|
54661
|
+
};
|
|
53946
54662
|
let approvalPending = false;
|
|
53947
54663
|
const approvalNudge = "[syst\xE8me] Tu es sur Telegram et une de tes actions vient de cr\xE9er une demande d'approbation : elle attend la validation de l'utilisateur avant de s'ex\xE9cuter. Avant que le job se mette en pause, appelle `telegram_send_message` pour dire \xE0 l'utilisateur, avec tes propres mots, quelle action tu as lanc\xE9e et que tu attends son approbation (il pourra valider directement depuis Telegram via les boutons \u2705/\u274C, ou depuis le dashboard). N'appelle PAS `return_result` \u2014 la mise en pause est automatique.";
|
|
54664
|
+
const approvalGraceMsRaw = runnerEnv?.NODALAI_APPROVAL_GRACE_MS ?? Number(process.env["NODALAI_APPROVAL_GRACE_MS"]);
|
|
54665
|
+
const approvalGraceMs = Number.isFinite(approvalGraceMsRaw) && approvalGraceMsRaw >= 0 ? approvalGraceMsRaw : 12e4;
|
|
53948
54666
|
const suspendForApproval = async () => {
|
|
54667
|
+
if (approvalGraceMs > 0) {
|
|
54668
|
+
const pollIntervalMs = Math.max(1, Math.min(2e3, Math.floor(approvalGraceMs / 4)));
|
|
54669
|
+
const deadline = Date.now() + approvalGraceMs;
|
|
54670
|
+
const graceHbInterval = setInterval(() => {
|
|
54671
|
+
void touchJob(db, jobId).catch(() => {
|
|
54672
|
+
});
|
|
54673
|
+
}, 6e4);
|
|
54674
|
+
try {
|
|
54675
|
+
while (Date.now() < deadline) {
|
|
54676
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
|
|
54677
|
+
const [statusRow] = await db.select({ status: agentJobs.status }).from(agentJobs).where(eq5(agentJobs.id, jobId));
|
|
54678
|
+
if (statusRow?.status === "cancelled") {
|
|
54679
|
+
trace("grace_window_cancelled");
|
|
54680
|
+
await cancelJob(db, jobId, runStats(), messages);
|
|
54681
|
+
return { status: "cancelled" };
|
|
54682
|
+
}
|
|
54683
|
+
const openRows = await db.select().from(approvalRequests).where(
|
|
54684
|
+
and4(eq5(approvalRequests.jobId, jobId), isNull(approvalRequests.executedAt))
|
|
54685
|
+
).orderBy(approvalRequests.requestedAt);
|
|
54686
|
+
if (openRows.length === 0 || openRows.some((r) => r.status === "pending")) {
|
|
54687
|
+
continue;
|
|
54688
|
+
}
|
|
54689
|
+
trace("grace_window_resolved_inline", { count: openRows.length });
|
|
54690
|
+
const executed = await executeResolvedApprovals(openRows, messages);
|
|
54691
|
+
messages = executed.messages;
|
|
54692
|
+
if (executed.catastrophicRefusalMessage !== null) {
|
|
54693
|
+
trace("resume_catastrophic_command_failed_job");
|
|
54694
|
+
await failJob(
|
|
54695
|
+
db,
|
|
54696
|
+
jobId,
|
|
54697
|
+
"catastrophic_command_refused",
|
|
54698
|
+
runStats(),
|
|
54699
|
+
messages,
|
|
54700
|
+
executed.catastrophicRefusalMessage
|
|
54701
|
+
);
|
|
54702
|
+
return {
|
|
54703
|
+
status: "failed",
|
|
54704
|
+
error: "catastrophic_command_refused",
|
|
54705
|
+
result: executed.catastrophicRefusalMessage
|
|
54706
|
+
};
|
|
54707
|
+
}
|
|
54708
|
+
return "resumed_inline";
|
|
54709
|
+
}
|
|
54710
|
+
} finally {
|
|
54711
|
+
clearInterval(graceHbInterval);
|
|
54712
|
+
}
|
|
54713
|
+
}
|
|
53949
54714
|
await saveCheckpoint(db, jobId, {
|
|
53950
54715
|
messages,
|
|
53951
54716
|
turn,
|
|
@@ -53958,6 +54723,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53958
54723
|
servedProvider
|
|
53959
54724
|
});
|
|
53960
54725
|
await setJobStatus(db, jobId, "awaiting_approval");
|
|
54726
|
+
await reviveJobIfApprovalResolvedDuringSuspend(db, jobId, runnerEnv);
|
|
53961
54727
|
return { status: "awaiting_approval" };
|
|
53962
54728
|
};
|
|
53963
54729
|
const maxTotalTokensPerJob = (() => {
|
|
@@ -53971,7 +54737,11 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
53971
54737
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMITS.maxCostPerJobUsd;
|
|
53972
54738
|
})();
|
|
53973
54739
|
const compactionThreshold = (() => {
|
|
53974
|
-
const ctxWindow = modelContextWindow(
|
|
54740
|
+
const ctxWindow = modelContextWindow(
|
|
54741
|
+
llmClient.config.provider,
|
|
54742
|
+
llmClient.config.model,
|
|
54743
|
+
llmClient.config.contextWindow
|
|
54744
|
+
);
|
|
53975
54745
|
const fracRaw = Number(process.env["LLM_COMPACTION_FRACTION"]);
|
|
53976
54746
|
const frac = Number.isFinite(fracRaw) && fracRaw > 0 && fracRaw < 1 ? fracRaw : 0.7;
|
|
53977
54747
|
const absRaw = Number(process.env["LLM_COMPACTION_TOKENS"]);
|
|
@@ -54167,7 +54937,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54167
54937
|
},
|
|
54168
54938
|
...servedProvider ? { servedProvider } : {}
|
|
54169
54939
|
});
|
|
54170
|
-
const isDeliveryOnlyTurn = rawToolCalls.length > 0 && rawToolCalls.every((tc) =>
|
|
54940
|
+
const isDeliveryOnlyTurn = rawToolCalls.length > 0 && rawToolCalls.every((tc) => DELIVERY_TOOL_NAMES2.has(tc.toolName));
|
|
54171
54941
|
consecutiveDeliveryOnlyTurns = isDeliveryOnlyTurn ? consecutiveDeliveryOnlyTurns + 1 : 0;
|
|
54172
54942
|
if (consecutiveDeliveryOnlyTurns > DEFAULT_LIMITS.maxConsecutiveDeliveryTurns) {
|
|
54173
54943
|
trace("delivery_spam_guard", { turn, consecutiveDeliveryOnlyTurns });
|
|
@@ -54197,7 +54967,12 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54197
54967
|
if (rawToolCalls.length === 0) {
|
|
54198
54968
|
trace("no_tool_calls_branch", { turn, hasText: Boolean(response.text) });
|
|
54199
54969
|
if (approvalPending) {
|
|
54200
|
-
|
|
54970
|
+
const suspended = await suspendForApproval();
|
|
54971
|
+
if (suspended === "resumed_inline") {
|
|
54972
|
+
approvalPending = false;
|
|
54973
|
+
continue;
|
|
54974
|
+
}
|
|
54975
|
+
return suspended;
|
|
54201
54976
|
}
|
|
54202
54977
|
const textContent = response.text ?? "";
|
|
54203
54978
|
if (textContent) {
|
|
@@ -54240,7 +55015,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54240
55015
|
runnerEnv
|
|
54241
55016
|
).catch((e) => console.warn("[reflection]", e));
|
|
54242
55017
|
}
|
|
54243
|
-
return { status: "completed", result: textContent };
|
|
55018
|
+
return { status: "completed", result: withDeliveryNotice(textContent) };
|
|
54244
55019
|
}
|
|
54245
55020
|
if (emptyTurnRetries < MAX_EMPTY_TURN_RETRIES) {
|
|
54246
55021
|
emptyTurnRetries += 1;
|
|
@@ -54286,6 +55061,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54286
55061
|
entityId: job.entityId ?? "",
|
|
54287
55062
|
db,
|
|
54288
55063
|
jobChatId: job.chatId ?? null,
|
|
55064
|
+
resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
|
|
54289
55065
|
embeddingClient: deps.embeddingClient,
|
|
54290
55066
|
workspaces: agentWorkspacesList,
|
|
54291
55067
|
skillStoreDir: skillStore,
|
|
@@ -54451,6 +55227,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54451
55227
|
entityId: job.entityId ?? "",
|
|
54452
55228
|
db,
|
|
54453
55229
|
jobChatId: job.chatId ?? null,
|
|
55230
|
+
resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
|
|
54454
55231
|
embeddingClient: deps.embeddingClient,
|
|
54455
55232
|
workspaces: agentWorkspacesList,
|
|
54456
55233
|
skillStoreDir: skillStore,
|
|
@@ -54493,7 +55270,11 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54493
55270
|
parentJobId: job.parentJobId ?? null,
|
|
54494
55271
|
task: job.task,
|
|
54495
55272
|
channel: job.channel,
|
|
54496
|
-
chatId: job.chatId
|
|
55273
|
+
chatId: job.chatId,
|
|
55274
|
+
// Lot E (conversations) : le child délégué hérite du fil de
|
|
55275
|
+
// conversation du parent — sans ça, la page Jobs rattacherait
|
|
55276
|
+
// le sous-job à aucune conversation.
|
|
55277
|
+
conversationId: job.conversationId ?? null
|
|
54497
55278
|
};
|
|
54498
55279
|
const preAssignSideResults = toolResultBlocks.map((b) => ({
|
|
54499
55280
|
type: "tool_result",
|
|
@@ -54567,8 +55348,9 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54567
55348
|
awaitingApproval = true;
|
|
54568
55349
|
continue;
|
|
54569
55350
|
}
|
|
54570
|
-
if (toolResult.outcome === "success" &&
|
|
55351
|
+
if (toolResult.outcome === "success" && DELIVERY_TOOL_NAMES2.has(call.name)) {
|
|
54571
55352
|
telegramDelivered = true;
|
|
55353
|
+
deliveredViaToolName = call.name;
|
|
54572
55354
|
}
|
|
54573
55355
|
if (toolResult.outcome === "success") {
|
|
54574
55356
|
unresolvedToolFailures.delete(call.name);
|
|
@@ -54605,7 +55387,13 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54605
55387
|
messages = [...messages, { role: "user", content: approvalNudge }];
|
|
54606
55388
|
continue;
|
|
54607
55389
|
}
|
|
54608
|
-
|
|
55390
|
+
const suspended = await suspendForApproval();
|
|
55391
|
+
if (suspended === "resumed_inline") {
|
|
55392
|
+
awaitingApproval = false;
|
|
55393
|
+
approvalPending = false;
|
|
55394
|
+
continue;
|
|
55395
|
+
}
|
|
55396
|
+
return suspended;
|
|
54609
55397
|
}
|
|
54610
55398
|
const isToolErrorBlock = (block) => {
|
|
54611
55399
|
if (block.toolName === "return_result") return false;
|
|
@@ -54799,7 +55587,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54799
55587
|
trace("exit_completed_via_return_result", {
|
|
54800
55588
|
propagatedResultLen: propagatedResult.length
|
|
54801
55589
|
});
|
|
54802
|
-
return { status: "completed", result: propagatedResult };
|
|
55590
|
+
return { status: "completed", result: withDeliveryNotice(propagatedResult) };
|
|
54803
55591
|
}
|
|
54804
55592
|
if (toolResultBlocks.length > 0) {
|
|
54805
55593
|
messages = [...messages, { role: "tool", content: toolResultBlocks }];
|
|
@@ -54914,6 +55702,16 @@ async function runJob(jobId, deps, runnerEnv, opts) {
|
|
|
54914
55702
|
);
|
|
54915
55703
|
return { status: "failed", error: `message_structure_invalid:${err.code}` };
|
|
54916
55704
|
}
|
|
55705
|
+
if (isContextOverflowError(err)) {
|
|
55706
|
+
const win = modelContextWindow(
|
|
55707
|
+
llmClient.config.provider,
|
|
55708
|
+
llmClient.config.model,
|
|
55709
|
+
llmClient.config.contextWindow
|
|
55710
|
+
);
|
|
55711
|
+
const code = `context_window_exceeded:${llmClient.config.model} (configured ~${win} tokens \u2014 set the model's real context window in LLM providers)`;
|
|
55712
|
+
await failJob(db, jobId, code, runStats(), messages);
|
|
55713
|
+
return { status: "failed", error: code };
|
|
55714
|
+
}
|
|
54917
55715
|
if (err instanceof Error) {
|
|
54918
55716
|
const unavailableMatch = err.message.match(
|
|
54919
55717
|
/Model tried to call unavailable tool ['"`]([^'"`]+)['"`]/i
|
|
@@ -55018,6 +55816,15 @@ async function resolveApprovalDecision(deps, runnerEnv, input) {
|
|
|
55018
55816
|
const resumed = await deps.db.update(agentJobs).set({ status: "pending", updatedAt: /* @__PURE__ */ new Date() }).where(and4(eq5(agentJobs.id, jobId), eq5(agentJobs.status, "awaiting_approval"))).returning({ id: agentJobs.id });
|
|
55019
55817
|
if (resumed.length === 0) {
|
|
55020
55818
|
const [current] = await deps.db.select({ status: agentJobs.status }).from(agentJobs).where(eq5(agentJobs.id, jobId)).limit(1);
|
|
55819
|
+
if (current?.status === "processing" || current?.status === "pending") {
|
|
55820
|
+
return {
|
|
55821
|
+
ok: true,
|
|
55822
|
+
jobId,
|
|
55823
|
+
decision: input.decision,
|
|
55824
|
+
chatId: job.chatId ?? null,
|
|
55825
|
+
resumed: "in_process"
|
|
55826
|
+
};
|
|
55827
|
+
}
|
|
55021
55828
|
return { ok: false, code: "job_not_resumable", status: current?.status ?? null };
|
|
55022
55829
|
}
|
|
55023
55830
|
void triggerWorker(jobId, runnerEnv);
|
|
@@ -55025,7 +55832,8 @@ async function resolveApprovalDecision(deps, runnerEnv, input) {
|
|
|
55025
55832
|
ok: true,
|
|
55026
55833
|
jobId,
|
|
55027
55834
|
decision: input.decision,
|
|
55028
|
-
chatId: job.chatId ?? null
|
|
55835
|
+
chatId: job.chatId ?? null,
|
|
55836
|
+
resumed: "worker"
|
|
55029
55837
|
};
|
|
55030
55838
|
}
|
|
55031
55839
|
|
|
@@ -55055,7 +55863,8 @@ async function approveRoute(c, deps, runnerEnv) {
|
|
|
55055
55863
|
if (result.code === "job_not_found") return c.json({ error: "job_not_found" }, 404);
|
|
55056
55864
|
return c.json({ error: "approval_already_resolved", status: result.status }, 400);
|
|
55057
55865
|
}
|
|
55058
|
-
|
|
55866
|
+
const status = result.resumed === "in_process" ? "processing" : "pending";
|
|
55867
|
+
return c.json({ ok: true, jobId: result.jobId, status, decision }, 200);
|
|
55059
55868
|
}
|
|
55060
55869
|
|
|
55061
55870
|
// src/cron/reset-orphans.ts
|
|
@@ -55347,13 +56156,19 @@ async function executeReadyTasks(db, deps, max2 = 5) {
|
|
|
55347
56156
|
const runMemory = task.rootJobId ? await loadRunMemory(db, task.rootJobId, task.id, explicitDepIds) : "";
|
|
55348
56157
|
const taskText = buildTaskText(task, runMemory);
|
|
55349
56158
|
let childDepth = 0;
|
|
56159
|
+
let childConversationId = null;
|
|
55350
56160
|
if (task.rootJobId) {
|
|
55351
|
-
const [creatorRow] = await db.select({
|
|
56161
|
+
const [creatorRow] = await db.select({
|
|
56162
|
+
delegationDepth: agentJobs.delegationDepth,
|
|
56163
|
+
status: agentJobs.status,
|
|
56164
|
+
conversationId: agentJobs.conversationId
|
|
56165
|
+
}).from(agentJobs).where(eq5(agentJobs.id, task.rootJobId)).limit(1);
|
|
55352
56166
|
if (creatorRow && CANCELLED_OR_TERMINAL.has(creatorRow.status ?? "")) {
|
|
55353
56167
|
await db.update(agentTasks).set({ status: "cancelled", result: "root job cancelled", updatedAt: /* @__PURE__ */ new Date() }).where(eq5(agentTasks.id, task.id));
|
|
55354
56168
|
continue;
|
|
55355
56169
|
}
|
|
55356
56170
|
childDepth = (creatorRow?.delegationDepth ?? 0) + 1;
|
|
56171
|
+
childConversationId = creatorRow?.conversationId ?? null;
|
|
55357
56172
|
}
|
|
55358
56173
|
const jobRows = await db.insert(agentJobs).values({
|
|
55359
56174
|
entityId: task.entityId,
|
|
@@ -55362,6 +56177,7 @@ async function executeReadyTasks(db, deps, max2 = 5) {
|
|
|
55362
56177
|
task: taskText,
|
|
55363
56178
|
parentJobId: task.rootJobId ?? void 0,
|
|
55364
56179
|
delegationDepth: childDepth,
|
|
56180
|
+
conversationId: childConversationId ?? void 0,
|
|
55365
56181
|
status: "pending",
|
|
55366
56182
|
messages: [{ role: "user", content: taskText }]
|
|
55367
56183
|
}).returning({ id: agentJobs.id });
|
|
@@ -55592,8 +56408,15 @@ async function runScheduleTick(db, deps, max2 = 5) {
|
|
|
55592
56408
|
}
|
|
55593
56409
|
|
|
55594
56410
|
// src/cron/deliver-results.ts
|
|
56411
|
+
var TERMINAL_ROOT_STATUSES = ["cancelled", "completed", "failed"];
|
|
55595
56412
|
async function findUndeliveredRootJobIds(db) {
|
|
55596
|
-
const rows = await db.selectDistinct({ rootJobId: agentTasks.rootJobId }).from(agentTasks).innerJoin(agentJobs, eq5(agentJobs.id, agentTasks.rootJobId)).where(
|
|
56413
|
+
const rows = await db.selectDistinct({ rootJobId: agentTasks.rootJobId }).from(agentTasks).innerJoin(agentJobs, eq5(agentJobs.id, agentTasks.rootJobId)).where(
|
|
56414
|
+
and4(
|
|
56415
|
+
isNotNull(agentTasks.rootJobId),
|
|
56416
|
+
isNull(agentJobs.completedAt),
|
|
56417
|
+
notInArray(agentJobs.status, TERMINAL_ROOT_STATUSES)
|
|
56418
|
+
)
|
|
56419
|
+
);
|
|
55597
56420
|
return rows.map((r) => r.rootJobId);
|
|
55598
56421
|
}
|
|
55599
56422
|
async function deliverCompletedRoots(db) {
|
|
@@ -55614,6 +56437,7 @@ async function deliverCompletedRoots(db) {
|
|
|
55614
56437
|
const rootJob = rootJobRows[0];
|
|
55615
56438
|
if (!rootJob) continue;
|
|
55616
56439
|
if (rootJob.completedAt !== null) continue;
|
|
56440
|
+
if (TERMINAL_ROOT_STATUSES.includes(rootJob.status ?? "")) continue;
|
|
55617
56441
|
const complete = await checkRootJobComplete(rootJobId, db);
|
|
55618
56442
|
if (!complete) continue;
|
|
55619
56443
|
const taskRows = await db.select({
|
|
@@ -55636,8 +56460,10 @@ async function deliverCompletedRoots(db) {
|
|
|
55636
56460
|
}).where(
|
|
55637
56461
|
and4(
|
|
55638
56462
|
eq5(agentJobs.id, rootJobId),
|
|
55639
|
-
isNull(agentJobs.completedAt)
|
|
56463
|
+
isNull(agentJobs.completedAt),
|
|
55640
56464
|
// gate: only one tick wins
|
|
56465
|
+
notInArray(agentJobs.status, TERMINAL_ROOT_STATUSES)
|
|
56466
|
+
// gate: never resurrect a cancelled/terminal root
|
|
55641
56467
|
)
|
|
55642
56468
|
).returning({ id: agentJobs.id });
|
|
55643
56469
|
if (claimed.length === 0) {
|
|
@@ -56366,10 +57192,10 @@ async function runCronTickGuarded(deps, maxTasksPerTick = 5, maxTickMs = 30 * 6e
|
|
|
56366
57192
|
}).finally(() => {
|
|
56367
57193
|
if (watchdogFired) outstandingAbandoned--;
|
|
56368
57194
|
});
|
|
56369
|
-
const watchdog = new Promise((
|
|
57195
|
+
const watchdog = new Promise((resolve3) => {
|
|
56370
57196
|
const timer = setTimeout(() => {
|
|
56371
57197
|
watchdogFired = true;
|
|
56372
|
-
|
|
57198
|
+
resolve3("timeout");
|
|
56373
57199
|
}, maxTickMs);
|
|
56374
57200
|
if (typeof timer.unref === "function") timer.unref();
|
|
56375
57201
|
});
|
|
@@ -56593,6 +57419,10 @@ ${message}` : instruction;
|
|
|
56593
57419
|
status: "pending",
|
|
56594
57420
|
channel: "dashboard",
|
|
56595
57421
|
task: instruction,
|
|
57422
|
+
// Jobs page grouping (migration 0059): this channel already has a
|
|
57423
|
+
// real conversation entity (the dashboard sidebar thread) — stamp
|
|
57424
|
+
// that id directly rather than re-deriving it from a gap heuristic.
|
|
57425
|
+
conversationId,
|
|
56596
57426
|
messages: [{ role: "user", content: workerContent }]
|
|
56597
57427
|
}).returning({ id: agentJobs.id });
|
|
56598
57428
|
const reply = text23;
|
|
@@ -56788,6 +57618,32 @@ function startCronTicker(deps, opts = {}) {
|
|
|
56788
57618
|
import { writeFile as writeFile6, mkdir as mkdir5, readdir as readdir8, stat as stat9, unlink as unlink4 } from "node:fs/promises";
|
|
56789
57619
|
import { join as join11 } from "node:path";
|
|
56790
57620
|
import { homedir as homedir4 } from "node:os";
|
|
57621
|
+
|
|
57622
|
+
// src/job/conversation-id.ts
|
|
57623
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
57624
|
+
async function resolveConversationId(opts) {
|
|
57625
|
+
const [prior] = await opts.db.select({
|
|
57626
|
+
conversationId: agentJobs.conversationId,
|
|
57627
|
+
createdAt: agentJobs.createdAt,
|
|
57628
|
+
completedAt: agentJobs.completedAt
|
|
57629
|
+
}).from(agentJobs).where(
|
|
57630
|
+
and4(
|
|
57631
|
+
eq5(agentJobs.entityId, opts.entityId),
|
|
57632
|
+
eq5(agentJobs.agentId, opts.agentId),
|
|
57633
|
+
eq5(agentJobs.channel, opts.channel),
|
|
57634
|
+
eq5(agentJobs.chatId, opts.chatId)
|
|
57635
|
+
)
|
|
57636
|
+
).orderBy(desc(agentJobs.createdAt)).limit(1);
|
|
57637
|
+
if (prior?.conversationId) {
|
|
57638
|
+
const deliveredAt = prior.completedAt ? new Date(prior.completedAt).getTime() : new Date(prior.createdAt).getTime();
|
|
57639
|
+
if (Date.now() - deliveredAt < IDLE_RESET_MS) {
|
|
57640
|
+
return prior.conversationId;
|
|
57641
|
+
}
|
|
57642
|
+
}
|
|
57643
|
+
return randomUUID3();
|
|
57644
|
+
}
|
|
57645
|
+
|
|
57646
|
+
// src/telegram/handler.ts
|
|
56791
57647
|
async function handleTelegramUpdate(args) {
|
|
56792
57648
|
const { update, receivingAgentId, receivingAgentEntityId, receivingAgentBotUsername, tx } = args;
|
|
56793
57649
|
const message = update.message;
|
|
@@ -56889,12 +57745,20 @@ async function handleTelegramUpdate(args) {
|
|
|
56889
57745
|
if (!taskText.trim() && hasPhoto) {
|
|
56890
57746
|
taskText = "Image envoy\xE9e (sans l\xE9gende).";
|
|
56891
57747
|
}
|
|
57748
|
+
const conversationId = await resolveConversationId({
|
|
57749
|
+
db: tx,
|
|
57750
|
+
entityId: receivingAgentEntityId,
|
|
57751
|
+
agentId: targetAgentId,
|
|
57752
|
+
channel: "telegram",
|
|
57753
|
+
chatId: String(chatId)
|
|
57754
|
+
});
|
|
56892
57755
|
const [job] = await tx.insert(agentJobs).values({
|
|
56893
57756
|
entityId: receivingAgentEntityId,
|
|
56894
57757
|
agentId: targetAgentId,
|
|
56895
57758
|
channel: "telegram",
|
|
56896
57759
|
task: taskText,
|
|
56897
57760
|
chatId: String(chatId),
|
|
57761
|
+
conversationId,
|
|
56898
57762
|
status: "pending",
|
|
56899
57763
|
// Text-only at insert; when there's a photo the poller upgrades this to a
|
|
56900
57764
|
// multimodal [text + image] message after downloading the file.
|
|
@@ -57442,15 +58306,15 @@ async function sendAuthConfirmation(botToken, pending) {
|
|
|
57442
58306
|
}
|
|
57443
58307
|
function sleepWithAbort(ms, signal) {
|
|
57444
58308
|
if (signal.aborted) return Promise.resolve();
|
|
57445
|
-
return new Promise((
|
|
58309
|
+
return new Promise((resolve3) => {
|
|
57446
58310
|
const timer = setTimeout(() => {
|
|
57447
58311
|
signal.removeEventListener("abort", onAbort);
|
|
57448
|
-
|
|
58312
|
+
resolve3();
|
|
57449
58313
|
}, ms);
|
|
57450
58314
|
const onAbort = () => {
|
|
57451
58315
|
clearTimeout(timer);
|
|
57452
58316
|
signal.removeEventListener("abort", onAbort);
|
|
57453
|
-
|
|
58317
|
+
resolve3();
|
|
57454
58318
|
};
|
|
57455
58319
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
57456
58320
|
});
|