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.
Files changed (169) hide show
  1. package/cli.js +118 -2
  2. package/migrations/0058_entity_llm_keys_context_window.sql +6 -0
  3. package/migrations/0059_agent_jobs_conversation_id.sql +10 -0
  4. package/migrations/meta/_journal.json +14 -0
  5. package/package.json +1 -1
  6. package/runner.js +1435 -571
  7. package/web/.next/BUILD_ID +1 -1
  8. package/web/.next/app-path-routes-manifest.json +9 -9
  9. package/web/.next/build-manifest.json +2 -2
  10. package/web/.next/prerender-manifest.json +3 -3
  11. package/web/.next/routes-manifest.json +32 -1
  12. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
  13. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
  14. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  15. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +2 -2
  16. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
  17. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  18. package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
  19. package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
  20. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  21. package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
  22. package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
  23. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  24. package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
  25. package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
  26. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/app/(dashboard)/chat/page.js +2 -2
  28. package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
  29. package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
  30. package/web/.next/server/app/(dashboard)/connectors/page.js +1 -1
  31. package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
  32. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  33. package/web/.next/server/app/(dashboard)/credentials/page.js +1 -1
  34. package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
  35. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  36. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
  37. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
  38. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  39. package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
  40. package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
  41. package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
  42. package/web/.next/server/app/(dashboard)/learned-skills/page.js +2 -2
  43. package/web/.next/server/app/(dashboard)/learned-skills/page.js.nft.json +1 -1
  44. package/web/.next/server/app/(dashboard)/learned-skills/page_client-reference-manifest.js +1 -1
  45. package/web/.next/server/app/(dashboard)/llm-providers/page.js +1 -1
  46. package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
  47. package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
  48. package/web/.next/server/app/(dashboard)/logs/page.js +2 -2
  49. package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
  50. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  51. package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
  52. package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +1 -1
  53. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  54. package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
  55. package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
  56. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  57. package/web/.next/server/app/(dashboard)/page.js +2 -2
  58. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  59. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  60. package/web/.next/server/app/(dashboard)/settings/page.js +2 -2
  61. package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
  62. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  63. package/web/.next/server/app/(dashboard)/settings/root-context/page.js +2 -2
  64. package/web/.next/server/app/(dashboard)/settings/root-context/page.js.nft.json +1 -1
  65. package/web/.next/server/app/(dashboard)/settings/root-context/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +2 -2
  67. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
  68. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  69. package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
  70. package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
  71. package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
  72. package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
  73. package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +1 -1
  74. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  75. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  76. package/web/.next/server/app/_global-error.html +1 -1
  77. package/web/.next/server/app/_global-error.rsc +2 -2
  78. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  79. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  80. package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  81. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  82. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
  83. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  84. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  85. package/web/.next/server/app/_not-found.html +1 -1
  86. package/web/.next/server/app/_not-found.rsc +3 -3
  87. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  88. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  89. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  90. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  91. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  92. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  93. package/web/.next/server/app/api/auth/[...all]/route.js +1 -1
  94. package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
  95. package/web/.next/server/app/api/oauth/[provider]/callback/route.js.nft.json +1 -1
  96. package/web/.next/server/app/api/oauth/[provider]/start/route.js +1 -1
  97. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  98. package/web/.next/server/app/onboarding/page.js +201 -193
  99. package/web/.next/server/app/onboarding/page.js.nft.json +1 -1
  100. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  101. package/web/.next/server/app-paths-manifest.json +9 -9
  102. package/web/.next/server/chunks/2043.js +1 -0
  103. package/web/.next/server/chunks/2046.js +1 -0
  104. package/web/.next/server/chunks/2713.js +26 -18
  105. package/web/.next/server/chunks/4479.js +1 -0
  106. package/web/.next/server/chunks/4574.js +1 -1
  107. package/web/.next/server/chunks/{8810.js → 5744.js} +5 -4
  108. package/web/.next/server/chunks/{8464.js → 7388.js} +1 -2
  109. package/web/.next/server/chunks/7560.js +1 -0
  110. package/web/.next/server/middleware-build-manifest.js +1 -1
  111. package/web/.next/server/pages/404.html +1 -1
  112. package/web/.next/server/pages/500.html +1 -1
  113. package/web/.next/server/server-reference-manifest.js +1 -1
  114. package/web/.next/server/server-reference-manifest.json +1 -1
  115. package/web/.next/static/chunks/3114-d846062bc8434b58.js +1 -0
  116. package/web/.next/static/chunks/4893-730f4c62f76c6c48.js +1 -0
  117. package/web/.next/static/chunks/8097-bad68cd78aa59a10.js +1 -0
  118. package/web/.next/static/chunks/{9836-177f6ca2f15ff703.js → 9836-a69e19544b669a71.js} +1 -1
  119. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-18973089c62eb12e.js +6 -0
  120. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-7913483b3bfd7dda.js +1 -0
  121. package/web/.next/static/chunks/app/(dashboard)/agents/page-eb7b72d4d7bfde01.js +1 -0
  122. package/web/.next/static/chunks/app/(dashboard)/approvals/{page-b3a838ecbc94a030.js → page-dc8dc54ed07bc81a.js} +1 -1
  123. package/web/.next/static/chunks/app/(dashboard)/automations/page-1034613ecd525509.js +1 -0
  124. package/web/.next/static/chunks/app/(dashboard)/chat/page-4d0e2fd060f2aafb.js +1 -0
  125. package/web/.next/static/chunks/app/(dashboard)/connectors/{page-2c6f913cc6aea4b7.js → page-8bad5bc43f197471.js} +1 -1
  126. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-116a001adade3a1e.js +1 -0
  127. package/web/.next/static/chunks/app/(dashboard)/jobs/page-0fc661a4e7588bdb.js +1 -0
  128. package/web/.next/static/chunks/app/(dashboard)/layout-9068b164d3ae4853.js +1 -0
  129. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-01f1b68b334bf442.js +1 -0
  130. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-99fd3e7b631131e8.js +1 -0
  131. package/web/.next/static/chunks/app/(dashboard)/logs/page-94babd54bdd0d1af.js +1 -0
  132. package/web/.next/static/chunks/app/(dashboard)/mcp/page-2d190bdd49b7b4b4.js +1 -0
  133. package/web/.next/static/chunks/app/(dashboard)/memories/page-ac872edd26efe323.js +1 -0
  134. package/web/.next/static/chunks/app/(dashboard)/page-8cd3f1d4d072ac25.js +1 -0
  135. package/web/.next/static/chunks/app/(dashboard)/settings/page-aed8fc039205ccd7.js +1 -0
  136. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-a2eb786f9d897813.js +1 -0
  137. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-7f87e2c66d1ceea4.js +1 -0
  138. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-54734a6e0675887a.js +1 -0
  139. package/web/.next/static/chunks/app/(dashboard)/skills/page-cae26ae45b9b11e5.js +1 -0
  140. package/web/.next/static/chunks/app/onboarding/{page-f2f04878e81fdf23.js → page-e63eee720a95baac.js} +2 -2
  141. package/web/.next/static/css/{4d808b91d7b58fce.css → 289a2e89c0d093fd.css} +1 -1
  142. package/web/.next/server/chunks/3223.js +0 -1
  143. package/web/.next/server/chunks/5515.js +0 -1
  144. package/web/.next/server/chunks/6468.js +0 -1
  145. package/web/.next/server/chunks/8410.js +0 -1
  146. package/web/.next/static/chunks/4964-c6711d3d479b6085.js +0 -1
  147. package/web/.next/static/chunks/609-a0c06f3592427d58.js +0 -1
  148. package/web/.next/static/chunks/7704-7ba1c3d45662ffc8.js +0 -1
  149. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-cfce874f4acede5e.js +0 -6
  150. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-9249c2839f551ba1.js +0 -1
  151. package/web/.next/static/chunks/app/(dashboard)/agents/page-7d784d737acedccf.js +0 -1
  152. package/web/.next/static/chunks/app/(dashboard)/automations/page-b1b548de4f52696c.js +0 -1
  153. package/web/.next/static/chunks/app/(dashboard)/chat/page-bcf6dcc73b370eb0.js +0 -1
  154. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-43f033251eddcc4b.js +0 -1
  155. package/web/.next/static/chunks/app/(dashboard)/jobs/page-1f892f950bb30403.js +0 -1
  156. package/web/.next/static/chunks/app/(dashboard)/layout-e184673151ff8a05.js +0 -1
  157. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-f873bb6f8539e47e.js +0 -1
  158. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-64adda30176f4ccb.js +0 -1
  159. package/web/.next/static/chunks/app/(dashboard)/logs/page-79cfd9141c39d8ef.js +0 -1
  160. package/web/.next/static/chunks/app/(dashboard)/mcp/page-d276d7bab25286a1.js +0 -1
  161. package/web/.next/static/chunks/app/(dashboard)/memories/page-c66720494a340503.js +0 -1
  162. package/web/.next/static/chunks/app/(dashboard)/page-3649204dd27a0ad0.js +0 -1
  163. package/web/.next/static/chunks/app/(dashboard)/settings/page-1cab404d2eccfd80.js +0 -1
  164. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-b5898c5444f88c9a.js +0 -1
  165. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-82d91fc6b6acc161.js +0 -1
  166. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-58b5ed04583ca7d4.js +0 -1
  167. package/web/.next/static/chunks/app/(dashboard)/skills/page-312bd4ae8ddc8114.js +0 -1
  168. /package/web/.next/static/{SgnP_MouhUmw0bBf-lUmD → XF1rXMlGkoyAu_GI5theh}/_buildManifest.js +0 -0
  169. /package/web/.next/static/{SgnP_MouhUmw0bBf-lUmD → XF1rXMlGkoyAu_GI5theh}/_ssgManifest.js +0 -0
package/cli.js CHANGED
@@ -75,7 +75,21 @@ import { z } from "zod";
75
75
  import { homedir } from "os";
76
76
  import { join as join2 } from "path";
77
77
  import { readFileSync, writeFileSync, mkdirSync, existsSync as existsSync2, chmodSync } from "fs";
78
+ import { execFileSync } from "child_process";
78
79
  import { randomBytes } from "crypto";
80
+ function restrictFileToOwner(path) {
81
+ try {
82
+ chmodSync(path, 384);
83
+ } catch {
84
+ }
85
+ if (process.platform !== "win32") return;
86
+ const user = process.env["USERNAME"] || process.env["USER"];
87
+ if (!user) return;
88
+ try {
89
+ execFileSync("icacls", [path, "/inheritance:r", "/grant:r", `${user}:F`], { stdio: "ignore" });
90
+ } catch {
91
+ }
92
+ }
79
93
  function ensureConfigDir() {
80
94
  for (const dir of [CONFIG_DIR, PID_DIR, LOG_DIR, PG_DATA_DIR]) {
81
95
  if (!existsSync2(dir)) {
@@ -111,6 +125,7 @@ function readConfig(configFile = CONFIG_FILE) {
111
125
  function writeConfig(config, configFile = CONFIG_FILE) {
112
126
  if (configFile === CONFIG_FILE) ensureConfigDir();
113
127
  writeFileSync(configFile, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
128
+ restrictFileToOwner(configFile);
114
129
  }
115
130
  var ConfigSchema, CONFIG_DIR, CONFIG_FILE, PID_DIR, LOG_DIR, PG_DATA_DIR;
116
131
  var init_config = __esm({
@@ -11274,6 +11289,10 @@ var init_job = __esm({
11274
11289
  task: z7.string().min(1),
11275
11290
  original_task: z7.string().nullable(),
11276
11291
  chat_id: z7.string().nullable(),
11292
+ // Groups jobs from the same conversational thread (migration 0059) — see
11293
+ // apps/runner/src/job/conversation-id.ts for the stamping rule. Null for
11294
+ // non-conversational jobs (cron/schedule/webhook with no parent).
11295
+ conversation_id: z7.string().guid().nullable(),
11277
11296
  system_prompt: z7.string().nullable(),
11278
11297
  messages: z7.array(z7.record(z7.string(), z7.unknown())),
11279
11298
  tools_used: z7.array(z7.string()),
@@ -11314,6 +11333,7 @@ var init_job = __esm({
11314
11333
  status: JobStatusSchema.default("pending"),
11315
11334
  original_task: z7.string().nullable().optional(),
11316
11335
  chat_id: z7.string().nullable().optional(),
11336
+ conversation_id: z7.string().guid().nullable().optional(),
11317
11337
  system_prompt: z7.string().nullable().optional(),
11318
11338
  request_id: z7.string().nullable().optional(),
11319
11339
  parent_job_id: z7.string().guid().nullable().optional()
@@ -11321,6 +11341,38 @@ var init_job = __esm({
11321
11341
  }
11322
11342
  });
11323
11343
 
11344
+ // ../../packages/shared/src/entities/job-classification.ts
11345
+ var CHAT_DELIVERY_TOOLS, CHAT_CONTEXT_READ_TOOLS, CHAT_ALLOWED_TOOLS;
11346
+ var init_job_classification = __esm({
11347
+ "../../packages/shared/src/entities/job-classification.ts"() {
11348
+ "use strict";
11349
+ CHAT_DELIVERY_TOOLS = [
11350
+ "telegram_send_message",
11351
+ "send_image",
11352
+ "send_file",
11353
+ "send_video",
11354
+ "send_audio",
11355
+ "send_voice",
11356
+ "return_result",
11357
+ "dashboard_publish"
11358
+ ];
11359
+ CHAT_CONTEXT_READ_TOOLS = [
11360
+ "query_memory",
11361
+ "search_history",
11362
+ "skill_view",
11363
+ "list_models",
11364
+ "list_schedules",
11365
+ "file_read",
11366
+ "file_list",
11367
+ "file_search"
11368
+ ];
11369
+ CHAT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
11370
+ ...CHAT_DELIVERY_TOOLS,
11371
+ ...CHAT_CONTEXT_READ_TOOLS
11372
+ ]);
11373
+ }
11374
+ });
11375
+
11324
11376
  // ../../packages/shared/src/entities/task.ts
11325
11377
  import { z as z8 } from "zod";
11326
11378
  var TaskDescriptionSchema, TaskTitleSchema, AgentTaskSchema, AgentTaskInsertSchema;
@@ -11426,7 +11478,9 @@ var init_connector = __esm({
11426
11478
  name: z10.string().min(1).max(120),
11427
11479
  slug: z10.string().min(1).max(80).regex(/^[a-z0-9-]+$/),
11428
11480
  base_url: z10.string().nullable(),
11429
- // api_key stored encrypted in DB (pgp_sym_encrypt); raw value never returned
11481
+ // audit#2026-07-07 SEC-5: this comment used to claim pgp_sym_encrypt (pgcrypto),
11482
+ // which is not used anywhere. Real encryption is application-level AES-256-GCM
11483
+ // via @nodal-agents/secrets before the value ever reaches the DB; raw value never returned.
11430
11484
  api_key: z10.string().nullable(),
11431
11485
  active: z10.boolean(),
11432
11486
  auth_type: ConnectorAuthTypeSchema,
@@ -12256,6 +12310,28 @@ var init_timezone = __esm({
12256
12310
  }
12257
12311
  });
12258
12312
 
12313
+ // ../../packages/shared/src/redact-secrets.ts
12314
+ var init_redact_secrets = __esm({
12315
+ "../../packages/shared/src/redact-secrets.ts"() {
12316
+ "use strict";
12317
+ }
12318
+ });
12319
+
12320
+ // ../../packages/shared/src/catastrophic-command.ts
12321
+ var init_catastrophic_command = __esm({
12322
+ "../../packages/shared/src/catastrophic-command.ts"() {
12323
+ "use strict";
12324
+ }
12325
+ });
12326
+
12327
+ // ../../packages/shared/src/approval-impact.ts
12328
+ var init_approval_impact = __esm({
12329
+ "../../packages/shared/src/approval-impact.ts"() {
12330
+ "use strict";
12331
+ init_catastrophic_command();
12332
+ }
12333
+ });
12334
+
12259
12335
  // ../../packages/shared/src/index.ts
12260
12336
  var init_src2 = __esm({
12261
12337
  "../../packages/shared/src/index.ts"() {
@@ -12266,6 +12342,7 @@ var init_src2 = __esm({
12266
12342
  init_user();
12267
12343
  init_agent();
12268
12344
  init_job();
12345
+ init_job_classification();
12269
12346
  init_task();
12270
12347
  init_credential();
12271
12348
  init_connector();
@@ -12285,6 +12362,9 @@ var init_src2 = __esm({
12285
12362
  init_model_catalog();
12286
12363
  init_mcp_catalog();
12287
12364
  init_timezone();
12365
+ init_redact_secrets();
12366
+ init_approval_impact();
12367
+ init_catastrophic_command();
12288
12368
  }
12289
12369
  });
12290
12370
 
@@ -12464,6 +12544,11 @@ var init_llm_keys = __esm({
12464
12544
  apiKeyLast4: text("api_key_last4").notNull().default(""),
12465
12545
  baseUrl: text("base_url"),
12466
12546
  nickname: text("nickname"),
12547
+ // Real context window (tokens) for a custom/local model the catalog can't
12548
+ // know (É-3). Auto-detected from the endpoint at save time or set by the
12549
+ // user; NULL ⇒ fall back to the catalogued value or DEFAULT_CONTEXT_WINDOW.
12550
+ // Used window-relative to trigger compaction before the model overflows.
12551
+ contextWindow: integer("context_window"),
12467
12552
  isActive: boolean("is_active").notNull().default(true),
12468
12553
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
12469
12554
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
@@ -12619,6 +12704,18 @@ var init_jobs = __esm({
12619
12704
  task: text("task").notNull(),
12620
12705
  originalTask: text("original_task"),
12621
12706
  chatId: text("chat_id"),
12707
+ /**
12708
+ * Groups jobs from the same conversational thread for the Jobs page UI
12709
+ * (migration 0059). Purely a reporting/grouping concern — the runtime
12710
+ * execution path (delegation, cron, task-board) never reads this column.
12711
+ * NULL for non-conversational jobs (cron/schedule/webhook with no parent).
12712
+ * Stamped at creation by apps/runner/src/job/conversation-id.ts, reusing
12713
+ * the exact idle-reset session boundary thread-history.ts already uses
12714
+ * for chat continuity — see that file for the gap rule. Delegated/
12715
+ * task-board children inherit their creator's value; nothing else
12716
+ * mutates it after insert.
12717
+ */
12718
+ conversationId: uuid("conversation_id"),
12622
12719
  systemPrompt: text("system_prompt"),
12623
12720
  messages: jsonb("messages").default(sql`'[]'::jsonb`),
12624
12721
  /**
@@ -12701,6 +12798,9 @@ var init_jobs = __esm({
12701
12798
  sql`${table.createdAt} DESC`
12702
12799
  ),
12703
12800
  index("idx_agent_jobs_parent_job_id").on(table.parentJobId),
12801
+ // Migration 0059: the Jobs page groups conversational jobs by
12802
+ // (entity_id, conversation_id) — this index serves that lookup directly.
12803
+ index("idx_agent_jobs_entity_conversation").on(table.entityId, table.conversationId),
12704
12804
  // idx_jobs_parent (F-20, audit #2) was an exact duplicate of
12705
12805
  // idx_agent_jobs_parent_job_id above — same column, same order, both from
12706
12806
  // migration 0000. Dropped in migration 0054; kept this one (matches the
@@ -12844,7 +12944,10 @@ var init_connectors = __esm({
12844
12944
  name: text("name").notNull(),
12845
12945
  slug: text("slug").notNull(),
12846
12946
  baseUrl: text("base_url"),
12847
- // Encrypted with pgp_sym_encrypt in DB raw value never returned in select
12947
+ // audit#2026-07-07 SEC-5: this comment used to claim pgp_sym_encrypt (pgcrypto),
12948
+ // which is not used anywhere. Real encryption is application-level AES-256-GCM
12949
+ // via @nodal-agents/secrets before the value ever reaches this column — the DB
12950
+ // column itself just holds ciphertext as opaque text. Raw value never returned in select.
12848
12951
  apiKey: text("api_key"),
12849
12952
  active: boolean("active").default(true),
12850
12953
  authType: text("auth_type").notNull().default("api_key"),
@@ -13804,6 +13907,7 @@ var init_transaction = __esm({
13804
13907
  // ../../packages/secrets/src/index.ts
13805
13908
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
13806
13909
  import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
13910
+ import { execFileSync as execFileSync2 } from "child_process";
13807
13911
  import { homedir as homedir2 } from "os";
13808
13912
  import { dirname as dirname3, join as join5 } from "path";
13809
13913
  function masterKeyPath() {
@@ -13843,6 +13947,16 @@ async function assertMasterKeyRestorable(db, options = {}) {
13843
13947
  const llmKeyRows = await db.select({ apiKey: entityLlmKeys.apiKey }).from(entityLlmKeys);
13844
13948
  const hasEncryptedLlmKey = llmKeyRows.some((r) => r.apiKey !== "" && isEncrypted(r.apiKey));
13845
13949
  if (hasEncryptedLlmKey) throw missingKeyError("a stored LLM API key");
13950
+ const connectorRows = await db.select({ apiKey: connectors.apiKey }).from(connectors);
13951
+ const hasEncryptedConnector = connectorRows.some((r) => !!r.apiKey && isEncrypted(r.apiKey));
13952
+ if (hasEncryptedConnector) throw missingKeyError("a stored connector API key");
13953
+ const mcpRows = await db.select({ apiKey: mcpServers.apiKey, envVars: mcpServers.envVars }).from(mcpServers);
13954
+ const hasEncryptedMcp = mcpRows.some((r) => {
13955
+ if (r.apiKey && isEncrypted(r.apiKey)) return true;
13956
+ const env = r.envVars ?? {};
13957
+ return Object.values(env).some((v) => typeof v === "string" && isEncrypted(v));
13958
+ });
13959
+ if (hasEncryptedMcp) throw missingKeyError("a stored MCP server secret");
13846
13960
  }
13847
13961
  function missingKeyError(what) {
13848
13962
  return new Error(
@@ -13854,6 +13968,8 @@ var init_master_key_guard = __esm({
13854
13968
  "use strict";
13855
13969
  init_credentials();
13856
13970
  init_llm_keys();
13971
+ init_connectors();
13972
+ init_mcp();
13857
13973
  init_src3();
13858
13974
  }
13859
13975
  });
@@ -0,0 +1,6 @@
1
+ -- É-3 (audit sécu 2026-07-07): per-model context window for custom/local models
2
+ -- the catalog can't know. NULL ⇒ fall back to the catalogued value or
3
+ -- DEFAULT_CONTEXT_WINDOW. Used window-relative to trigger conversation
4
+ -- compaction before the model overflows (a too-large default silently killed
5
+ -- local 8-16K models mid-conversation).
6
+ ALTER TABLE "entity_llm_keys" ADD COLUMN "context_window" integer;
@@ -0,0 +1,10 @@
1
+ -- Conversation grouping (Jobs page): a conversational thread (Telegram DM,
2
+ -- dashboard chat escalation, delegated/task-board child) shares one
3
+ -- conversation_id across the jobs it produced, so the UI can collapse a
4
+ -- 10-message chat into one row instead of 10 identical-looking job rows.
5
+ -- Purely additive/reporting — the runner's execution path never reads this
6
+ -- column. NULL for non-conversational jobs (cron/schedule/webhook with no
7
+ -- parent). See apps/runner/src/job/conversation-id.ts for the stamping rule.
8
+ ALTER TABLE "agent_jobs" ADD COLUMN "conversation_id" uuid;
9
+ --> statement-breakpoint
10
+ CREATE INDEX "idx_agent_jobs_entity_conversation" ON "agent_jobs" ("entity_id","conversation_id");
@@ -400,6 +400,20 @@
400
400
  "when": 1783700000000,
401
401
  "tag": "0057_telegram_allowed_chats",
402
402
  "breakpoints": true
403
+ },
404
+ {
405
+ "idx": 58,
406
+ "version": "7",
407
+ "when": 1783800000000,
408
+ "tag": "0058_entity_llm_keys_context_window",
409
+ "breakpoints": true
410
+ },
411
+ {
412
+ "idx": 59,
413
+ "version": "7",
414
+ "when": 1783900000000,
415
+ "tag": "0059_agent_jobs_conversation_id",
416
+ "breakpoints": true
403
417
  }
404
418
  ]
405
419
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodal-agents",
3
- "version": "0.7.2",
3
+ "version": "0.7.5",
4
4
  "description": "Local-first AI agent platform with a web dashboard — install in one command.",
5
5
  "license": "MIT",
6
6
  "repository": {