nodal-agents 0.8.6 → 0.8.8

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 (209) hide show
  1. package/README.md +1 -1
  2. package/cli.js +308 -37
  3. package/migrations/0082_entities_auto_run_paused.sql +32 -0
  4. package/migrations/0083_code_project_archives.sql +14 -0
  5. package/migrations/0084_release_wrong_auto_run_pause.sql +24 -0
  6. package/migrations/0085_workspaces_is_dev_folder.sql +33 -0
  7. package/migrations/0086_code_projects_rename_and_hide.sql +61 -0
  8. package/migrations/0087_workspaces_hidden_from_code.sql +44 -0
  9. package/migrations/meta/_journal.json +42 -0
  10. package/package.json +1 -1
  11. package/runner.js +3388 -1801
  12. package/web/.next/BUILD_ID +1 -1
  13. package/web/.next/app-path-routes-manifest.json +12 -12
  14. package/web/.next/build-manifest.json +2 -2
  15. package/web/.next/prerender-manifest.json +3 -3
  16. package/web/.next/server/app/(dashboard)/agents/[id]/channels/page.js +1 -1
  17. package/web/.next/server/app/(dashboard)/agents/[id]/channels/page_client-reference-manifest.js +1 -1
  18. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +4 -4
  19. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  20. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +1 -1
  21. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  22. package/web/.next/server/app/(dashboard)/agents/page.js +1 -1
  23. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  24. package/web/.next/server/app/(dashboard)/approvals/page.js +1 -1
  25. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  26. package/web/.next/server/app/(dashboard)/automations/page.js +1 -1
  27. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  28. package/web/.next/server/app/(dashboard)/chat/page.js +1 -1
  29. package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
  30. package/web/.next/server/app/(dashboard)/code/[id]/page.js +1 -1
  31. package/web/.next/server/app/(dashboard)/code/[id]/page_client-reference-manifest.js +1 -1
  32. package/web/.next/server/app/(dashboard)/code/page.js +1 -1
  33. package/web/.next/server/app/(dashboard)/code/page_client-reference-manifest.js +1 -1
  34. package/web/.next/server/app/(dashboard)/connectors/page.js +1 -1
  35. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  36. package/web/.next/server/app/(dashboard)/credentials/page.js +1 -1
  37. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  38. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +1 -1
  39. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/jobs/page.js +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 +1 -1
  43. package/web/.next/server/app/(dashboard)/learned-skills/page_client-reference-manifest.js +1 -1
  44. package/web/.next/server/app/(dashboard)/llm-providers/page.js +1 -1
  45. package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/logs/page.js +1 -1
  47. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  48. package/web/.next/server/app/(dashboard)/mcp/page.js +1 -1
  49. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  50. package/web/.next/server/app/(dashboard)/memories/page.js +1 -1
  51. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/(dashboard)/page.js +3 -3
  53. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  54. package/web/.next/server/app/(dashboard)/settings/page.js +1 -1
  55. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  56. package/web/.next/server/app/(dashboard)/settings/root-context/page.js +1 -1
  57. package/web/.next/server/app/(dashboard)/settings/root-context/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +1 -1
  59. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  60. package/web/.next/server/app/(dashboard)/skills/new/page.js +1 -1
  61. package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
  62. package/web/.next/server/app/(dashboard)/skills/page.js +1 -1
  63. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/_global-error/page.js +1 -1
  65. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/_global-error.html +1 -1
  67. package/web/.next/server/app/_global-error.rsc +3 -3
  68. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  69. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
  70. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  71. package/web/.next/server/app/_not-found/page.js +1 -1
  72. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  73. package/web/.next/server/app/_not-found.html +1 -1
  74. package/web/.next/server/app/_not-found.rsc +4 -4
  75. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  76. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +4 -4
  77. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  78. package/web/.next/server/app/api/auth/[...all]/route.js +1 -1
  79. package/web/.next/server/app/api/health/route.js +1 -1
  80. package/web/.next/server/app/login/page.js +1 -1
  81. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  82. package/web/.next/server/app/onboarding/page.js +2 -2
  83. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  84. package/web/.next/server/app-paths-manifest.json +12 -12
  85. package/web/.next/server/chunks/1126.js +1 -1
  86. package/web/.next/server/chunks/1721.js +1749 -0
  87. package/web/.next/server/chunks/1824.js +1 -0
  88. package/web/.next/server/chunks/196.js +1 -0
  89. package/web/.next/server/chunks/{5149.js → 2009.js} +1 -1
  90. package/web/.next/server/chunks/210.js +1 -0
  91. package/web/.next/server/chunks/3927.js +1597 -0
  92. package/web/.next/server/chunks/42.js +2 -2
  93. package/web/.next/server/chunks/5384.js +1 -0
  94. package/web/.next/server/chunks/6484.js +1629 -0
  95. package/web/.next/server/chunks/7048.js +1 -0
  96. package/web/.next/server/chunks/7741.js +1 -1
  97. package/web/.next/server/chunks/7915.js +1 -0
  98. package/web/.next/server/chunks/{5877.js → 8191.js} +71 -89
  99. package/web/.next/server/chunks/8447.js +1 -1
  100. package/web/.next/server/chunks/9949.js +1746 -0
  101. package/web/.next/server/middleware-build-manifest.js +1 -1
  102. package/web/.next/server/middleware.js +4 -4
  103. package/web/.next/server/pages/404.html +1 -1
  104. package/web/.next/server/pages/500.html +1 -1
  105. package/web/.next/server/server-reference-manifest.js +1 -1
  106. package/web/.next/server/server-reference-manifest.json +1 -1
  107. package/web/.next/server/webpack-runtime.js +1 -1
  108. package/web/.next/static/chunks/3451-58a5c57e79371ae4.js +1629 -0
  109. package/web/.next/static/chunks/5034-e249414dc457d97d.js +1 -0
  110. package/web/.next/static/chunks/6646-35d756ba2c002ecf.js +1 -0
  111. package/web/.next/static/chunks/6967-3b77488c4b708136.js +1 -0
  112. package/web/.next/static/chunks/8423-57a00fc00778c1e1.js +1 -0
  113. package/web/.next/static/chunks/{9416-baf32aa3deceb0ed.js → 9416-391f60c712340297.js} +1 -1
  114. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-dd6460b3a050ddf7.js +6 -0
  115. package/web/.next/static/chunks/app/(dashboard)/agents/page-04ac243f09f1f620.js +1 -0
  116. package/web/.next/static/chunks/app/(dashboard)/approvals/page-dfd4cf86827c0aed.js +1 -0
  117. package/web/.next/static/chunks/app/(dashboard)/automations/page-0e724dfa1716a937.js +1 -0
  118. package/web/.next/static/chunks/app/(dashboard)/chat/page-041eedb2e6cf29fb.js +1 -0
  119. package/web/.next/static/chunks/app/(dashboard)/code/[id]/page-bed8be1148fb7d1d.js +1 -0
  120. package/web/.next/static/chunks/app/(dashboard)/code/page-ac3a349d07d95fd9.js +1 -0
  121. package/web/.next/static/chunks/app/(dashboard)/connectors/{page-a67da30a55296b54.js → page-0f39f3e59541badf.js} +1 -1
  122. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-1719a805a2e39ccc.js +1 -0
  123. package/web/.next/static/chunks/app/(dashboard)/jobs/page-129c4cd41458438a.js +1 -0
  124. package/web/.next/static/chunks/app/(dashboard)/layout-1602118a43db0d42.js +1 -0
  125. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-16a376c609001dd2.js +1 -0
  126. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-7bd5a6f909d8a136.js +1 -0
  127. package/web/.next/static/chunks/app/(dashboard)/logs/page-a2d32cf8ffa4d4a6.js +1 -0
  128. package/web/.next/static/chunks/app/(dashboard)/mcp/page-86fac62ecfc87b98.js +1 -0
  129. package/web/.next/static/chunks/app/(dashboard)/memories/page-bd61659f6bbccb21.js +1 -0
  130. package/web/.next/static/chunks/app/(dashboard)/{page-1e05fa622609cdf1.js → page-70ea130d3de66e30.js} +1 -1
  131. package/web/.next/static/chunks/app/(dashboard)/settings/page-2fec30e99b307637.js +1 -0
  132. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-5a7dae2d7e9ce07f.js +1 -0
  133. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-c22bde781a44b751.js +1 -0
  134. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-0e8b89dc355078a8.js +1 -0
  135. package/web/.next/static/chunks/app/(dashboard)/skills/page-93884e1ef158297f.js +1 -0
  136. package/web/.next/static/chunks/app/error-3d9af79e7856dcc8.js +1 -0
  137. package/web/.next/static/chunks/app/login/page-7303a5eca7eeb19b.js +1 -0
  138. package/web/.next/static/chunks/app/onboarding/{page-f02705f7710caa08.js → page-0ec8d988ee84bdb1.js} +2 -2
  139. package/web/.next/static/css/be2ece2e5b18d112.css +3 -0
  140. package/web/.next/server/app/(dashboard)/agents/[id]/channels/page.js.nft.json +0 -1
  141. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +0 -1
  142. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +0 -1
  143. package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +0 -1
  144. package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +0 -1
  145. package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +0 -1
  146. package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +0 -1
  147. package/web/.next/server/app/(dashboard)/code/[id]/page.js.nft.json +0 -1
  148. package/web/.next/server/app/(dashboard)/code/page.js.nft.json +0 -1
  149. package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +0 -1
  150. package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +0 -1
  151. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +0 -1
  152. package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +0 -1
  153. package/web/.next/server/app/(dashboard)/learned-skills/page.js.nft.json +0 -1
  154. package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +0 -1
  155. package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +0 -1
  156. package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +0 -1
  157. package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +0 -1
  158. package/web/.next/server/app/(dashboard)/page.js.nft.json +0 -1
  159. package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +0 -1
  160. package/web/.next/server/app/(dashboard)/settings/root-context/page.js.nft.json +0 -1
  161. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +0 -1
  162. package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +0 -1
  163. package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +0 -1
  164. package/web/.next/server/app/_global-error/page.js.nft.json +0 -1
  165. package/web/.next/server/app/_not-found/page.js.nft.json +0 -1
  166. package/web/.next/server/app/api/auth/[...all]/route.js.nft.json +0 -1
  167. package/web/.next/server/app/api/health/route.js.nft.json +0 -1
  168. package/web/.next/server/app/api/oauth/[provider]/callback/route.js.nft.json +0 -1
  169. package/web/.next/server/app/api/oauth/[provider]/start/route.js.nft.json +0 -1
  170. package/web/.next/server/app/auth/callback/route.js.nft.json +0 -1
  171. package/web/.next/server/app/login/page.js.nft.json +0 -1
  172. package/web/.next/server/app/onboarding/page.js.nft.json +0 -1
  173. package/web/.next/server/chunks/2745.js +0 -1
  174. package/web/.next/server/chunks/2938.js +0 -1
  175. package/web/.next/server/chunks/4630.js +0 -1
  176. package/web/.next/server/chunks/4639.js +0 -1744
  177. package/web/.next/server/chunks/4711.js +0 -1
  178. package/web/.next/server/chunks/4924.js +0 -1741
  179. package/web/.next/server/chunks/6680.js +0 -1615
  180. package/web/.next/server/chunks/9729.js +0 -1
  181. package/web/.next/server/middleware.js.nft.json +0 -1
  182. package/web/.next/static/chunks/4994-7effa935c5703218.js +0 -1
  183. package/web/.next/static/chunks/5034-7b0857c231a2db7e.js +0 -1
  184. package/web/.next/static/chunks/8423-bf78a9de790d8ebc.js +0 -1
  185. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-f55badb51d6e10b1.js +0 -6
  186. package/web/.next/static/chunks/app/(dashboard)/agents/page-d256dbe832dfb238.js +0 -1
  187. package/web/.next/static/chunks/app/(dashboard)/approvals/page-99ec2b36c44ed3d3.js +0 -1
  188. package/web/.next/static/chunks/app/(dashboard)/automations/page-9242e9b754d98c68.js +0 -1
  189. package/web/.next/static/chunks/app/(dashboard)/chat/page-7f9c147e49730cb4.js +0 -1
  190. package/web/.next/static/chunks/app/(dashboard)/code/[id]/page-46acd3da2c78b6df.js +0 -1
  191. package/web/.next/static/chunks/app/(dashboard)/code/page-12598290e23cb30a.js +0 -1
  192. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-793efbdc85566e24.js +0 -1
  193. package/web/.next/static/chunks/app/(dashboard)/jobs/page-cce723799dad2458.js +0 -1
  194. package/web/.next/static/chunks/app/(dashboard)/layout-5976fd8a05f7cefd.js +0 -1
  195. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-0f3b9332eeaf4282.js +0 -1
  196. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-4e0fef0a652f4e8a.js +0 -1
  197. package/web/.next/static/chunks/app/(dashboard)/logs/page-31b25388df6e0212.js +0 -1
  198. package/web/.next/static/chunks/app/(dashboard)/mcp/page-2364c615affa47d3.js +0 -1
  199. package/web/.next/static/chunks/app/(dashboard)/memories/page-52944e872698b85f.js +0 -1
  200. package/web/.next/static/chunks/app/(dashboard)/settings/page-19ac2c3c794fa0f7.js +0 -1
  201. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-cb20f211b06e807f.js +0 -1
  202. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-5bc99166041fcf0d.js +0 -1
  203. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-453b7333a310658c.js +0 -1
  204. package/web/.next/static/chunks/app/(dashboard)/skills/page-055a17f39f670958.js +0 -1
  205. package/web/.next/static/chunks/app/error-d7d2badaa59bb441.js +0 -1
  206. package/web/.next/static/chunks/app/login/page-02aa17b387bdb416.js +0 -1
  207. package/web/.next/static/css/f0904e0d2f4e1435.css +0 -3
  208. /package/web/.next/static/{PMZOOK9t9OB35ORr4mOpd → Ox1nSNbTz9Sz93pV6rEcD}/_buildManifest.js +0 -0
  209. /package/web/.next/static/{PMZOOK9t9OB35ORr4mOpd → Ox1nSNbTz9Sz93pV6rEcD}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -46,7 +46,7 @@ Every agent runs on the model **you** choose, with the tools **you** grant, in a
46
46
  ### 🏠 Own your whole stack
47
47
 
48
48
  - **Local-first** — single command, embedded Postgres, zero cloud dependency.
49
- - **Your data & keys** — conversations, memory, and credentials never leave your machine; API keys are AES-256-GCM encrypted at rest.
49
+ - **Your data & keys** — conversations, memory, and credentials never leave your machine, and every credential is AES-256-GCM encrypted at rest: API keys, OAuth tokens, MCP server secrets, the bot tokens of every messaging channel, and your webhook secrets.
50
50
  - **Workspaces** — multiple isolated workspaces on one install (personal vs work), each with its own agents, skills, connectors, jobs, and memory.
51
51
  - **No markup** — bring your own API keys; you pay the provider directly, nothing on top.
52
52
 
package/cli.js CHANGED
@@ -13085,6 +13085,33 @@ var init_model_catalog = __esm({
13085
13085
  contextWindow: 1048576,
13086
13086
  pricing: { inputPerMillionUsd: 1.4, outputPerMillionUsd: 4.4 }
13087
13087
  },
13088
+ {
13089
+ modelId: "z-ai/glm-5.3-flash",
13090
+ label: "GLM 5.3 Flash",
13091
+ // The cheap, fast sibling of 5.3 — ~19x cheaper in, ~18x cheaper out,
13092
+ // and a LARGER context (1.31M vs 1.05M). Two real differences from the
13093
+ // full 5.3, both read off OpenRouter's /api/v1/models on 2026-08-28:
13094
+ // - it is MULTIMODAL (input_modalities: text, image, video) where 5.3
13095
+ // is text-only, so it is in VISION_MODEL_IDS below;
13096
+ // - reasoning is supported (`reasoning`, `reasoning_effort` are in
13097
+ // supported_parameters) but NOT mandatory the way 5.2/5.3's is —
13098
+ // nothing upstream says this one always thinks, so 'off' stays on
13099
+ // the UI scale rather than being hidden on a guess.
13100
+ // forcedToolChoice:false follows the family posture: `tool_choice` is
13101
+ // accepted, but nothing states 'required' is honoured, and the runtime
13102
+ // tool_choice floor relaxes it anyway.
13103
+ capabilities: {
13104
+ tools: true,
13105
+ forcedToolChoice: false,
13106
+ reasoning: true,
13107
+ reasoningControl: {
13108
+ kind: "effort",
13109
+ levels: ["low", "medium", "high", "max"]
13110
+ }
13111
+ },
13112
+ contextWindow: 1310720,
13113
+ pricing: { inputPerMillionUsd: 0.075, outputPerMillionUsd: 0.25 }
13114
+ },
13088
13115
  // Moonshot (Kimi)
13089
13116
  {
13090
13117
  modelId: "moonshotai/kimi-k2.6",
@@ -13242,6 +13269,10 @@ var init_model_catalog = __esm({
13242
13269
  "openai/gpt-5.6-luna-pro",
13243
13270
  "openai/gpt-5.6-terra-pro",
13244
13271
  "qwen/qwen3.8-max",
13272
+ // Verified via OpenRouter /api/v1/models input_modalities on 2026-08-28:
13273
+ // z-ai/glm-5.3-flash reports ["text","image","video"]. The full z-ai/glm-5.3
13274
+ // reports ["text"] only and is deliberately NOT in this set.
13275
+ "z-ai/glm-5.3-flash",
13245
13276
  // Verified via OpenRouter /api/v1/models input_modalities (2026-07-20).
13246
13277
  // qwen/qwen3.7-max is deliberately absent — text-only per the same source.
13247
13278
  "x-ai/grok-4.5",
@@ -13500,11 +13531,19 @@ var init_entities = __esm({
13500
13531
  // Controls whether agent-authored skills are auto-assigned to the authoring
13501
13532
  // agent ('auto') or queued for the entity owner to approve ('approval').
13502
13533
  skillAssignmentMode: text("skill_assignment_mode").notNull().default("approval").$type(),
13503
- // Allows the workspace OWNER to opt into Yolo (auto-approve run_command)
13504
- // even in non-local-trust (LAN/local-auth) mode. Off by default: in
13505
- // local-auth mode, commands always require approval unless the owner
13506
- // explicitly opts in here.
13507
- lanCommandYolo: boolean("lan_command_yolo").notNull().default(false),
13534
+ /**
13535
+ * Frein d'urgence de l'auto-exécution (0082, inversion du modèle à deux
13536
+ * clés décision Quentin 24/08). true = TOUT l'auto-run de code du
13537
+ * workspace est en pause : les règles auto_approve des outils d'exécution
13538
+ * (run_command, code_task, scripts, MCP stdio) deviennent dormantes sans
13539
+ * être supprimées — le runner les déshabille à l'exécution. false
13540
+ * (défaut) = les toggles Yolo par agent (owner-only) s'appliquent tels
13541
+ * quels. Remplace lan_command_yolo, qui était une PRÉ-CONDITION
13542
+ * d'activation : redondante (les toggles par agent étaient déjà
13543
+ * owner-only) et pénible — sa vraie valeur était le coupe-circuit, qui
13544
+ * devient son seul rôle.
13545
+ */
13546
+ autoRunPaused: boolean("auto_run_paused").notNull().default(false),
13508
13547
  /**
13509
13548
  * Interrupteur maitre du serveur MCP (PR C, decision Quentin 23/08).
13510
13549
  * Defaut FERME : un point d'entree externe qui cree des jobs doit etre
@@ -13654,12 +13693,16 @@ var init_agents = __esm({
13654
13693
  // action layer guarantees at least one provider stays enabled.
13655
13694
  cliDefaults: jsonb("cli_defaults").$type(),
13656
13695
  // Which harness drives this agent (étape E). 'nodal' (default) = the
13657
- // Nodal runner loop. 'claude-code' = the agent IS a Claude Code session:
13658
- // persona from DB via --append-system-prompt, channels/cron/workspaces/
13659
- // budget/approvals stay Nodal's, but the LOOP, tools and context are the
13660
- // CLI's — said as-is to the user (dispatcher, not brain). 'codex' is
13661
- // reserved (fails loud runtime_not_supported until implemented). A DATA
13662
- // field, never a hardcoded per-agent branch (invariant #1).
13696
+ // Nodal runner loop. 'claude-code' and 'codex' = the agent IS a session of
13697
+ // that CLI: persona from DB, channels/cron/workspaces/budget/approvals stay
13698
+ // Nodal's, but the LOOP, tools and context are the CLI's — said as-is to
13699
+ // the user (dispatcher, not brain). A DATA field, never a hardcoded
13700
+ // per-agent branch (invariant #1).
13701
+ //
13702
+ // 'codex' a été RÉSERVÉ ici du 19/08 au 27/08 : accepté par la contrainte,
13703
+ // refusé par le Zod de l'action et par le runner. Ouvert le 27/08 —
13704
+ // apps/runner/src/cli-runtime/provider.ts porte désormais LE tableau des
13705
+ // runtimes servis ; cette contrainte et lui doivent rester d'accord.
13663
13706
  runtime: text("runtime").default("nodal").notNull(),
13664
13707
  // Runtime-agent permission posture, as DATA: mode 'read' (default when
13665
13708
  // NULL) hides the CLI's write tools; 'write' allows workspace edits
@@ -14621,6 +14664,35 @@ var init_mcp = __esm({
14621
14664
  }
14622
14665
  });
14623
14666
 
14667
+ // ../../packages/db/src/schema/code-projects.ts
14668
+ var codeProjects;
14669
+ var init_code_projects = __esm({
14670
+ "../../packages/db/src/schema/code-projects.ts"() {
14671
+ "use strict";
14672
+ init_pg_core();
14673
+ init_entities();
14674
+ codeProjects = pgTable(
14675
+ "code_projects",
14676
+ {
14677
+ id: uuid("id").primaryKey().defaultRandom(),
14678
+ entityId: uuid("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
14679
+ /** Chemin absolu slash-normalisé du projet (la clé de groupement dérivée). */
14680
+ projectPath: text("project_path").notNull(),
14681
+ /** Nom choisi par le propriétaire. NULL = nom du dossier. */
14682
+ displayName: text("display_name"),
14683
+ /** Masqué de la liste ET du contexte injecté aux agents. */
14684
+ hidden: boolean("hidden").notNull().default(false),
14685
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
14686
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
14687
+ },
14688
+ (table) => [
14689
+ unique("code_projects_entity_path_unique").on(table.entityId, table.projectPath),
14690
+ index("idx_code_projects_entity").on(table.entityId)
14691
+ ]
14692
+ );
14693
+ }
14694
+ });
14695
+
14624
14696
  // ../../packages/db/src/schema/auth.ts
14625
14697
  var sessions, accounts, verifications;
14626
14698
  var init_auth = __esm({
@@ -14718,6 +14790,28 @@ var init_agent_workspaces = __esm({
14718
14790
  path: text("path").notNull(),
14719
14791
  // UI display order; lower = appears first. Default 0.
14720
14792
  position: integer("position").default(0).notNull(),
14793
+ /**
14794
+ * Ticked by the owner: "nothing written here is a project of mine."
14795
+ *
14796
+ * NOT the return of `is_dev_folder` (0085, dropped by 0086). That one asked
14797
+ * which folders to INCLUDE, so nothing showed until you ticked, a folder
14798
+ * you forgot lost real work in silence, and you still had to guess whether
14799
+ * the ticked folder WAS a project or CONTAINED projects.
14800
+ *
14801
+ * This one only ever removes, and it removes a whole subtree — so the
14802
+ * "is it a project?" question never arises. Everything shows by default,
14803
+ * which keeps the honest failure mode: visible noise you clear, rather than
14804
+ * missing work nothing tells you about. See 0087.
14805
+ *
14806
+ * The folder stays a full workspace: the agent reads and writes there as
14807
+ * before, and its LABEL is still used to resolve relative paths. Only the
14808
+ * project view ignores it — the Code tab and the injected `## Runtime`
14809
+ * block alike.
14810
+ *
14811
+ * A PATH counts as hidden when at least one of its rows is ticked: the same
14812
+ * folder attached to five agents is one gesture, not five.
14813
+ */
14814
+ hiddenFromCode: boolean("hidden_from_code").default(false).notNull(),
14721
14815
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
14722
14816
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
14723
14817
  },
@@ -15158,6 +15252,7 @@ __export(schema_exports, {
15158
15252
  chatMessages: () => chatMessages,
15159
15253
  cliRuns: () => cliRuns,
15160
15254
  cliSessions: () => cliSessions,
15255
+ codeProjects: () => codeProjects,
15161
15256
  connectors: () => connectors,
15162
15257
  conversations: () => conversations,
15163
15258
  credentials: () => credentials,
@@ -15198,6 +15293,7 @@ var init_schema2 = __esm({
15198
15293
  init_schedules();
15199
15294
  init_llm_keys();
15200
15295
  init_mcp();
15296
+ init_code_projects();
15201
15297
  init_auth();
15202
15298
  init_agent_connector_assignments();
15203
15299
  init_agent_workspaces();
@@ -15539,6 +15635,17 @@ async function assertMasterKeyRestorable(db, options = {}) {
15539
15635
  return Object.values(env).some((v) => typeof v === "string" && isEncrypted(v));
15540
15636
  });
15541
15637
  if (hasEncryptedMcp) throw missingKeyError("a stored MCP server secret");
15638
+ const bindingRows = await db.select({ credentials: channelBindings.credentials }).from(channelBindings);
15639
+ const hasEncryptedBinding = bindingRows.some(
15640
+ (r) => !!r.credentials && isEncrypted(r.credentials)
15641
+ );
15642
+ if (hasEncryptedBinding) throw missingKeyError("a stored channel credential");
15643
+ const agentRows = await db.select({ botToken: agents.telegramBotToken }).from(agents);
15644
+ const hasEncryptedBotToken = agentRows.some((r) => !!r.botToken && isEncrypted(r.botToken));
15645
+ if (hasEncryptedBotToken) throw missingKeyError("a stored channel credential (bot token)");
15646
+ const webhookRows = await db.select({ secret: webhookTriggers.secret }).from(webhookTriggers);
15647
+ const hasEncryptedWebhookSecret = webhookRows.some((r) => !!r.secret && isEncrypted(r.secret));
15648
+ if (hasEncryptedWebhookSecret) throw missingKeyError("a stored webhook secret");
15542
15649
  }
15543
15650
  function missingKeyError(what) {
15544
15651
  return new Error(
@@ -15552,11 +15659,15 @@ var init_master_key_guard = __esm({
15552
15659
  init_llm_keys();
15553
15660
  init_connectors();
15554
15661
  init_mcp();
15662
+ init_channel_bindings();
15663
+ init_agents();
15664
+ init_webhooks();
15555
15665
  init_src3();
15556
15666
  }
15557
15667
  });
15558
15668
 
15559
15669
  // ../../packages/db/src/queries/channel-identity.ts
15670
+ import { createHash } from "crypto";
15560
15671
  async function resolveOwnerConversation(db, agentId, channel) {
15561
15672
  if (channel === "telegram") {
15562
15673
  const [row2] = await db.select({ chatId: telegramAllowedChats.chatId }).from(telegramAllowedChats).where(
@@ -15612,15 +15723,44 @@ async function getChannelBinding(db, agentId, channel) {
15612
15723
  async function listChannelBindings(db, agentId) {
15613
15724
  return db.select().from(channelBindings).where(eq(channelBindings.agentId, agentId));
15614
15725
  }
15726
+ function decryptChannelSecret(raw, what) {
15727
+ if (!isEncrypted(raw)) return raw;
15728
+ try {
15729
+ return decrypt(raw);
15730
+ } catch (err) {
15731
+ throw new Error(
15732
+ `failed to decrypt ${what}: ${err instanceof Error ? err.message : String(err)}. The master key at ~/.nodalai/secrets.key does not match the one this row was written with (restored backup? copied database?). Re-enter the credential from the dashboard to re-encrypt it under the current key.`
15733
+ );
15734
+ }
15735
+ }
15736
+ function encryptChannelCredentials(creds) {
15737
+ return encrypt(JSON.stringify(creds));
15738
+ }
15739
+ function encryptChannelSecret(plaintext) {
15740
+ return isEncrypted(plaintext) ? plaintext : encrypt(plaintext);
15741
+ }
15742
+ function credentialsFingerprint(creds) {
15743
+ const canonical = JSON.stringify(
15744
+ Object.keys(creds).sort().map((k) => [k, creds[k]])
15745
+ );
15746
+ return createHash("sha256").update(canonical).digest("hex");
15747
+ }
15615
15748
  async function getBindingCredentials(db, agentId, channel) {
15616
15749
  if (channel === "telegram") {
15617
15750
  const [row] = await db.select({ botToken: agents.telegramBotToken }).from(agents).where(eq(agents.id, agentId)).limit(1);
15618
- return row?.botToken ? { botToken: row.botToken } : null;
15751
+ if (!row?.botToken) return null;
15752
+ return {
15753
+ botToken: decryptChannelSecret(row.botToken, `telegram bot token (agent ${agentId})`)
15754
+ };
15619
15755
  }
15620
15756
  const binding = await getChannelBinding(db, agentId, channel);
15621
15757
  if (!binding) return null;
15758
+ const json2 = decryptChannelSecret(
15759
+ binding.credentials,
15760
+ `${channel} credentials (agent ${agentId})`
15761
+ );
15622
15762
  try {
15623
- const parsed = JSON.parse(binding.credentials);
15763
+ const parsed = JSON.parse(json2);
15624
15764
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
15625
15765
  return parsed;
15626
15766
  } catch {
@@ -15673,6 +15813,7 @@ var init_channel_identity = __esm({
15673
15813
  "../../packages/db/src/queries/channel-identity.ts"() {
15674
15814
  "use strict";
15675
15815
  init_drizzle_orm();
15816
+ init_src3();
15676
15817
  init_telegram_allowed_chats();
15677
15818
  init_channel_bindings();
15678
15819
  init_channel_allowed_conversations();
@@ -16140,6 +16281,7 @@ __export(src_exports, {
16140
16281
  chatMessages: () => chatMessages,
16141
16282
  cliRuns: () => cliRuns,
16142
16283
  cliSessions: () => cliSessions,
16284
+ codeProjects: () => codeProjects,
16143
16285
  connectors: () => connectors,
16144
16286
  conversations: () => conversations,
16145
16287
  count: () => count,
@@ -16148,8 +16290,12 @@ __export(src_exports, {
16148
16290
  createClient: () => createClient,
16149
16291
  createSkillRepo: () => createSkillRepo,
16150
16292
  credentials: () => credentials,
16293
+ credentialsFingerprint: () => credentialsFingerprint,
16294
+ decryptChannelSecret: () => decryptChannelSecret,
16151
16295
  decryptCredentialForDisplay: () => decryptCredentialForDisplay,
16152
16296
  desc: () => desc,
16297
+ encryptChannelCredentials: () => encryptChannelCredentials,
16298
+ encryptChannelSecret: () => encryptChannelSecret,
16153
16299
  entities: () => entities,
16154
16300
  entityLlmKeys: () => entityLlmKeys,
16155
16301
  entityMembers: () => entityMembers,
@@ -16283,7 +16429,7 @@ var init_local_auth = __esm({
16283
16429
  });
16284
16430
 
16285
16431
  // ../../packages/auth/src/lib/constant-time.ts
16286
- import { createHash, timingSafeEqual } from "crypto";
16432
+ import { createHash as createHash2, timingSafeEqual } from "crypto";
16287
16433
  var init_constant_time = __esm({
16288
16434
  "../../packages/auth/src/lib/constant-time.ts"() {
16289
16435
  "use strict";
@@ -16502,9 +16648,34 @@ var init_ports = __esm({
16502
16648
  }
16503
16649
  });
16504
16650
 
16651
+ // src/lib/log-rotation.ts
16652
+ import { existsSync as existsSync6, renameSync, rmSync, statSync } from "fs";
16653
+ function rotateLogIfNeeded(logFile, maxBytes = LOG_ROTATE_MAX_BYTES) {
16654
+ try {
16655
+ if (!existsSync6(logFile)) return false;
16656
+ if (statSync(logFile).size < maxBytes) return false;
16657
+ const archived = `${logFile}.1`;
16658
+ rmSync(archived, { force: true });
16659
+ renameSync(logFile, archived);
16660
+ return true;
16661
+ } catch (err) {
16662
+ console.warn(
16663
+ `[nodalai] log rotation skipped for ${logFile}: ${err instanceof Error ? err.message : String(err)}`
16664
+ );
16665
+ return false;
16666
+ }
16667
+ }
16668
+ var LOG_ROTATE_MAX_BYTES;
16669
+ var init_log_rotation = __esm({
16670
+ "src/lib/log-rotation.ts"() {
16671
+ "use strict";
16672
+ LOG_ROTATE_MAX_BYTES = 20 * 1024 * 1024;
16673
+ }
16674
+ });
16675
+
16505
16676
  // src/lib/processes.ts
16506
16677
  import { execa as execa2 } from "execa";
16507
- import { createWriteStream, openSync, writeFileSync as writeFileSync4, readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
16678
+ import { createWriteStream, openSync, writeFileSync as writeFileSync4, readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
16508
16679
  import { join as join6, dirname as dirname4, resolve } from "path";
16509
16680
  import { fileURLToPath as fileURLToPath3 } from "url";
16510
16681
  async function killProcessTree(child) {
@@ -16653,7 +16824,7 @@ function resolveAppDir(appName) {
16653
16824
  const fileDir = dirname4(here);
16654
16825
  let cursor = fileDir;
16655
16826
  for (let i = 0; i < 8; i++) {
16656
- if (existsSync6(join6(cursor, "pnpm-workspace.yaml"))) {
16827
+ if (existsSync7(join6(cursor, "pnpm-workspace.yaml"))) {
16657
16828
  return join6(cursor, appName);
16658
16829
  }
16659
16830
  const parent = dirname4(cursor);
@@ -16672,6 +16843,7 @@ function spawnWiring(logFile, detach) {
16672
16843
  }
16673
16844
  function spawnRunner(env, opts = {}) {
16674
16845
  const logFile = join6(LOG_DIR, "runner.log");
16846
+ rotateLogIfNeeded(logFile);
16675
16847
  const outStream = createWriteStream(logFile, { flags: "a" });
16676
16848
  let bin;
16677
16849
  let args;
@@ -16717,6 +16889,7 @@ env keys: ${Object.keys(env).join(",")}
16717
16889
  }
16718
16890
  function spawnWeb(env, opts = {}) {
16719
16891
  const logFile = join6(LOG_DIR, "web.log");
16892
+ rotateLogIfNeeded(logFile);
16720
16893
  const outStream = createWriteStream(logFile, { flags: "a" });
16721
16894
  let bin;
16722
16895
  let args;
@@ -16804,7 +16977,7 @@ function writePids(pids) {
16804
16977
  }
16805
16978
  function readPids() {
16806
16979
  const pidFile = join6(PID_DIR, "processes.json");
16807
- if (!existsSync6(pidFile)) return null;
16980
+ if (!existsSync7(pidFile)) return null;
16808
16981
  try {
16809
16982
  return JSON.parse(readFileSync5(pidFile, "utf-8"));
16810
16983
  } catch {
@@ -16813,7 +16986,7 @@ function readPids() {
16813
16986
  }
16814
16987
  function clearPids() {
16815
16988
  const pidFile = join6(PID_DIR, "processes.json");
16816
- if (existsSync6(pidFile)) {
16989
+ if (existsSync7(pidFile)) {
16817
16990
  writeFileSync4(pidFile, JSON.stringify({}), "utf-8");
16818
16991
  }
16819
16992
  }
@@ -16859,6 +17032,7 @@ var init_processes = __esm({
16859
17032
  "src/lib/processes.ts"() {
16860
17033
  "use strict";
16861
17034
  init_config();
17035
+ init_log_rotation();
16862
17036
  SNAPSHOT_BUDGET_MS = 2e4;
16863
17037
  SHUTDOWN_SNAPSHOT_BUDGET_MS = 6e3;
16864
17038
  inFlightSnapshot = null;
@@ -16890,11 +17064,76 @@ var init_processes = __esm({
16890
17064
  lastSnapshotFailure = null;
16891
17065
  BUNDLED_ROOT = (() => {
16892
17066
  const here = dirname4(fileURLToPath3(import.meta.url));
16893
- return existsSync6(join6(here, "runner.js")) ? here : null;
17067
+ return existsSync7(join6(here, "runner.js")) ? here : null;
16894
17068
  })();
16895
17069
  }
16896
17070
  });
16897
17071
 
17072
+ // src/lib/watchdog.ts
17073
+ async function probeRunnerHealth(runnerUrl) {
17074
+ try {
17075
+ const controller = new AbortController();
17076
+ const t = setTimeout(() => controller.abort(), 3e3);
17077
+ const res = await fetch(`${runnerUrl}/api/health`, { signal: controller.signal });
17078
+ clearTimeout(t);
17079
+ if (res.status === 200) return { state: "healthy", detail: "" };
17080
+ const body = await res.json().catch(() => null);
17081
+ const detail = body?.db === "error" ? "database unreachable (runner reports db: error)" : `runner is unhealthy (HTTP ${res.status})`;
17082
+ return { state: "degraded", detail };
17083
+ } catch (err) {
17084
+ const aborted = err instanceof Error && err.name === "AbortError";
17085
+ return {
17086
+ state: "unreachable",
17087
+ detail: aborted ? "runner did not answer /api/health within 3s (busy or stalled)" : "runner is not answering /api/health"
17088
+ };
17089
+ }
17090
+ }
17091
+ function startHealthWatchdog(opts) {
17092
+ const intervalMs = opts.intervalMs ?? 3e4;
17093
+ const failureThreshold = opts.failureThreshold ?? 2;
17094
+ let announced = "healthy";
17095
+ let consecutiveFailures = 0;
17096
+ let stopped = false;
17097
+ let inFlight = false;
17098
+ const tick = async () => {
17099
+ if (stopped || inFlight) return;
17100
+ inFlight = true;
17101
+ try {
17102
+ const result = await opts.probe();
17103
+ if (stopped) return;
17104
+ if (result.state === "healthy") {
17105
+ consecutiveFailures = 0;
17106
+ if (announced !== "healthy") {
17107
+ announced = "healthy";
17108
+ opts.onTransition("healthy", "services are answering again");
17109
+ }
17110
+ return;
17111
+ }
17112
+ consecutiveFailures += 1;
17113
+ if (consecutiveFailures < failureThreshold) return;
17114
+ if (announced !== result.state) {
17115
+ announced = result.state;
17116
+ opts.onTransition(result.state, result.detail);
17117
+ }
17118
+ } finally {
17119
+ inFlight = false;
17120
+ }
17121
+ };
17122
+ const timer2 = setInterval(() => void tick(), intervalMs);
17123
+ timer2.unref?.();
17124
+ return {
17125
+ stop: () => {
17126
+ stopped = true;
17127
+ clearInterval(timer2);
17128
+ }
17129
+ };
17130
+ }
17131
+ var init_watchdog = __esm({
17132
+ "src/lib/watchdog.ts"() {
17133
+ "use strict";
17134
+ }
17135
+ });
17136
+
16898
17137
  // src/commands/up.ts
16899
17138
  var up_exports = {};
16900
17139
  __export(up_exports, {
@@ -16904,7 +17143,7 @@ import chalk from "chalk";
16904
17143
  import ora from "ora";
16905
17144
  import open from "open";
16906
17145
  import { randomBytes as randomBytes4 } from "crypto";
16907
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
17146
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
16908
17147
  import { join as join7 } from "path";
16909
17148
  async function killSilent(child) {
16910
17149
  await killProcessTree(child);
@@ -16912,7 +17151,7 @@ async function killSilent(child) {
16912
17151
  function printLogTail(service, lines) {
16913
17152
  const logFile = join7(LOG_DIR, `${service}.log`);
16914
17153
  try {
16915
- if (!existsSync7(logFile)) {
17154
+ if (!existsSync8(logFile)) {
16916
17155
  console.error(chalk.gray(`
16917
17156
  ${service}: no log at ${logFile}`));
16918
17157
  return;
@@ -17274,7 +17513,32 @@ ${lines}
17274
17513
  }
17275
17514
  console.log(chalk.gray(" Ctrl+C to stop all services"));
17276
17515
  console.log("");
17516
+ const watchdog = startHealthWatchdog({
17517
+ probe: () => probeRunnerHealth(runnerUrl),
17518
+ onTransition: (state, detail) => {
17519
+ const at = (/* @__PURE__ */ new Date()).toLocaleTimeString();
17520
+ if (state === "healthy") {
17521
+ console.log(chalk.green(`
17522
+ [${at}] Recovered \u2014 ${detail}.`));
17523
+ return;
17524
+ }
17525
+ console.error(chalk.red.bold(`
17526
+ [${at}] DEGRADED \u2014 ${detail}.`));
17527
+ if (state === "degraded") {
17528
+ console.error(
17529
+ chalk.red(
17530
+ " The dashboard may still render, but nothing can read or write the database:\n jobs are not created, chats and schedules are failing."
17531
+ )
17532
+ );
17533
+ printLogTail("runner", 10);
17534
+ }
17535
+ console.error(
17536
+ chalk.yellow(" \u2192 Restart the stack: `nodal-agents down`, then `nodal-agents up`.")
17537
+ );
17538
+ }
17539
+ });
17277
17540
  await Promise.race([runnerProcess, webProcess]);
17541
+ watchdog.stop();
17278
17542
  await shutdown();
17279
17543
  }
17280
17544
  var init_up = __esm({
@@ -17287,6 +17551,7 @@ var init_up = __esm({
17287
17551
  init_ports();
17288
17552
  init_processes();
17289
17553
  init_src4();
17554
+ init_watchdog();
17290
17555
  }
17291
17556
  });
17292
17557
 
@@ -17574,7 +17839,7 @@ __export(down_exports, {
17574
17839
  runDown: () => runDown
17575
17840
  });
17576
17841
  import chalk3 from "chalk";
17577
- import { existsSync as existsSync8 } from "fs";
17842
+ import { existsSync as existsSync9 } from "fs";
17578
17843
  import { join as join8 } from "path";
17579
17844
  async function killPid(pid, label) {
17580
17845
  if (!isPidAlive(pid)) {
@@ -17591,7 +17856,7 @@ async function killPid(pid, label) {
17591
17856
  }
17592
17857
  async function stopPostgresGracefully() {
17593
17858
  const pidFile = join8(PG_DATA_DIR, "postmaster.pid");
17594
- if (!existsSync8(pidFile)) return false;
17859
+ if (!existsSync9(pidFile)) return false;
17595
17860
  const pgPid = readPostmasterPid(PG_DATA_DIR);
17596
17861
  try {
17597
17862
  const EmbeddedPostgres = (await import("embedded-postgres")).default;
@@ -17661,7 +17926,7 @@ __export(logs_exports, {
17661
17926
  runLogs: () => runLogs
17662
17927
  });
17663
17928
  import chalk4 from "chalk";
17664
- import { existsSync as existsSync9, createReadStream, watch, statSync } from "fs";
17929
+ import { existsSync as existsSync10, createReadStream, watch, statSync as statSync2 } from "fs";
17665
17930
  import { join as join9 } from "path";
17666
17931
  function isServiceName(s) {
17667
17932
  return VALID_SERVICES.includes(s);
@@ -17675,7 +17940,7 @@ async function runLogs(service) {
17675
17940
  const services = target === "all" ? VALID_SERVICES : [target];
17676
17941
  for (const svc of services) {
17677
17942
  const logFile = join9(LOG_DIR, `${svc}.log`);
17678
- if (!existsSync9(logFile)) {
17943
+ if (!existsSync10(logFile)) {
17679
17944
  console.log(chalk4.gray(` [${svc}] No log file yet at ${logFile}`));
17680
17945
  continue;
17681
17946
  }
@@ -17690,18 +17955,18 @@ async function runLogs(service) {
17690
17955
  }
17691
17956
  if (target !== "all") {
17692
17957
  const logFile = join9(LOG_DIR, `${target}.log`);
17693
- if (!existsSync9(logFile)) {
17958
+ if (!existsSync10(logFile)) {
17694
17959
  console.log(chalk4.yellow("Waiting for log file to be created\u2026"));
17695
17960
  }
17696
17961
  console.log(chalk4.gray("\nFollowing log (Ctrl+C to stop)...\n"));
17697
17962
  let fileSize = 0;
17698
17963
  try {
17699
- fileSize = statSync(logFile).size;
17964
+ fileSize = statSync2(logFile).size;
17700
17965
  } catch {
17701
17966
  }
17702
17967
  const watcher = watch(logFile, () => {
17703
17968
  try {
17704
- const newSize = statSync(logFile).size;
17969
+ const newSize = statSync2(logFile).size;
17705
17970
  if (newSize > fileSize) {
17706
17971
  const stream = createReadStream(logFile, { start: fileSize, encoding: "utf-8" });
17707
17972
  stream.on("data", (chunk) => process.stdout.write(chunk));
@@ -17726,9 +17991,9 @@ var init_logs = __esm({
17726
17991
 
17727
17992
  // ../../packages/checkpoints/src/checkpoints.ts
17728
17993
  import { execFile } from "child_process";
17729
- import { createHash as createHash2 } from "crypto";
17994
+ import { createHash as createHash3 } from "crypto";
17730
17995
  import { mkdir, writeFile } from "fs/promises";
17731
- import { existsSync as existsSync10 } from "fs";
17996
+ import { existsSync as existsSync11 } from "fs";
17732
17997
  import { join as join10 } from "path";
17733
17998
  import { promisify } from "util";
17734
17999
  function workspaceKey(workspace) {
@@ -17737,7 +18002,7 @@ function workspaceKey(workspace) {
17737
18002
  norm = norm.toLowerCase().replace(/\//g, "\\");
17738
18003
  }
17739
18004
  norm = norm.replace(/[\\/]+$/, "");
17740
- return createHash2("sha256").update(norm).digest("hex").slice(0, 16);
18005
+ return createHash3("sha256").update(norm).digest("hex").slice(0, 16);
17741
18006
  }
17742
18007
  async function git(store, workspace, args) {
17743
18008
  const key = workspaceKey(workspace);
@@ -17768,7 +18033,7 @@ async function ensureStore(store) {
17768
18033
  const gitDir = join10(store, "store");
17769
18034
  await mkdir(join10(store, "indexes"), { recursive: true });
17770
18035
  await writeFile(join10(store, "gitconfig"), "", { flag: "a" });
17771
- if (!existsSync10(join10(gitDir, "HEAD"))) {
18036
+ if (!existsSync11(join10(gitDir, "HEAD"))) {
17772
18037
  await mkdir(gitDir, { recursive: true });
17773
18038
  await run("git", ["init", "--bare", "--quiet", gitDir], {
17774
18039
  timeout: GIT_TIMEOUT_MS,
@@ -17801,7 +18066,7 @@ async function snapshot(store, workspace, label) {
17801
18066
  return { sha, workspace, at, label };
17802
18067
  }
17803
18068
  async function listCheckpoints(store, workspace, limit = 20) {
17804
- if (!existsSync10(join10(store, "store", "HEAD"))) return [];
18069
+ if (!existsSync11(join10(store, "store", "HEAD"))) return [];
17805
18070
  const ref = `refs/nodal/${workspaceKey(workspace)}`;
17806
18071
  const out = await git(store, workspace, [
17807
18072
  "log",
@@ -30975,7 +31240,13 @@ async function buildNodalMcpServer(opts) {
30975
31240
  }
30976
31241
  targetAgent = root;
30977
31242
  } else {
30978
- const [launcher] = await opts.db.select({ id: agents.id }).from(agents).where(and(eq(agents.id, agentRow.id), eq(agents.active, true))).limit(1);
31243
+ const [launcher] = await opts.db.select({ id: agents.id }).from(agents).where(
31244
+ and(
31245
+ eq(agents.id, agentRow.id),
31246
+ eq(agents.active, true),
31247
+ eq(agents.entityId, entityId)
31248
+ )
31249
+ ).limit(1);
30979
31250
  if (!launcher) {
30980
31251
  throw new Error(
30981
31252
  "mcp_agent_not_found: the agent this server was launched for has been deactivated. Reactivate it, set a root agent, or restart the server for another agent."
@@ -31185,7 +31456,7 @@ __export(reset_exports, {
31185
31456
  });
31186
31457
  import chalk6 from "chalk";
31187
31458
  import prompts2 from "prompts";
31188
- import { rmSync, existsSync as existsSync11 } from "fs";
31459
+ import { rmSync as rmSync2, existsSync as existsSync12 } from "fs";
31189
31460
  async function runReset(options = {}) {
31190
31461
  if (!options.yes) {
31191
31462
  const { confirm } = await prompts2({
@@ -31203,8 +31474,8 @@ async function runReset(options = {}) {
31203
31474
  }
31204
31475
  console.log(chalk6.yellow("Stopping any running processes\u2026"));
31205
31476
  await runDown();
31206
- if (existsSync11(CONFIG_DIR)) {
31207
- rmSync(CONFIG_DIR, { recursive: true, force: true });
31477
+ if (existsSync12(CONFIG_DIR)) {
31478
+ rmSync2(CONFIG_DIR, { recursive: true, force: true });
31208
31479
  console.log(chalk6.green(` Removed ${CONFIG_DIR}`));
31209
31480
  } else {
31210
31481
  console.log(chalk6.gray(` ${CONFIG_DIR} was already empty`));
@@ -0,0 +1,32 @@
1
+ -- 0082 — inversion du modele a deux cles Yolo (decision Quentin 24/08).
2
+ --
3
+ -- L'ancien lan_command_yolo etait une PRE-CONDITION : sur une install reseau,
4
+ -- il fallait l'allumer pour avoir LE DROIT d'activer le Yolo par agent — deux
5
+ -- serrures, une seule cle (les deux gestes etaient deja owner-only). Sa vraie
6
+ -- valeur etait le coupe-circuit : l'eteindre rendait dormantes toutes les
7
+ -- regles d'un coup. auto_run_paused ne garde QUE ce role : un frein
8
+ -- d'urgence, inactif par defaut, qui met en pause tout l'auto-run de code du
9
+ -- workspace quand on l'enclenche.
10
+ --
11
+ -- Le backfill preserve l'INTENTION de chaque install : un master OFF alors
12
+ -- que des regles auto_approve d'outils d'execution existent = le proprietaire
13
+ -- avait deliberement coupe (usage coupe-circuit) -> pause maintenue. Un
14
+ -- master OFF sans aucune regle armee (l'immense majorite, dont toute install
15
+ -- neuve) = rien a couper -> pas de pause. Un master ON = il autorisait ->
16
+ -- pas de pause. Aucun comportement effectif ne change au reboot.
17
+ ALTER TABLE entities ADD COLUMN auto_run_paused boolean NOT NULL DEFAULT false;
18
+
19
+ UPDATE entities e
20
+ SET auto_run_paused = true
21
+ WHERE e.lan_command_yolo = false
22
+ AND EXISTS (
23
+ SELECT 1
24
+ FROM approval_rules ar
25
+ LEFT JOIN agents a ON a.id = ar.agent_id
26
+ WHERE ar.action = 'auto_approve'
27
+ AND ar.tool_name IN ('run_command', 'code_task', 'run_skill_script',
28
+ 'skill_file_write', 'create_mcp', 'attach_mcp')
29
+ AND (a.entity_id = e.id OR ar.entity_id = e.id)
30
+ );
31
+
32
+ ALTER TABLE entities DROP COLUMN lan_command_yolo;