aiden-runtime 4.1.5 → 4.5.0

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 (163) hide show
  1. package/README.md +250 -847
  2. package/dist/api/server.js +32 -5
  3. package/dist/cli/v4/aidenCLI.js +351 -53
  4. package/dist/cli/v4/callbacks.js +170 -0
  5. package/dist/cli/v4/chatSession.js +138 -3
  6. package/dist/cli/v4/commands/_runtimeToggleHelpers.js +92 -0
  7. package/dist/cli/v4/commands/browserDepth.js +45 -0
  8. package/dist/cli/v4/commands/cron.js +264 -0
  9. package/dist/cli/v4/commands/daemon.js +541 -0
  10. package/dist/cli/v4/commands/daemonStatus.js +253 -0
  11. package/dist/cli/v4/commands/help.js +7 -0
  12. package/dist/cli/v4/commands/index.js +20 -1
  13. package/dist/cli/v4/commands/runs.js +203 -0
  14. package/dist/cli/v4/commands/sandbox.js +48 -0
  15. package/dist/cli/v4/commands/suggestions.js +68 -0
  16. package/dist/cli/v4/commands/tce.js +41 -0
  17. package/dist/cli/v4/commands/trigger.js +378 -0
  18. package/dist/cli/v4/commands/update.js +95 -3
  19. package/dist/cli/v4/daemonAgentBuilder.js +142 -0
  20. package/dist/cli/v4/defaultSoul.js +1 -1
  21. package/dist/cli/v4/display/capabilityCard.js +26 -0
  22. package/dist/cli/v4/display.js +18 -8
  23. package/dist/cli/v4/replyRenderer.js +31 -23
  24. package/dist/cli/v4/updateBootPrompt.js +170 -0
  25. package/dist/core/playwrightBridge.js +129 -0
  26. package/dist/core/v4/aidenAgent.js +308 -4
  27. package/dist/core/v4/browserState.js +436 -0
  28. package/dist/core/v4/checkpoint.js +79 -0
  29. package/dist/core/v4/daemon/bootstrap.js +604 -0
  30. package/dist/core/v4/daemon/cleanShutdown.js +154 -0
  31. package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
  32. package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
  33. package/dist/core/v4/daemon/cron/migration.js +199 -0
  34. package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
  35. package/dist/core/v4/daemon/daemonConfig.js +90 -0
  36. package/dist/core/v4/daemon/db/connection.js +106 -0
  37. package/dist/core/v4/daemon/db/migrations.js +296 -0
  38. package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
  39. package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
  40. package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
  41. package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
  42. package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
  43. package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
  44. package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
  45. package/dist/core/v4/daemon/dispatcher/index.js +53 -0
  46. package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
  47. package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
  48. package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
  49. package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
  50. package/dist/core/v4/daemon/drain.js +156 -0
  51. package/dist/core/v4/daemon/eventLoopLag.js +73 -0
  52. package/dist/core/v4/daemon/health.js +159 -0
  53. package/dist/core/v4/daemon/idempotencyStore.js +204 -0
  54. package/dist/core/v4/daemon/index.js +179 -0
  55. package/dist/core/v4/daemon/instanceTracker.js +99 -0
  56. package/dist/core/v4/daemon/resourceRegistry.js +150 -0
  57. package/dist/core/v4/daemon/restartCode.js +32 -0
  58. package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
  59. package/dist/core/v4/daemon/runStore.js +114 -0
  60. package/dist/core/v4/daemon/runtimeLock.js +167 -0
  61. package/dist/core/v4/daemon/signals.js +50 -0
  62. package/dist/core/v4/daemon/supervisor.js +272 -0
  63. package/dist/core/v4/daemon/triggerBus.js +279 -0
  64. package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
  65. package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
  66. package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
  67. package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
  68. package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
  69. package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
  70. package/dist/core/v4/daemon/triggers/email/index.js +332 -0
  71. package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
  72. package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
  73. package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
  74. package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
  75. package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
  76. package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
  77. package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
  78. package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
  79. package/dist/core/v4/daemon/triggers/webhook.js +376 -0
  80. package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
  81. package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
  82. package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
  83. package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
  84. package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
  85. package/dist/core/v4/daemon/types.js +15 -0
  86. package/dist/core/v4/dockerSession.js +461 -0
  87. package/dist/core/v4/dryRun.js +117 -0
  88. package/dist/core/v4/failureClassifier.js +779 -0
  89. package/dist/core/v4/recoveryReport.js +449 -0
  90. package/dist/core/v4/runtimeToggles.js +187 -0
  91. package/dist/core/v4/sandboxConfig.js +285 -0
  92. package/dist/core/v4/sandboxFs.js +316 -0
  93. package/dist/core/v4/suggestionCatalog.js +41 -0
  94. package/dist/core/v4/suggestionEngine.js +210 -0
  95. package/dist/core/v4/toolRegistry.js +18 -0
  96. package/dist/core/v4/turnState.js +587 -0
  97. package/dist/core/v4/update/checkUpdate.js +63 -3
  98. package/dist/core/v4/update/installMethodDetect.js +115 -0
  99. package/dist/core/v4/update/registryClient.js +121 -0
  100. package/dist/core/v4/update/skipState.js +75 -0
  101. package/dist/core/v4/verifier.js +448 -0
  102. package/dist/core/version.js +1 -1
  103. package/dist/tools/v4/browser/_observer.js +224 -0
  104. package/dist/tools/v4/browser/browserBlocker.js +396 -0
  105. package/dist/tools/v4/browser/browserClick.js +18 -1
  106. package/dist/tools/v4/browser/browserClose.js +18 -1
  107. package/dist/tools/v4/browser/browserExtract.js +5 -1
  108. package/dist/tools/v4/browser/browserFill.js +17 -1
  109. package/dist/tools/v4/browser/browserGetUrl.js +5 -1
  110. package/dist/tools/v4/browser/browserNavigate.js +16 -1
  111. package/dist/tools/v4/browser/browserScreenshot.js +5 -1
  112. package/dist/tools/v4/browser/browserScroll.js +18 -1
  113. package/dist/tools/v4/browser/browserType.js +17 -1
  114. package/dist/tools/v4/browser/captchaCheck.js +5 -1
  115. package/dist/tools/v4/executeCode.js +1 -0
  116. package/dist/tools/v4/files/fileCopy.js +56 -2
  117. package/dist/tools/v4/files/fileDelete.js +38 -1
  118. package/dist/tools/v4/files/fileList.js +12 -1
  119. package/dist/tools/v4/files/fileMove.js +59 -2
  120. package/dist/tools/v4/files/filePatch.js +43 -1
  121. package/dist/tools/v4/files/fileRead.js +12 -1
  122. package/dist/tools/v4/files/fileWrite.js +41 -1
  123. package/dist/tools/v4/index.js +71 -58
  124. package/dist/tools/v4/memory/memoryAdd.js +14 -0
  125. package/dist/tools/v4/memory/memoryRemove.js +14 -0
  126. package/dist/tools/v4/memory/memoryReplace.js +15 -0
  127. package/dist/tools/v4/memory/sessionSummary.js +12 -0
  128. package/dist/tools/v4/process/processKill.js +19 -0
  129. package/dist/tools/v4/process/processList.js +1 -0
  130. package/dist/tools/v4/process/processLogRead.js +1 -0
  131. package/dist/tools/v4/process/processSpawn.js +13 -0
  132. package/dist/tools/v4/process/processWait.js +1 -0
  133. package/dist/tools/v4/sessions/recallSession.js +1 -0
  134. package/dist/tools/v4/sessions/sessionList.js +1 -0
  135. package/dist/tools/v4/sessions/sessionSearch.js +1 -0
  136. package/dist/tools/v4/skills/lookupToolSchema.js +2 -0
  137. package/dist/tools/v4/skills/skillManage.js +13 -0
  138. package/dist/tools/v4/skills/skillView.js +1 -0
  139. package/dist/tools/v4/skills/skillsList.js +1 -0
  140. package/dist/tools/v4/subagent/subagentFanout.js +1 -0
  141. package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
  142. package/dist/tools/v4/system/appClose.js +13 -0
  143. package/dist/tools/v4/system/appInput.js +13 -0
  144. package/dist/tools/v4/system/appLaunch.js +13 -0
  145. package/dist/tools/v4/system/clipboardRead.js +1 -0
  146. package/dist/tools/v4/system/clipboardWrite.js +14 -0
  147. package/dist/tools/v4/system/mediaKey.js +12 -0
  148. package/dist/tools/v4/system/mediaSessions.js +1 -0
  149. package/dist/tools/v4/system/mediaTransport.js +13 -0
  150. package/dist/tools/v4/system/naturalEvents.js +1 -0
  151. package/dist/tools/v4/system/nowPlaying.js +1 -0
  152. package/dist/tools/v4/system/osProcessList.js +1 -0
  153. package/dist/tools/v4/system/screenshot.js +1 -0
  154. package/dist/tools/v4/system/systemInfo.js +1 -0
  155. package/dist/tools/v4/system/volumeSet.js +17 -0
  156. package/dist/tools/v4/terminal/shellExec.js +81 -9
  157. package/dist/tools/v4/web/deepResearch.js +1 -0
  158. package/dist/tools/v4/web/openUrl.js +1 -0
  159. package/dist/tools/v4/web/webFetch.js +1 -0
  160. package/dist/tools/v4/web/webPage.js +1 -0
  161. package/dist/tools/v4/web/webSearch.js +1 -0
  162. package/dist/tools/v4/web/youtubeSearch.js +1 -0
  163. package/package.json +7 -1
@@ -81,6 +81,10 @@ const modelDiscovery_1 = require("../core/modelDiscovery");
81
81
  const userProfile_1 = require("../core/userProfile");
82
82
  const toolRegistry_1 = require("../core/toolRegistry");
83
83
  const playwrightBridge_1 = require("../core/playwrightBridge");
84
+ // v4.5 Phase 1 — daemon foundation (gated by AIDEN_DAEMON=1; dormant otherwise).
85
+ // The shared `bootstrapDaemon` module handles every responsibility — db open,
86
+ // runtime lock, crash recovery, health endpoints, signal handlers.
87
+ const daemon_1 = require("../core/v4/daemon");
84
88
  const computerControl_1 = require("../core/computerControl");
85
89
  const agentLoop_1 = require("../core/agentLoop");
86
90
  const statusVerbs_1 = require("../core/statusVerbs");
@@ -503,8 +507,18 @@ function createApiServer() {
503
507
  }
504
508
  }
505
509
  }, 5 * 60 * 1000).unref();
506
- // JSON body parsing (10 MB limit)
507
- app.use(express_1.default.json({ limit: '10mb' }));
510
+ // JSON body parsing (10 MB limit) — path-conditional skip for the
511
+ // v4.5 daemon's webhook routes, which need the RAW body to verify
512
+ // HMAC signatures. Without this guard, express.json would consume
513
+ // the request stream before mountWebhookRoutes' inline express.raw
514
+ // could see it, breaking signature verification (every valid POST
515
+ // would 401 because we'd HMAC empty bytes).
516
+ const _jsonParser = express_1.default.json({ limit: '10mb' });
517
+ app.use((req, res, next) => {
518
+ if (req.path.startsWith('/api/triggers/webhook/'))
519
+ return next();
520
+ return _jsonParser(req, res, next);
521
+ });
508
522
  // Security headers
509
523
  app.use((_req, res, next) => {
510
524
  res.setHeader('X-Content-Type-Options', 'nosniff');
@@ -6080,9 +6094,22 @@ function startApiServer(portArg) {
6080
6094
  }
6081
6095
  catch { }
6082
6096
  }
6083
- // ── Clean shutdown: remove PID on signal ────────────────────
6084
- process.once('SIGINT', () => { removePid(); (0, playwrightBridge_1.pwClose)().finally(() => (0, memoryDistiller_1.distillAllActiveSessions)(8000).finally(() => process.exit(0))); });
6085
- process.once('SIGTERM', () => { removePid(); (0, playwrightBridge_1.pwClose)().finally(() => (0, memoryDistiller_1.distillAllActiveSessions)(8000).finally(() => process.exit(0))); });
6097
+ // ── v4.5 Phase 1 daemon foundation (gated, dormant when off) ──
6098
+ // When AIDEN_DAEMON=1, the shared bootstrap module activates the
6099
+ // foundation (opens daemon.db, acquires runtime lock, evaluates
6100
+ // boot state, mounts /health/* + /metrics + /api/daemon/* onto
6101
+ // the existing app, installs the 5-step ordered drain on SIGINT/
6102
+ // SIGTERM/SIGUSR1).
6103
+ //
6104
+ // When AIDEN_DAEMON is unset/=0, bootstrap returns a NOOP_HANDLE
6105
+ // and the legacy signal handlers below run — zero regression for
6106
+ // the current OpenAI-compatible API surface.
6107
+ const _daemonHandle = (0, daemon_1.bootstrapDaemon)({ app });
6108
+ if (!_daemonHandle.active) {
6109
+ // ── Legacy clean shutdown: remove PID on signal (AIDEN_DAEMON=0) ──
6110
+ process.once('SIGINT', () => { removePid(); (0, playwrightBridge_1.pwClose)().finally(() => (0, memoryDistiller_1.distillAllActiveSessions)(8000).finally(() => process.exit(0))); });
6111
+ process.once('SIGTERM', () => { removePid(); (0, playwrightBridge_1.pwClose)().finally(() => (0, memoryDistiller_1.distillAllActiveSessions)(8000).finally(() => process.exit(0))); });
6112
+ }
6086
6113
  // ── EADDRINUSE: kill stale process, retry once ───────────────
6087
6114
  server.on('error', (err) => {
6088
6115
  if (err.code !== 'EADDRINUSE') {
@@ -207,7 +207,35 @@ function buildAgentFallbackSlots(primaryAdapter, primaryProviderId, primaryModel
207
207
  // same Groq account at slots 0 and 1).
208
208
  return [primarySlot, ...defaults];
209
209
  }
210
+ /**
211
+ * Commander 5.x option-value collector. Use with `.option('--flag <v>',
212
+ * '...', collectArray, [])` so repeated `--flag a --flag b` produces
213
+ * `['a','b']` instead of `'b'`. Commander 5 does NOT support the
214
+ * `<x...>` variadic syntax on options (only positional args).
215
+ */
216
+ function collectArray(value, previous) {
217
+ if (!Array.isArray(previous))
218
+ return [value];
219
+ return previous.concat([value]);
220
+ }
210
221
  async function main(argv, opts = {}) {
222
+ // v4.5 Phase 1 — daemon foundation bootstrap is deferred until the
223
+ // REPL action handler fires (see the default `.action()` below).
224
+ // Subcommands like `trigger add`, `trigger list`, `config get`,
225
+ // `--version`, `--help`, `doctor`, etc. do NOT need the daemon
226
+ // foundation. Booting it for every CLI invocation:
227
+ // 1. Wastes work (HTTP server up + tear down for a one-shot
228
+ // DB-write subcommand).
229
+ // 2. Creates phantom "crash recovery" warnings: the daemon-
230
+ // foundation install/teardown cycle for each subcommand
231
+ // doesn't shut down gracefully (process.exit happens after
232
+ // the action), so the NEXT invocation's evaluateBootState
233
+ // sees a stale daemon_instances row with a missed heartbeat
234
+ // and writes a crash_reports row.
235
+ // 3. Adds latency on cold paths (CLI feels slow when AIDEN_DAEMON=1).
236
+ // The interactive REPL action calls `bootstrapDaemon()` explicitly,
237
+ // which is the only entry point that genuinely benefits from the
238
+ // foundation (long-running session = useful daemon).
211
239
  const program = new commander_1.Command();
212
240
  program
213
241
  .name('aiden')
@@ -234,6 +262,36 @@ async function main(argv, opts = {}) {
234
262
  process.stderr.write(`Run 'aiden --help' for available commands.\n`);
235
263
  process.exit(2);
236
264
  }
265
+ // v4.5 Phase 1 — daemon foundation bootstrap. Lazy-imported so
266
+ // the daemon module's side-effect cost stays at zero when
267
+ // AIDEN_DAEMON is off (better-sqlite3 native binding, chokidar,
268
+ // express, etc. all stay unloaded). Only fires for the REPL
269
+ // path — subcommands (trigger add/list/show/remove/enable/
270
+ // disable/test, config, --version, --help, doctor, …) do NOT
271
+ // touch the daemon foundation.
272
+ // v4.5 Phase 7c — two-phase bootstrap. The daemon FOUNDATION
273
+ // (file watchers, webhook routes, email triggers, cron
274
+ // emitter, dispatcher with placeholder runner, HTTP server)
275
+ // comes up HERE — before buildAgentRuntime, before the setup
276
+ // wizard. This restores the pre-7b guarantee that daemon
277
+ // foundation boots regardless of REPL configuration state
278
+ // (matters for systemd/launchd units booted on fresh machines
279
+ // and for any environment without a provider configured yet).
280
+ //
281
+ // The REAL agent runner gets installed later inside
282
+ // runInteractiveChat() once buildAgentRuntime returns — see
283
+ // `installDaemonAgentBuilder` call there.
284
+ try {
285
+ if (process.env.AIDEN_DAEMON === '1') {
286
+ const { bootstrapDaemonFoundation } = await Promise.resolve().then(() => __importStar(require('../../core/v4/daemon/bootstrap')));
287
+ bootstrapDaemonFoundation();
288
+ }
289
+ }
290
+ catch (e) {
291
+ // Fail-loud but non-fatal — daemon foundation init failure
292
+ // must not block the user from opening a REPL.
293
+ console.error('[daemon] foundation bootstrap failed: ' + (e instanceof Error ? e.message : String(e)));
294
+ }
237
295
  // Tier-3.1: surface --no-ui as an env var so downstream modules
238
296
  // (which import uiBuild.ts) see the flag without threading it
239
297
  // through every call site.
@@ -318,6 +376,139 @@ async function main(argv, opts = {}) {
318
376
  }
319
377
  await runSkillsSubcommand(action, arg, opts);
320
378
  });
379
+ // v4.5 Phase 2 — file watcher trigger management.
380
+ program
381
+ .command('trigger <action> [args...]')
382
+ .description('Manage daemon triggers. Actions: add, list, show, remove, enable, disable, test.')
383
+ // Commander 5.x stores option values directly on the Command
384
+ // instance, so `--name` would clobber Command.prototype.name().
385
+ // Use `--label` instead; we map to the internal `name` arg below.
386
+ .option('--label <label>', 'Trigger label/name (for add).')
387
+ // Commander 5.x does NOT support `<x...>` variadic syntax on
388
+ // OPTIONS (only on positional args) — repeating overwrites
389
+ // instead of appending. Use an explicit collector so multiple
390
+ // --path / --include / --exclude / --event flags accumulate.
391
+ .option('--path <path>', 'Path to watch (for add file). Repeatable.', collectArray, [])
392
+ .option('--include <glob>', 'Include glob pattern. Repeatable.', collectArray, [])
393
+ .option('--exclude <glob>', 'Exclude glob pattern. Repeatable.', collectArray, [])
394
+ .option('--event <type>', 'Event type: add|change|unlink. Repeatable.', collectArray, [])
395
+ .option('--debounce-ms <n>', 'Per-path debounce in ms (default 750).', (v) => Number.parseInt(v, 10))
396
+ .option('--settle-ms <n>', 'Stable-stat settle in ms (default 1000).', (v) => Number.parseInt(v, 10))
397
+ .option('--max-settle-ms <n>', 'Max settle time before giving up (default 30000).', (v) => Number.parseInt(v, 10))
398
+ .option('--max-queue-depth <n>', 'Max per-watcher queue depth (default 100).', (v) => Number.parseInt(v, 10))
399
+ .option('--no-ignore-temp', 'Disable default ignore for editor temps + .git + node_modules.')
400
+ .option('--content-hash', 'Compute sha256 per change (opt-in; slower).')
401
+ .option('--reconcile <policy>', 'skip_existing | process_new_since_last_seen | full_rescan (default skip_existing).')
402
+ .option('--polling', 'Force polling mode (network FS / WSL bind mount).')
403
+ .option('--prompt-template <text>', 'Phase 5 — agent prompt template.')
404
+ .option('--disabled', 'Create trigger in disabled state.')
405
+ // v4.5 Phase 3 — webhook-specific options.
406
+ .option('--hmac <format>', 'Webhook HMAC format: github | gitlab | generic (default generic).')
407
+ .option('--secret <s>', 'Webhook secret. Auto-generated when omitted.')
408
+ .option('--rate-limit <n>', 'Webhook requests/min (default 30).', (v) => Number.parseInt(v, 10))
409
+ .option('--max-body-bytes <n>', 'Webhook max body cap (default 1048576).', (v) => Number.parseInt(v, 10))
410
+ .option('--idempotency-ttl-ms <n>', 'Webhook idempotency TTL (default 3600000).', (v) => Number.parseInt(v, 10))
411
+ .option('--deliver-only', 'Phase 3 stub: accept + log; Phase 5 will dispatch via channel.')
412
+ // v4.5 Phase 4a — email IMAP options.
413
+ .option('--host <host>', 'IMAP host (for add email).')
414
+ .option('--port <n>', 'IMAP port (default 993).', (v) => Number.parseInt(v, 10))
415
+ .option('--user <addr>', 'IMAP user / email address (for add email).')
416
+ .option('--password <pwd>', 'IMAP password (for add email).')
417
+ .option('--no-tls', 'Disable TLS (for add email; default is TLS on).')
418
+ .option('--mailbox <name>', 'IMAP mailbox (default INBOX).')
419
+ .option('--poll-ms <n>', 'Email poll interval ms (default 15000).', (v) => Number.parseInt(v, 10))
420
+ .option('--allow-sender <addr-or-glob>', 'Allowed sender pattern (repeatable, REQUIRED for email).', collectArray, [])
421
+ .option('--allow-subject <regex>', 'Allowed subject regex (repeatable).', collectArray, [])
422
+ .option('--attachment-policy <policy>', 'skip | inline-text | save-to-tmp (default skip).')
423
+ .option('--no-validate', 'Skip IMAP pre-flight connectivity check.')
424
+ .action(async (action, posArgs, cmd) => {
425
+ const { runTriggerSubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/trigger')));
426
+ const cliOpts = cmd.opts();
427
+ const argv = {
428
+ name: cliOpts.label,
429
+ paths: cliOpts.path,
430
+ include: cliOpts.include,
431
+ exclude: cliOpts.exclude,
432
+ events: cliOpts.event,
433
+ debounceMs: cliOpts.debounceMs,
434
+ settleMs: cliOpts.settleMs,
435
+ maxSettleMs: cliOpts.maxSettleMs,
436
+ maxQueueDepth: cliOpts.maxQueueDepth,
437
+ noIgnoreTemp: cliOpts.ignoreTemp === false,
438
+ contentHash: cliOpts.contentHash === true,
439
+ reconcile: cliOpts.reconcile,
440
+ polling: cliOpts.polling === true,
441
+ promptTemplate: cliOpts.promptTemplate,
442
+ disabled: cliOpts.disabled === true,
443
+ // v4.5 Phase 3 — webhook options.
444
+ hmac: cliOpts.hmac,
445
+ secret: cliOpts.secret,
446
+ rateLimit: cliOpts.rateLimit,
447
+ maxBodyBytes: cliOpts.maxBodyBytes,
448
+ idempotencyTtlMs: cliOpts.idempotencyTtlMs,
449
+ deliverOnly: cliOpts.deliverOnly === true,
450
+ // v4.5 Phase 4a — email options.
451
+ host: cliOpts.host,
452
+ port: cliOpts.port,
453
+ user: cliOpts.user,
454
+ password: cliOpts.password,
455
+ noTls: cliOpts.tls === false,
456
+ mailbox: cliOpts.mailbox,
457
+ pollMs: cliOpts.pollMs,
458
+ allowSenders: cliOpts.allowSender ?? [],
459
+ allowSubjects: cliOpts.allowSubject ?? [],
460
+ attachmentPolicy: cliOpts.attachmentPolicy,
461
+ noValidate: cliOpts.validate === false,
462
+ };
463
+ const code = await runTriggerSubcommand(action, posArgs ?? [], argv, {
464
+ writeOut: opts.writeOut,
465
+ });
466
+ process.exit(code);
467
+ });
468
+ // v4.5 Phase 4b — daemon supervisor commands.
469
+ program
470
+ .command('daemon <action> [args...]')
471
+ .description('Manage the v4.5 daemon. Actions: install, uninstall, start, stop, restart, status, logs.')
472
+ .action(async (action, posArgs) => {
473
+ const { runDaemonSubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/daemon')));
474
+ const code = await runDaemonSubcommand(action, posArgs ?? [], {
475
+ writeOut: opts.writeOut,
476
+ });
477
+ process.exit(code);
478
+ });
479
+ // v4.5 Phase 6 — `aiden cron` top-level surface (mirrors slash command).
480
+ program
481
+ .command('cron <action> [args...]')
482
+ .description('Scheduled jobs. Actions: add, list, show, remove, enable, disable, run, logs.')
483
+ .option('--label <name>', 'job label (alphanumeric/dash/underscore)')
484
+ .option('--schedule <expr>', 'cron expr ("0 9 * * *") / interval ("every 5m") / ISO timestamp')
485
+ .option('--command <cmd>', 'shell command to run')
486
+ .option('--timezone <tz>', 'IANA timezone (default UTC)')
487
+ .option('--misfire-policy <policy>', 'skip_stale | run_once_if_late | catch_up_with_limit | manual_review')
488
+ .option('--prompt-template <tpl>', 'render template instead of running raw command (daemon mode)')
489
+ .option('--deliver-only', 'daemon skips the agent loop on fire')
490
+ .action(async (action, posArgs, cmdObj) => {
491
+ const { runCronSubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/cron')));
492
+ const code = await runCronSubcommand(action, posArgs ?? [], cmdObj, {
493
+ writeOut: opts.writeOut,
494
+ });
495
+ process.exit(code);
496
+ });
497
+ // v4.5 Phase 6 — `aiden runs` surface (daemon run history).
498
+ program
499
+ .command('runs <action> [args...]')
500
+ .description('Daemon runs. Actions: list, show <id>, interrupt <id>, stats.')
501
+ .option('--limit <n>', 'list: max rows (default 50)', (v) => Number.parseInt(v, 10))
502
+ .option('--source <src>', 'list: filter by trigger source (file/webhook/email/schedule/manual)')
503
+ .option('--status <s>', 'list: filter by status (queued/running/completed/failed/cancelled/interrupted)')
504
+ .option('--trigger <prefix>', 'list: sessionId prefix (e.g. "trigger:file:<id>:")')
505
+ .action(async (action, posArgs, cmdObj) => {
506
+ const { runRunsSubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/runs')));
507
+ const code = await runRunsSubcommand(action, posArgs ?? [], cmdObj, {
508
+ writeOut: opts.writeOut,
509
+ });
510
+ process.exit(code);
511
+ });
321
512
  program
322
513
  .command('mcp <action>')
323
514
  .description('MCP server mode (Phase v4.1-mcp). Actions: serve, status, tools.')
@@ -475,6 +666,21 @@ async function buildAgentRuntime(cliOpts, opts) {
475
666
  }
476
667
  const config = new config_1.ConfigManager(paths);
477
668
  await config.load();
669
+ // v4.5 Phase 8a — initialise the runtimeToggles singleton with a
670
+ // ConfigManager seam so /sandbox /tce /browser-depth flips persist
671
+ // to config.yaml. Core modules (sandboxConfig, turnState,
672
+ // browserState) consult this singleton; precedence is
673
+ // env > config.yaml > default per Q-P8a-1(a).
674
+ {
675
+ const { initRuntimeToggles } = await Promise.resolve().then(() => __importStar(require('../../core/v4/runtimeToggles')));
676
+ initRuntimeToggles({
677
+ configRead: (key) => config.getValue(key),
678
+ configWriteAndSave: async (key, value) => {
679
+ config.set(key, value);
680
+ await config.save();
681
+ },
682
+ });
683
+ }
478
684
  // Phase 30.2 — fresh-user UX. Detection extends the old
479
685
  // `isFreshInstall`-only gate so we cover three new failure modes:
480
686
  // 1. fresh user with no env / no OAuth / no config → wizard fires
@@ -504,42 +710,63 @@ async function buildAgentRuntime(cliOpts, opts) {
504
710
  // consumed when building the adapter.
505
711
  let exploreMode = false;
506
712
  if (wizardNeeded) {
507
- if (!detection.hasAnyProvider) {
508
- // Truly empty: no env, no OAuth, no Ollama, no inline config.
509
- process.stdout.write(`\n${(0, providerDetection_1.summarizeDetection)(detection)}\n`);
510
- }
511
- else if (configuredProviderBroken) {
512
- // Config points at a provider we can't credential-resolve.
513
- process.stdout.write(`\nConfigured provider '${detection.configProvider}' has no usable credentials ` +
514
- `at ${node_path_1.default.join(paths.root, 'auth', `${detection.configProvider}.json`)}.\n`);
515
- }
516
- else {
517
- // Detected something (env / oauth / ollama) but config.yaml is
518
- // missing or empty — DEFAULT_CONFIG would route to anthropic and
519
- // the resolver would fail. Surface the detection so the user
520
- // sees what we found, then walk them through proper setup.
521
- process.stdout.write(`\n${(0, providerDetection_1.summarizeDetection)(detection)}\n`);
522
- process.stdout.write('config.yaml is empty — let\'s pick a provider that matches.\n');
523
- }
524
- process.stdout.write('Launching setup wizard…\n\n');
525
- const result = await (0, setupWizard_1.runSetupWizard)({ paths });
526
- // Phase 30.2.1: three exit states.
527
- if (result.status === 'exited') {
528
- // Recovery option [5] — clean exit, no REPL.
529
- process.exit(0);
530
- }
531
- if (result.status === 'skipped') {
532
- // Recovery option [4] "explore mode" OR Ctrl+C cancellation.
533
- // Boot continues into the REPL with a NullAdapter; chat is
534
- // intercepted by ChatSession, slash commands work normally.
535
- // Flagged here and consumed below where the adapter is built.
713
+ // v4.5 Phase 7c — TTY guard. The wizard uses inquirer prompts
714
+ // which block on stdin. When stdin is NOT a TTY (systemd unit
715
+ // start, launchd run, piped invocation, CI), there's no user
716
+ // to answer, so the wizard would hang forever — which in turn
717
+ // blocks the rest of buildAgentRuntime AND any post-build
718
+ // daemon wiring. Bail into exploreMode instead so the REPL
719
+ // boots with a NullAdapter; the daemon foundation (started at
720
+ // top of REPL action handler) keeps serving trigger rails with
721
+ // the placeholder runner. The operator runs `aiden` interactively
722
+ // once to finish configuration; from then on the real-agent
723
+ // runner installs on every subsequent boot.
724
+ if (!process.stdin.isTTY) {
725
+ process.stderr.write('[setup] stdin is not a TTY skipping interactive wizard. ' +
726
+ 'Booting in explore mode with NullAdapter. ' +
727
+ 'Run `aiden` from a terminal once to configure a provider.\n');
536
728
  exploreMode = true;
729
+ // Skip the wizard block entirely; fall through to adapter
730
+ // build with exploreMode=true so a NullAdapter is used.
537
731
  }
538
- // 'configured' (or 'skipped' — we still want the env/.env reload
539
- // for slash commands like /providers that read fresh state)
540
- // re-load both so the resolver sees what the wizard wrote.
541
- (0, envSources_1.loadAidenEnvFile)(paths.envFile);
542
- await config.load();
732
+ else {
733
+ if (!detection.hasAnyProvider) {
734
+ // Truly empty: no env, no OAuth, no Ollama, no inline config.
735
+ process.stdout.write(`\n${(0, providerDetection_1.summarizeDetection)(detection)}\n`);
736
+ }
737
+ else if (configuredProviderBroken) {
738
+ // Config points at a provider we can't credential-resolve.
739
+ process.stdout.write(`\nConfigured provider '${detection.configProvider}' has no usable credentials ` +
740
+ `at ${node_path_1.default.join(paths.root, 'auth', `${detection.configProvider}.json`)}.\n`);
741
+ }
742
+ else {
743
+ // Detected something (env / oauth / ollama) but config.yaml is
744
+ // missing or empty — DEFAULT_CONFIG would route to anthropic and
745
+ // the resolver would fail. Surface the detection so the user
746
+ // sees what we found, then walk them through proper setup.
747
+ process.stdout.write(`\n${(0, providerDetection_1.summarizeDetection)(detection)}\n`);
748
+ process.stdout.write('config.yaml is empty — let\'s pick a provider that matches.\n');
749
+ }
750
+ process.stdout.write('Launching setup wizard…\n\n');
751
+ const result = await (0, setupWizard_1.runSetupWizard)({ paths });
752
+ // Phase 30.2.1: three exit states.
753
+ if (result.status === 'exited') {
754
+ // Recovery option [5] — clean exit, no REPL.
755
+ process.exit(0);
756
+ }
757
+ if (result.status === 'skipped') {
758
+ // Recovery option [4] "explore mode" OR Ctrl+C cancellation.
759
+ // Boot continues into the REPL with a NullAdapter; chat is
760
+ // intercepted by ChatSession, slash commands work normally.
761
+ // Flagged here and consumed below where the adapter is built.
762
+ exploreMode = true;
763
+ }
764
+ // 'configured' (or 'skipped' — we still want the env/.env reload
765
+ // for slash commands like /providers that read fresh state) →
766
+ // re-load both so the resolver sees what the wizard wrote.
767
+ (0, envSources_1.loadAidenEnvFile)(paths.envFile);
768
+ await config.load();
769
+ } // end of TTY branch
543
770
  }
544
771
  // Phase v4.1.2-bug1: boot model selection now consults the priority-
545
772
  // list auto-picker (cli/v4/providerBootSelector.ts) instead of
@@ -726,35 +953,28 @@ async function buildAgentRuntime(cliOpts, opts) {
726
953
  else
727
954
  display.dim(ln.text);
728
955
  }
956
+ // v4.5 TUI polish — blank line so the plugin boot card breathes
957
+ // before the AIDEN banner stamps in below.
958
+ display.write('\n');
729
959
  // Phase 16g: surface the SOUL.md upgrade notice once on boot (only
730
960
  // when set — for users with edited SOUL.md that would have been
731
961
  // silently overwritten by the upgrade).
732
962
  if (soulNotice) {
733
963
  display.dim(`[soul] ${soulNotice}`);
734
964
  }
735
- // Phase 20 Task 5: non-blocking npm update check. Fires on a separate
736
- // microtask so REPL boot is unaffected. The cache hit path is sub-ms;
737
- // the cache miss path is bounded by REGISTRY_TIMEOUT_MS (4 s) and runs
738
- // after the prompt is already up. Honors AIDEN_NO_UPDATE_CHECK=1.
965
+ // v4.5 update system the old setImmediate dim/warn one-liner
966
+ // here was superseded by the interactive boot prompt rendered
967
+ // later inside `chatSession.run()::maybeShowBootUpdatePrompt`.
968
+ // Single surface for update notification: the boxed prompt.
969
+ // Pre-warm the registry probe so the cache is fresh by the time
970
+ // the prompt asks — same non-blocking pattern, same opt-out
971
+ // semantics, no user-visible output from this call.
739
972
  setImmediate(async () => {
740
973
  try {
741
- const { checkForUpdate, formatUpdateLine } = await Promise.resolve().then(() => __importStar(require('../../core/v4/update/checkUpdate')));
974
+ const { checkForUpdate } = await Promise.resolve().then(() => __importStar(require('../../core/v4/update/checkUpdate')));
742
975
  // eslint-disable-next-line @typescript-eslint/no-var-requires
743
976
  const pkg = require('../../package.json');
744
- const status = await checkForUpdate({
745
- paths,
746
- installedVersion: pkg.version,
747
- });
748
- const line = formatUpdateLine(status);
749
- if (line) {
750
- // Phase 20 Task 6: louder surfacing on first-ever boot when the
751
- // installed package is already behind. Subsequent boots stay
752
- // low-key (dim) — users have seen the line before.
753
- if (status.firstRun && status.updateAvailable)
754
- display.warn(line);
755
- else
756
- display.dim(line);
757
- }
977
+ await checkForUpdate({ paths, installedVersion: pkg.version });
758
978
  }
759
979
  catch {
760
980
  /* silent — update check is best-effort */
@@ -902,6 +1122,11 @@ async function buildAgentRuntime(cliOpts, opts) {
902
1122
  const recallSessionHealth = new subsystemHealth_1.SubsystemHealthTracker('recall-session');
903
1123
  subsystemHealthRegistry.register('recall-session', () => recallSessionHealth.snapshot());
904
1124
  const skillTeacher = new skillTeacher_1.SkillTeacher(skillLoader, skillManageProxy, skillTeacherTier, undefined, (name) => toolRegistry.get(name), skillTeacherHealth);
1125
+ // v4.1.6 Polish 2 — late-wire the SkillTeacher reference so the
1126
+ // post-render `handleSkillProposal` flow can persist accepted
1127
+ // proposals. CliCallbacks was constructed earlier (line ~906) —
1128
+ // before SkillTeacher existed — so we set it now.
1129
+ callbacks.setSkillTeacher(skillTeacher);
905
1130
  // ── Tool executor with full Phase 9 + 10 context ─────────────────────
906
1131
  const toolExecutor = toolRegistry.buildExecutor({
907
1132
  cwd: process.cwd(),
@@ -921,6 +1146,14 @@ async function buildAgentRuntime(cliOpts, opts) {
921
1146
  return typeof v === 'boolean' ? v : undefined;
922
1147
  };
923
1148
  const resolveToolset = (name) => toolRegistry.get(name)?.toolset;
1149
+ // v4.2 Phase 4 — checkpoint/restore mutability resolver. The agent's
1150
+ // Phase 4 hook calls this before dispatching each tool to decide
1151
+ // whether to flag the live checkpoint as having mutated state. Same
1152
+ // registry source as resolveToolset; closure captures the live
1153
+ // registry reference so newly-registered tools are seen. Unknown
1154
+ // tools return undefined → agent treats them as non-mutating (no
1155
+ // checkpoint flag); plugin authors must declare `mutates` honestly.
1156
+ const resolveMutates = (name) => toolRegistry.get(name)?.mutates;
924
1157
  // ── Phase 16b.4: assemble system-prompt context ─────────────────────
925
1158
  // PromptBuilder needs SOUL.md (read at build time from `paths.soulMd`),
926
1159
  // a frozen MemorySnapshot (loaded once at boot — same lifecycle as
@@ -1034,6 +1267,22 @@ async function buildAgentRuntime(cliOpts, opts) {
1034
1267
  skillOutcomeTracker.onTool(call, phase, result);
1035
1268
  }
1036
1269
  catch { /* telemetry must not break the turn */ }
1270
+ // v4.5 Phase 8b — pre-tool-call contextual suggestion. Fires
1271
+ // when the call would benefit from a v4.4/v4.5 subsystem the
1272
+ // user currently has off. Engine handles dismissal + budget;
1273
+ // we just render the tip and tell the engine it was shown.
1274
+ if (phase === 'before') {
1275
+ try {
1276
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1277
+ const { getSuggestionEngine } = require('../../core/v4/suggestionEngine');
1278
+ const tip = getSuggestionEngine().checkToolCall(call);
1279
+ if (tip) {
1280
+ display.dim(tip.message);
1281
+ getSuggestionEngine().recordFired(tip.slot);
1282
+ }
1283
+ }
1284
+ catch { /* never let a suggestion crash a tool call */ }
1285
+ }
1037
1286
  callbacks.onToolCall?.(call, phase, result);
1038
1287
  },
1039
1288
  onCompression: callbacks.onCompression,
@@ -1042,6 +1291,7 @@ async function buildAgentRuntime(cliOpts, opts) {
1042
1291
  skillTeacherCallbacks: { promptUser: callbacks.promptSkillProposal },
1043
1292
  resolveVerifiedFlag,
1044
1293
  resolveToolset,
1294
+ resolveMutates,
1045
1295
  providerId,
1046
1296
  modelId,
1047
1297
  // Phase 16b.4: wire PromptBuilder so SOUL.md actually reaches the LLM.
@@ -1113,6 +1363,28 @@ async function buildAgentRuntime(cliOpts, opts) {
1113
1363
  memoryManager.onMutation((file) => {
1114
1364
  agent.markMemoryDirty(file === 'user' ? 'user' : 'memory');
1115
1365
  });
1366
+ // v4.5 Phase 7b — daemon agent builder. Captures the references
1367
+ // above so the dispatcher can construct a fresh AidenAgent per
1368
+ // daemon-claimed trigger. Strategy B (closure capture) — REPL
1369
+ // construction stays untouched; we just expose the builder on
1370
+ // the returned AgentRuntime so runInteractiveChat can pass it to
1371
+ // bootstrapDaemon().
1372
+ const { buildDaemonAgentBuilder } = await Promise.resolve().then(() => __importStar(require('./daemonAgentBuilder')));
1373
+ const daemonAgentBuilder = buildDaemonAgentBuilder({
1374
+ paths,
1375
+ resolver,
1376
+ fallbackAdapter: adapter,
1377
+ toolRegistry,
1378
+ toolExecutor,
1379
+ auxiliaryClient,
1380
+ promptBuilder,
1381
+ promptBuilderOptions,
1382
+ memoryManager,
1383
+ resolveVerifiedFlag,
1384
+ resolveToolset,
1385
+ resolveMutates,
1386
+ maxTurns: config.getValue('agent.max_turns', 90),
1387
+ });
1116
1388
  // Phase v4.1.2 alive-core: SOUL.md file watcher. Best-effort —
1117
1389
  // some filesystems (network mounts, certain WSL configs) don't
1118
1390
  // support fs.watch reliably. We try to attach; if it fails, the
@@ -1489,10 +1761,36 @@ async function buildAgentRuntime(cliOpts, opts) {
1489
1761
  pluginLoader,
1490
1762
  exploreMode,
1491
1763
  channelManager,
1764
+ daemonAgentBuilder,
1492
1765
  };
1493
1766
  }
1494
1767
  async function runInteractiveChat(cliOpts, opts) {
1495
1768
  const runtime = await buildAgentRuntime(cliOpts, opts);
1769
+ // v4.5 Phase 7c — install the REAL agent runner now that the
1770
+ // REPL agent is built. The daemon foundation already came up
1771
+ // earlier (top of REPL action handler) with the Phase 5a
1772
+ // placeholder runner serving claims. This call atomically swaps
1773
+ // the dispatcher's runner from placeholder → real; the next
1774
+ // claim uses `runtime.daemonAgentBuilder` to construct a real
1775
+ // AidenAgent per fire.
1776
+ try {
1777
+ if (process.env.AIDEN_DAEMON === '1') {
1778
+ const { getDaemonHandle, installDaemonAgentBuilder } = await Promise.resolve().then(() => __importStar(require('../../core/v4/daemon/bootstrap')));
1779
+ const handle = getDaemonHandle();
1780
+ if (handle && handle.active) {
1781
+ const ok = installDaemonAgentBuilder(handle, runtime.daemonAgentBuilder, { provider: runtime.providerId, model: runtime.modelId });
1782
+ if (!ok) {
1783
+ console.warn('[daemon] real-runner install returned false — placeholder still active');
1784
+ }
1785
+ }
1786
+ }
1787
+ }
1788
+ catch (e) {
1789
+ // Fail-loud but non-fatal — install failure leaves the
1790
+ // placeholder runner active; the REPL still works and the
1791
+ // daemon's rails still serve triggers.
1792
+ console.error('[daemon] install agent builder failed: ' + (e instanceof Error ? e.message : String(e)));
1793
+ }
1496
1794
  const sessionOpts = {
1497
1795
  agent: runtime.agent,
1498
1796
  display: runtime.display,