aiden-runtime 4.1.5 → 4.6.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 (181) hide show
  1. package/README.md +265 -847
  2. package/dist/api/server.js +32 -5
  3. package/dist/cli/v4/aidenCLI.js +536 -152
  4. package/dist/cli/v4/callbacks.js +170 -0
  5. package/dist/cli/v4/chatSession.js +245 -3
  6. package/dist/cli/v4/commands/_runtimeToggleHelpers.js +94 -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/fanout.js +42 -59
  12. package/dist/cli/v4/commands/help.js +13 -0
  13. package/dist/cli/v4/commands/index.js +35 -1
  14. package/dist/cli/v4/commands/mcp.js +80 -54
  15. package/dist/cli/v4/commands/plannerGuard.js +53 -0
  16. package/dist/cli/v4/commands/recovery.js +122 -0
  17. package/dist/cli/v4/commands/runs.js +223 -0
  18. package/dist/cli/v4/commands/sandbox.js +48 -0
  19. package/dist/cli/v4/commands/spawnPause.js +93 -0
  20. package/dist/cli/v4/commands/suggestions.js +68 -0
  21. package/dist/cli/v4/commands/tce.js +41 -0
  22. package/dist/cli/v4/commands/trigger.js +378 -0
  23. package/dist/cli/v4/commands/update.js +95 -3
  24. package/dist/cli/v4/daemonAgentBuilder.js +145 -0
  25. package/dist/cli/v4/defaultSoul.js +1 -1
  26. package/dist/cli/v4/display/capabilityCard.js +26 -0
  27. package/dist/cli/v4/display.js +18 -8
  28. package/dist/cli/v4/replyRenderer.js +31 -23
  29. package/dist/cli/v4/updateBootPrompt.js +170 -0
  30. package/dist/core/playwrightBridge.js +129 -0
  31. package/dist/core/v4/aidenAgent.js +527 -5
  32. package/dist/core/v4/browserState.js +436 -0
  33. package/dist/core/v4/checkpoint.js +79 -0
  34. package/dist/core/v4/daemon/bootstrap.js +651 -0
  35. package/dist/core/v4/daemon/cleanShutdown.js +154 -0
  36. package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
  37. package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
  38. package/dist/core/v4/daemon/cron/migration.js +199 -0
  39. package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
  40. package/dist/core/v4/daemon/daemonConfig.js +90 -0
  41. package/dist/core/v4/daemon/db/connection.js +106 -0
  42. package/dist/core/v4/daemon/db/migrations.js +362 -0
  43. package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
  44. package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
  45. package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
  46. package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
  47. package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
  48. package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
  49. package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
  50. package/dist/core/v4/daemon/dispatcher/index.js +53 -0
  51. package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
  52. package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
  53. package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
  54. package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
  55. package/dist/core/v4/daemon/drain.js +156 -0
  56. package/dist/core/v4/daemon/eventLoopLag.js +73 -0
  57. package/dist/core/v4/daemon/health.js +159 -0
  58. package/dist/core/v4/daemon/idempotencyStore.js +204 -0
  59. package/dist/core/v4/daemon/index.js +179 -0
  60. package/dist/core/v4/daemon/instanceTracker.js +99 -0
  61. package/dist/core/v4/daemon/resourceRegistry.js +150 -0
  62. package/dist/core/v4/daemon/restartCode.js +32 -0
  63. package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
  64. package/dist/core/v4/daemon/runStore.js +144 -0
  65. package/dist/core/v4/daemon/runtimeLock.js +167 -0
  66. package/dist/core/v4/daemon/signals.js +50 -0
  67. package/dist/core/v4/daemon/supervisor.js +272 -0
  68. package/dist/core/v4/daemon/triggerBus.js +279 -0
  69. package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
  70. package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
  71. package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
  72. package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
  73. package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
  74. package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
  75. package/dist/core/v4/daemon/triggers/email/index.js +332 -0
  76. package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
  77. package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
  78. package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
  79. package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
  80. package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
  81. package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
  82. package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
  83. package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
  84. package/dist/core/v4/daemon/triggers/webhook.js +376 -0
  85. package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
  86. package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
  87. package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
  88. package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
  89. package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
  90. package/dist/core/v4/daemon/types.js +15 -0
  91. package/dist/core/v4/dockerSession.js +461 -0
  92. package/dist/core/v4/dryRun.js +117 -0
  93. package/dist/core/v4/failureClassifier.js +779 -0
  94. package/dist/core/v4/providerFallback.js +35 -2
  95. package/dist/core/v4/recoveryReport.js +449 -0
  96. package/dist/core/v4/runtimeToggles.js +214 -0
  97. package/dist/core/v4/sandboxConfig.js +285 -0
  98. package/dist/core/v4/sandboxFs.js +316 -0
  99. package/dist/core/v4/selfimprovement/recoveryStore.js +307 -0
  100. package/dist/core/v4/selfimprovement/signatureBuilder.js +158 -0
  101. package/dist/core/v4/subagent/childBuilder.js +391 -0
  102. package/dist/core/v4/subagent/fanout.js +75 -51
  103. package/dist/core/v4/subagent/spawnPause.js +191 -0
  104. package/dist/core/v4/subagent/spawnSubAgent.js +310 -0
  105. package/dist/core/v4/suggestionCatalog.js +41 -0
  106. package/dist/core/v4/suggestionEngine.js +210 -0
  107. package/dist/core/v4/toolRegistry.js +37 -3
  108. package/dist/core/v4/turnState.js +587 -0
  109. package/dist/core/v4/update/checkUpdate.js +63 -3
  110. package/dist/core/v4/update/installMethodDetect.js +115 -0
  111. package/dist/core/v4/update/registryClient.js +121 -0
  112. package/dist/core/v4/update/skipState.js +75 -0
  113. package/dist/core/v4/verifier.js +448 -0
  114. package/dist/core/version.js +1 -1
  115. package/dist/moat/plannerGuard.js +29 -0
  116. package/dist/providers/v4/anthropicAdapter.js +31 -3
  117. package/dist/providers/v4/chatCompletionsAdapter.js +26 -3
  118. package/dist/providers/v4/codexResponsesAdapter.js +25 -2
  119. package/dist/providers/v4/ollamaPromptToolsAdapter.js +57 -2
  120. package/dist/tools/v4/browser/_observer.js +224 -0
  121. package/dist/tools/v4/browser/browserBlocker.js +396 -0
  122. package/dist/tools/v4/browser/browserClick.js +18 -1
  123. package/dist/tools/v4/browser/browserClose.js +18 -1
  124. package/dist/tools/v4/browser/browserExtract.js +5 -1
  125. package/dist/tools/v4/browser/browserFill.js +17 -1
  126. package/dist/tools/v4/browser/browserGetUrl.js +5 -1
  127. package/dist/tools/v4/browser/browserNavigate.js +16 -1
  128. package/dist/tools/v4/browser/browserScreenshot.js +5 -1
  129. package/dist/tools/v4/browser/browserScroll.js +18 -1
  130. package/dist/tools/v4/browser/browserType.js +17 -1
  131. package/dist/tools/v4/browser/captchaCheck.js +5 -1
  132. package/dist/tools/v4/executeCode.js +1 -0
  133. package/dist/tools/v4/files/fileCopy.js +56 -2
  134. package/dist/tools/v4/files/fileDelete.js +38 -1
  135. package/dist/tools/v4/files/fileList.js +12 -1
  136. package/dist/tools/v4/files/fileMove.js +59 -2
  137. package/dist/tools/v4/files/filePatch.js +43 -1
  138. package/dist/tools/v4/files/fileRead.js +12 -1
  139. package/dist/tools/v4/files/fileWrite.js +41 -1
  140. package/dist/tools/v4/index.js +88 -61
  141. package/dist/tools/v4/memory/memoryAdd.js +14 -0
  142. package/dist/tools/v4/memory/memoryRemove.js +14 -0
  143. package/dist/tools/v4/memory/memoryReplace.js +15 -0
  144. package/dist/tools/v4/memory/sessionSummary.js +12 -0
  145. package/dist/tools/v4/process/processKill.js +19 -0
  146. package/dist/tools/v4/process/processList.js +1 -0
  147. package/dist/tools/v4/process/processLogRead.js +1 -0
  148. package/dist/tools/v4/process/processSpawn.js +13 -0
  149. package/dist/tools/v4/process/processWait.js +1 -0
  150. package/dist/tools/v4/sessions/recallSession.js +1 -0
  151. package/dist/tools/v4/sessions/sessionList.js +1 -0
  152. package/dist/tools/v4/sessions/sessionSearch.js +1 -0
  153. package/dist/tools/v4/skills/lookupToolSchema.js +7 -0
  154. package/dist/tools/v4/skills/skillManage.js +13 -0
  155. package/dist/tools/v4/skills/skillView.js +1 -0
  156. package/dist/tools/v4/skills/skillsList.js +1 -0
  157. package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
  158. package/dist/tools/v4/subagent/subagentFanout.js +54 -1
  159. package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
  160. package/dist/tools/v4/system/appClose.js +13 -0
  161. package/dist/tools/v4/system/appInput.js +13 -0
  162. package/dist/tools/v4/system/appLaunch.js +13 -0
  163. package/dist/tools/v4/system/clipboardRead.js +1 -0
  164. package/dist/tools/v4/system/clipboardWrite.js +14 -0
  165. package/dist/tools/v4/system/mediaKey.js +12 -0
  166. package/dist/tools/v4/system/mediaSessions.js +1 -0
  167. package/dist/tools/v4/system/mediaTransport.js +13 -0
  168. package/dist/tools/v4/system/naturalEvents.js +1 -0
  169. package/dist/tools/v4/system/nowPlaying.js +1 -0
  170. package/dist/tools/v4/system/osProcessList.js +1 -0
  171. package/dist/tools/v4/system/screenshot.js +1 -0
  172. package/dist/tools/v4/system/systemInfo.js +1 -0
  173. package/dist/tools/v4/system/volumeSet.js +17 -0
  174. package/dist/tools/v4/terminal/shellExec.js +81 -9
  175. package/dist/tools/v4/web/deepResearch.js +1 -0
  176. package/dist/tools/v4/web/openUrl.js +1 -0
  177. package/dist/tools/v4/web/webFetch.js +1 -0
  178. package/dist/tools/v4/web/webPage.js +1 -0
  179. package/dist/tools/v4/web/webSearch.js +1 -0
  180. package/dist/tools/v4/web/youtubeSearch.js +1 -0
  181. package/package.json +13 -3
@@ -0,0 +1,378 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 Shiva Deore (Taracod).
4
+ * Licensed under AGPL-3.0. See LICENSE for details.
5
+ *
6
+ * Aiden — local-first agent.
7
+ */
8
+ /**
9
+ * cli/v4/commands/trigger.ts — v4.5 Phase 2: `aiden trigger` command set.
10
+ *
11
+ * Subcommands:
12
+ * aiden trigger add file --path <p> --name <n> [opts]
13
+ * Writes a `triggers` row with source='file' and a normalized
14
+ * spec_json. Daemon restart picks it up on next boot. Phase 5
15
+ * will add hot-reload.
16
+ *
17
+ * aiden trigger list — show all triggers + status
18
+ * aiden trigger show <id> — full spec + stats
19
+ * aiden trigger remove <id> — delete from triggers + cascade
20
+ * aiden trigger enable <id> — set triggers.enabled = 1
21
+ * aiden trigger disable <id> — set triggers.enabled = 0
22
+ * aiden trigger test <id> — fire a synthetic event
23
+ */
24
+ var __importDefault = (this && this.__importDefault) || function (mod) {
25
+ return (mod && mod.__esModule) ? mod : { "default": mod };
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.runTriggerSubcommand = runTriggerSubcommand;
29
+ const node_path_1 = __importDefault(require("node:path"));
30
+ const node_crypto_1 = require("node:crypto");
31
+ const daemon_1 = require("../../../core/v4/daemon");
32
+ const paths_1 = require("../../../core/v4/paths");
33
+ const noopOut = (s) => { process.stdout.write(s); };
34
+ const noopErr = (s) => { process.stderr.write(s); };
35
+ /**
36
+ * Run an `aiden trigger <action>` invocation. Returns the desired
37
+ * process exit code so the CLI top-level can propagate.
38
+ */
39
+ async function runTriggerSubcommand(action, args, argv, opts = {}) {
40
+ const out = opts.writeOut ?? noopOut;
41
+ const err = opts.writeErr ?? noopErr;
42
+ const aidenRoot = (0, paths_1.resolveAidenRoot)();
43
+ const db = (0, daemon_1.openDaemonDb)((0, daemon_1.daemonDbPath)(aidenRoot));
44
+ switch (action) {
45
+ case 'add': {
46
+ const kind = args[0];
47
+ if (kind === 'webhook') {
48
+ return runAddWebhook(db, argv, out, err);
49
+ }
50
+ if (kind === 'email') {
51
+ return runAddEmail(db, argv, out, err);
52
+ }
53
+ if (kind !== 'file') {
54
+ err(`trigger add: 'file', 'webhook', or 'email' kind required (got: ${kind ?? '<none>'})\n`);
55
+ return 2;
56
+ }
57
+ const a = argv;
58
+ if (!a.name || !a.paths || a.paths.length === 0) {
59
+ err('trigger add file: --name and at least one --path required\n');
60
+ return 2;
61
+ }
62
+ const spec = (0, daemon_1.parseFileWatcherSpec)({
63
+ paths: a.paths,
64
+ recursive: daemon_1.DEFAULT_FILE_WATCHER_SPEC.recursive,
65
+ includeGlobs: a.include,
66
+ excludeGlobs: a.exclude,
67
+ eventTypes: a.events,
68
+ debounceMs: a.debounceMs ?? daemon_1.DEFAULT_FILE_WATCHER_SPEC.debounceMs,
69
+ settleMs: a.settleMs ?? daemon_1.DEFAULT_FILE_WATCHER_SPEC.settleMs,
70
+ maxSettleMs: a.maxSettleMs ?? daemon_1.DEFAULT_FILE_WATCHER_SPEC.maxSettleMs,
71
+ maxQueueDepth: a.maxQueueDepth ?? daemon_1.DEFAULT_FILE_WATCHER_SPEC.maxQueueDepth,
72
+ ignoreTemp: a.noIgnoreTemp ? false : daemon_1.DEFAULT_FILE_WATCHER_SPEC.ignoreTemp,
73
+ contentHash: a.contentHash === true,
74
+ reconcile: a.reconcile ?? daemon_1.DEFAULT_FILE_WATCHER_SPEC.reconcile,
75
+ polling: a.polling === true ? { enabled: true } : undefined,
76
+ promptTemplate: a.promptTemplate,
77
+ });
78
+ // Resolve paths to absolute upfront so the watcher sees stable inputs.
79
+ spec.paths = spec.paths.map((p) => node_path_1.default.resolve(p));
80
+ const id = (0, node_crypto_1.randomUUID)();
81
+ const now = Date.now();
82
+ db.prepare(`INSERT INTO triggers
83
+ (id, source, name, spec_json, enabled, prompt_template, deliver_only,
84
+ created_at, updated_at)
85
+ VALUES (?, 'file', ?, ?, ?, ?, 0, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, now, now);
86
+ out(`trigger added: ${id} (${a.name})\n`);
87
+ out('Restart the daemon to activate the watcher.\n');
88
+ return 0;
89
+ }
90
+ case 'list': {
91
+ const rows = db.prepare(`SELECT id, source, name, enabled, created_at FROM triggers ORDER BY created_at DESC`).all();
92
+ if (rows.length === 0) {
93
+ out('No triggers registered.\n');
94
+ return 0;
95
+ }
96
+ for (const r of rows) {
97
+ const status = r.enabled ? 'enabled' : 'disabled';
98
+ out(`${r.id} ${r.source.padEnd(8)} ${status.padEnd(9)} ${r.name}\n`);
99
+ }
100
+ return 0;
101
+ }
102
+ case 'show': {
103
+ const id = args[0];
104
+ if (!id) {
105
+ err('trigger show: id required\n');
106
+ return 2;
107
+ }
108
+ const row = db.prepare('SELECT * FROM triggers WHERE id = ?').get(id);
109
+ if (!row) {
110
+ err(`trigger show: not found: ${id}\n`);
111
+ return 1;
112
+ }
113
+ out(JSON.stringify({
114
+ id: row.id, source: row.source, name: row.name,
115
+ enabled: row.enabled === 1, created_at: row.created_at,
116
+ spec: JSON.parse(row.spec_json),
117
+ }, null, 2) + '\n');
118
+ return 0;
119
+ }
120
+ case 'remove': {
121
+ const id = args[0];
122
+ if (!id) {
123
+ err('trigger remove: id required\n');
124
+ return 2;
125
+ }
126
+ const r = db.prepare('DELETE FROM triggers WHERE id = ?').run(id);
127
+ if (r.changes === 0) {
128
+ err(`trigger remove: not found: ${id}\n`);
129
+ return 1;
130
+ }
131
+ out(`trigger removed: ${id}\n`);
132
+ return 0;
133
+ }
134
+ case 'enable':
135
+ case 'disable': {
136
+ const id = args[0];
137
+ if (!id) {
138
+ err(`trigger ${action}: id required\n`);
139
+ return 2;
140
+ }
141
+ const r = db.prepare('UPDATE triggers SET enabled = ?, updated_at = ? WHERE id = ?')
142
+ .run(action === 'enable' ? 1 : 0, Date.now(), id);
143
+ if (r.changes === 0) {
144
+ err(`trigger ${action}: not found: ${id}\n`);
145
+ return 1;
146
+ }
147
+ out(`trigger ${action}d: ${id}\n`);
148
+ return 0;
149
+ }
150
+ case 'test': {
151
+ const id = args[0];
152
+ if (!id) {
153
+ err('trigger test: id required\n');
154
+ return 2;
155
+ }
156
+ const row = db.prepare('SELECT source FROM triggers WHERE id = ?').get(id);
157
+ if (!row) {
158
+ err(`trigger test: not found: ${id}\n`);
159
+ return 1;
160
+ }
161
+ const bus = (0, daemon_1.createTriggerBus)({ db });
162
+ const result = bus.insert({
163
+ source: row.source,
164
+ sourceKey: id,
165
+ payload: { synthetic: true, test: true, source: row.source },
166
+ });
167
+ out(`trigger test event inserted: id=${result.id} inserted=${result.inserted}\n`);
168
+ return 0;
169
+ }
170
+ case 'logs': {
171
+ // v4.5 Phase 6 — tail recent run_events for runs whose sessionId
172
+ // starts with `trigger:<source>:<id>:`. Surfaces what the agent
173
+ // did on each fire (or what the deliverOnly stub logged).
174
+ const id = args[0];
175
+ if (!id) {
176
+ err('trigger logs: id required\n');
177
+ return 2;
178
+ }
179
+ const trig = db.prepare('SELECT id, source, name FROM triggers WHERE id = ?').get(id);
180
+ if (!trig) {
181
+ err(`trigger logs: not found: ${id}\n`);
182
+ return 1;
183
+ }
184
+ const prefix = `trigger:${trig.source}:${id}:`;
185
+ const rows = db.prepare(`SELECT re.ts, re.kind, re.payload, r.id AS run_id
186
+ FROM run_events re
187
+ JOIN runs r ON re.run_id = r.id
188
+ WHERE r.session_id LIKE ?
189
+ ORDER BY re.ts DESC
190
+ LIMIT 50`).all(`${prefix}%`);
191
+ if (rows.length === 0) {
192
+ out(`No run events recorded for trigger ${id} (${trig.name}).\n`);
193
+ return 0;
194
+ }
195
+ out(`Last ${rows.length} event(s) for trigger ${id} (${trig.name}):\n`);
196
+ for (const r of rows.reverse()) { // chronological order for tail-like output
197
+ const ts = new Date(r.ts).toISOString().slice(0, 19) + 'Z';
198
+ const payloadStr = r.payload.length > 120 ? r.payload.slice(0, 120) + '…' : r.payload;
199
+ out(` [${ts}] run=${r.run_id} ${r.kind.padEnd(20)} ${payloadStr}\n`);
200
+ }
201
+ return 0;
202
+ }
203
+ case 'runs': {
204
+ // v4.5 Phase 6 — list runs that originated from this trigger.
205
+ const id = args[0];
206
+ if (!id) {
207
+ err('trigger runs: id required\n');
208
+ return 2;
209
+ }
210
+ const trig = db.prepare('SELECT id, source, name FROM triggers WHERE id = ?').get(id);
211
+ if (!trig) {
212
+ err(`trigger runs: not found: ${id}\n`);
213
+ return 1;
214
+ }
215
+ const prefix = `trigger:${trig.source}:${id}:`;
216
+ const rows = db.prepare(`SELECT id, status, finish_reason, started_at, completed_at
217
+ FROM runs
218
+ WHERE session_id LIKE ?
219
+ ORDER BY started_at DESC
220
+ LIMIT 50`).all(`${prefix}%`);
221
+ if (rows.length === 0) {
222
+ out(`No runs recorded for trigger ${id} (${trig.name}).\n`);
223
+ return 0;
224
+ }
225
+ out(`${'runId'.padEnd(6)} ${'status'.padEnd(11)} ${'finish'.padEnd(11)} started\n`);
226
+ for (const r of rows) {
227
+ const started = new Date(r.started_at).toISOString().slice(0, 19) + 'Z';
228
+ out(`${String(r.id).padEnd(6)} ${r.status.padEnd(11)} ${(r.finish_reason ?? '-').padEnd(11)} ${started}\n`);
229
+ }
230
+ out(`\n${rows.length} run${rows.length === 1 ? '' : 's'} for trigger ${id}\n`);
231
+ return 0;
232
+ }
233
+ default:
234
+ err(`Unknown trigger action: ${action}\n`);
235
+ err('Actions: add, list, show <id>, remove <id>, enable <id>, disable <id>, test <id>, logs <id>, runs <id>\n');
236
+ return 2;
237
+ }
238
+ }
239
+ function runAddWebhook(db, argv, out, err) {
240
+ const a = argv;
241
+ if (!a.name) {
242
+ err('trigger add webhook: --name required\n');
243
+ return 2;
244
+ }
245
+ // Generate the secret if the user didn't supply one — 32 bytes
246
+ // base64url-encoded (43-character URL-safe string).
247
+ const secret = a.secret && a.secret.length > 0
248
+ ? a.secret
249
+ : (0, node_crypto_1.randomBytes)(32).toString('base64url');
250
+ const spec = (0, daemon_1.parseWebhookSpec)({
251
+ name: a.name,
252
+ secret,
253
+ hmacFormat: a.hmac ?? daemon_1.DEFAULT_WEBHOOK_SPEC.hmacFormat,
254
+ allowedEvents: a.events,
255
+ rateLimit: { perMinute: a.rateLimit ?? daemon_1.DEFAULT_WEBHOOK_SPEC.rateLimit.perMinute },
256
+ maxBodyBytes: a.maxBodyBytes ?? daemon_1.DEFAULT_WEBHOOK_SPEC.maxBodyBytes,
257
+ idempotencyTtlMs: a.idempotencyTtlMs ?? daemon_1.DEFAULT_WEBHOOK_SPEC.idempotencyTtlMs,
258
+ deliverOnly: a.deliverOnly === true,
259
+ promptTemplate: a.promptTemplate,
260
+ publicBound: false,
261
+ });
262
+ const id = (0, node_crypto_1.randomUUID)();
263
+ const now = Date.now();
264
+ db.prepare(`INSERT INTO triggers
265
+ (id, source, name, spec_json, enabled, prompt_template, deliver_only,
266
+ created_at, updated_at)
267
+ VALUES (?, 'webhook', ?, ?, ?, ?, ?, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, spec.deliverOnly ? 1 : 0, now, now);
268
+ const cfg = (0, daemon_1.getDaemonConfig)();
269
+ const host = process.env.AIDEN_DAEMON_BIND ?? '127.0.0.1';
270
+ out(`trigger added: ${id} (${a.name})\n`);
271
+ out(`webhook url: http://${host}:${cfg.port}/api/triggers/webhook/${id}\n`);
272
+ out(`hmac format: ${spec.hmacFormat}\n`);
273
+ out(`rate limit: ${spec.rateLimit.perMinute}/min\n`);
274
+ out(`secret: ${secret}\n`);
275
+ out(`⚠ Save this secret now — it cannot be retrieved later.\n`);
276
+ out(`Restart the daemon to activate the route.\n`);
277
+ return 0;
278
+ }
279
+ async function runAddEmail(db, argv, out, err) {
280
+ const a = argv;
281
+ if (!a.name) {
282
+ err('trigger add email: --label required\n');
283
+ return 2;
284
+ }
285
+ if (!a.host) {
286
+ err('trigger add email: --host required\n');
287
+ return 2;
288
+ }
289
+ if (!a.user) {
290
+ err('trigger add email: --user required\n');
291
+ return 2;
292
+ }
293
+ if (!a.password) {
294
+ err('trigger add email: --password required\n');
295
+ return 2;
296
+ }
297
+ if (!a.allowSenders || a.allowSenders.length === 0) {
298
+ err('trigger add email: at least one --allow-sender required.\n' +
299
+ ' Examples: --allow-sender "user@example.com"\n' +
300
+ ' --allow-sender "*@taracod.com"\n');
301
+ return 2;
302
+ }
303
+ // Build + validate the spec (this also compile-checks subject regexes).
304
+ let spec;
305
+ try {
306
+ spec = (0, daemon_1.parseEmailSpec)({
307
+ name: a.name,
308
+ imap: {
309
+ host: a.host,
310
+ port: a.port ?? daemon_1.DEFAULT_IMAP.port,
311
+ user: a.user,
312
+ password: a.password,
313
+ tls: a.noTls ? false : daemon_1.DEFAULT_IMAP.tls,
314
+ authTimeoutMs: daemon_1.DEFAULT_IMAP.authTimeoutMs,
315
+ },
316
+ mailbox: a.mailbox ?? daemon_1.DEFAULT_EMAIL_SPEC.mailbox,
317
+ pollIntervalMs: a.pollMs ?? daemon_1.DEFAULT_EMAIL_SPEC.pollIntervalMs,
318
+ allowedSenders: a.allowSenders,
319
+ allowedSubjectPatterns: a.allowSubjects,
320
+ maxBodyBytes: a.maxBodyBytes ?? daemon_1.DEFAULT_EMAIL_SPEC.maxBodyBytes,
321
+ promptTemplate: a.promptTemplate,
322
+ deliverOnly: a.deliverOnly === true,
323
+ attachmentPolicy: a.attachmentPolicy ?? daemon_1.DEFAULT_EMAIL_SPEC.attachmentPolicy,
324
+ });
325
+ }
326
+ catch (e) {
327
+ err(`trigger add email: ${e instanceof Error ? e.message : String(e)}\n`);
328
+ return 2;
329
+ }
330
+ // Q-P4-5 default: validate at add-time. Opt out with --no-validate.
331
+ if (!a.noValidate) {
332
+ out('Validating IMAP connectivity ...\n');
333
+ const conn = (0, daemon_1.createImapConnection)({
334
+ config: spec.imap,
335
+ log: (level, msg) => { if (level === 'error')
336
+ err(msg + '\n'); },
337
+ });
338
+ try {
339
+ await conn.connect();
340
+ try {
341
+ await conn.openMailbox(spec.mailbox);
342
+ }
343
+ catch (e) {
344
+ err(`IMAP connectivity validated, but mailbox open failed: ${e instanceof Error ? e.message : String(e)}\n`);
345
+ await conn.disconnect();
346
+ return 1;
347
+ }
348
+ await conn.disconnect();
349
+ out(' ✓ connected, authenticated, mailbox opened\n');
350
+ }
351
+ catch (e) {
352
+ err(`IMAP connection failed: ${e instanceof Error ? e.message : String(e)}\n`);
353
+ err('Use --no-validate to skip the pre-flight check and add the trigger anyway.\n');
354
+ return 1;
355
+ }
356
+ }
357
+ else {
358
+ out('Skipping IMAP connectivity validation (--no-validate).\n');
359
+ }
360
+ const id = (0, node_crypto_1.randomUUID)();
361
+ const now = Date.now();
362
+ db.prepare(`INSERT INTO triggers
363
+ (id, source, name, spec_json, enabled, prompt_template, deliver_only,
364
+ created_at, updated_at)
365
+ VALUES (?, 'email', ?, ?, ?, ?, ?, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, spec.deliverOnly ? 1 : 0, now, now);
366
+ out(`trigger added: ${id} (${a.name})\n`);
367
+ out(`imap host: ${spec.imap.host}:${spec.imap.port}${spec.imap.tls ? ' (TLS)' : ''}\n`);
368
+ out(`mailbox: ${spec.mailbox}\n`);
369
+ out(`poll interval: ${spec.pollIntervalMs}ms\n`);
370
+ out(`allow-senders: ${spec.allowedSenders.join(', ')}\n`);
371
+ out(`⚠ Password stored in plaintext in daemon.db (chmod 600 on POSIX,\n`);
372
+ out(` user-private on Windows). Encryption-at-rest is deferred to v4.6+.\n`);
373
+ out(`Restart the daemon to activate the trigger.\n`);
374
+ // Note: runAddEmail returns a Promise<number>, so the outer switch must
375
+ // await it. (Already handled — runTriggerSubcommand is async.)
376
+ void node_crypto_1.randomBytes; // imported but only used by webhook helper
377
+ return 0;
378
+ }
@@ -26,6 +26,8 @@ exports.update = void 0;
26
26
  const version_1 = require("../../../core/version");
27
27
  const checkUpdate_1 = require("../../../core/v4/update/checkUpdate");
28
28
  const executeInstall_1 = require("../../../core/v4/update/executeInstall");
29
+ const installMethodDetect_1 = require("../../../core/v4/update/installMethodDetect");
30
+ const skipState_1 = require("../../../core/v4/update/skipState");
29
31
  async function printStatus(ctx) {
30
32
  if (!ctx.paths) {
31
33
  ctx.display.warn('/update needs Aiden user-data paths — try in a real session.');
@@ -85,18 +87,108 @@ async function runInstall(ctx) {
85
87
  }
86
88
  ctx.display.warn(result.error ?? 'Install failed (no error message).');
87
89
  }
90
+ // ── v4.5 update system — skip + auto subcommands ───────────────────────────
91
+ async function runSkip(ctx) {
92
+ if (!ctx.paths) {
93
+ ctx.display.warn('/update skip needs Aiden user-data paths — try in a real session.');
94
+ return;
95
+ }
96
+ const version = ctx.args[1];
97
+ if (!version || version.trim().length === 0) {
98
+ ctx.display.printError('Usage: /update skip <version>\n' +
99
+ 'Example: /update skip 4.5.1\n' +
100
+ 'Suppresses the boot prompt for that version + any older. Newer versions still prompt.');
101
+ return;
102
+ }
103
+ // Reject obviously-bad inputs early so users get a clear error.
104
+ try {
105
+ (0, checkUpdate_1.compareVersions)(version, version);
106
+ }
107
+ catch {
108
+ ctx.display.printError(`/update skip: "${version}" is not a recognised version (expected MAJOR.MINOR.PATCH).`);
109
+ return;
110
+ }
111
+ try {
112
+ await (0, checkUpdate_1.updateCacheFile)(ctx.paths, (current) => (0, skipState_1.applySkip)(current, version));
113
+ ctx.display.write(`Skipping ${version}. Boot prompt will resume when a newer version ships.\n`);
114
+ }
115
+ catch (e) {
116
+ ctx.display.warn(`/update skip: failed to persist (${e instanceof Error ? e.message : String(e)}).`);
117
+ }
118
+ }
119
+ async function runAuto(ctx) {
120
+ const sub = (ctx.args[1] ?? 'status').toLowerCase();
121
+ if (sub === 'status') {
122
+ const off = process.env.AIDEN_NO_UPDATE_CHECK === '1';
123
+ ctx.display.write(`Update auto-check: ${off ? 'OFF' : 'ON'} (source: ${off ? 'env' : 'default'})\n`);
124
+ if (off) {
125
+ ctx.display.dim(' unset AIDEN_NO_UPDATE_CHECK or run `/update auto on` to re-enable.');
126
+ }
127
+ return;
128
+ }
129
+ if (sub === 'on' || sub === 'off') {
130
+ // The env var is the authoritative gate (matches existing Phase 20
131
+ // contract). `/update auto on` clears it for the current process;
132
+ // permanent off needs the user's shell to keep it set, since we
133
+ // shouldn't quietly write env vars to user shells. Document this
134
+ // clearly so users aren't confused.
135
+ if (sub === 'on') {
136
+ delete process.env.AIDEN_NO_UPDATE_CHECK;
137
+ ctx.display.write('Update auto-check: ON for this session.\n');
138
+ ctx.display.dim(' To persist: ensure AIDEN_NO_UPDATE_CHECK is unset in your shell init.');
139
+ }
140
+ else {
141
+ process.env.AIDEN_NO_UPDATE_CHECK = '1';
142
+ ctx.display.write('Update auto-check: OFF for this session.\n');
143
+ ctx.display.dim(' To persist: set AIDEN_NO_UPDATE_CHECK=1 in your shell init.');
144
+ }
145
+ return;
146
+ }
147
+ ctx.display.printError('Usage: /update auto on|off|status');
148
+ }
149
+ async function runClearSkip(ctx) {
150
+ if (!ctx.paths) {
151
+ ctx.display.warn('/update unskip needs Aiden user-data paths — try in a real session.');
152
+ return;
153
+ }
154
+ try {
155
+ await (0, checkUpdate_1.updateCacheFile)(ctx.paths, (current) => (0, skipState_1.clearSkip)(current));
156
+ ctx.display.write('Cleared skipped-version state. The boot prompt will re-fire next session.\n');
157
+ }
158
+ catch (e) {
159
+ ctx.display.warn(`/update unskip: failed to persist (${e instanceof Error ? e.message : String(e)}).`);
160
+ }
161
+ }
88
162
  exports.update = {
89
163
  name: 'update',
90
- description: 'Check for / install the latest aiden-runtime. Use "install" subcommand to apply.',
164
+ description: 'Check, install, or skip aiden-runtime updates.',
91
165
  category: 'system',
92
166
  icon: '⬆',
93
167
  handler: async (ctx) => {
94
168
  const sub = (ctx.args[0] ?? '').toLowerCase();
169
+ // Display install method on `/update` default so users see how
170
+ // their install will be updated before they trigger one.
95
171
  if (sub === 'install') {
96
172
  await runInstall(ctx);
173
+ return;
97
174
  }
98
- else {
99
- await printStatus(ctx);
175
+ if (sub === 'skip') {
176
+ await runSkip(ctx);
177
+ return;
178
+ }
179
+ if (sub === 'unskip') {
180
+ await runClearSkip(ctx);
181
+ return;
182
+ }
183
+ if (sub === 'auto') {
184
+ await runAuto(ctx);
185
+ return;
186
+ }
187
+ // Default ('' / 'check') → status probe.
188
+ await printStatus(ctx);
189
+ if (ctx.paths) {
190
+ const method = (0, installMethodDetect_1.detectInstallMethod)();
191
+ ctx.display.dim(` install method: ${method.description}`);
100
192
  }
101
193
  },
102
194
  };
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 Shiva Deore (Taracod).
4
+ * Licensed under AGPL-3.0. See LICENSE for details.
5
+ *
6
+ * Aiden — local-first agent.
7
+ */
8
+ /**
9
+ * cli/v4/daemonAgentBuilder.ts — v4.5 Phase 7b.
10
+ *
11
+ * Builds the `AgentBuilder` closure the daemon dispatcher uses to
12
+ * construct a fresh AidenAgent per claimed trigger event. Captures
13
+ * the REPL's already-initialized provider resolver, tool registry,
14
+ * auxiliary client, prompt builder, and memory manager — REPL and
15
+ * daemon turns share these instances per Q-P7b-2(a). State that
16
+ * MUST stay isolated (memoryDirty Set, cachedSystemPrompt) lives
17
+ * on AidenAgent instances, and the closure creates a fresh agent
18
+ * every time the dispatcher invokes it.
19
+ *
20
+ * Scope cuts deliberately deferred (per Phase 7b audit greenlight):
21
+ * - plannerGuard / honestyEnforcement / skillTeacher / skillMiner
22
+ * are NOT wired into daemon-mode agents. They add ~3 LLM calls
23
+ * per turn and the daemon's job is "act on the trigger," not
24
+ * "improve the agent." Opt-in per-trigger lands in v4.6 if
25
+ * real-world use surfaces a need.
26
+ * - REPL-only spinner hooks (onMemoryRefreshStart/onPromptBuilt/
27
+ * onProviderRequestStart) are omitted; daemon has no display
28
+ * surface.
29
+ * - skillTeacherCallbacks.promptUser is omitted; daemon has no
30
+ * operator to ask.
31
+ *
32
+ * Strategy B (closure capture) — chosen over Strategy A (full
33
+ * factory refactor) to keep the REPL's existing agent construction
34
+ * untouched. The risk of regression in the REPL path is minimised
35
+ * because we don't rewrite ANY of the REPL's setup — the daemon
36
+ * closure just constructs a SECOND lightweight agent on demand
37
+ * from the same building blocks.
38
+ */
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.buildDaemonAgentBuilder = buildDaemonAgentBuilder;
41
+ const aidenAgent_1 = require("../../core/v4/aidenAgent");
42
+ const approvalEngine_1 = require("../../moat/approvalEngine");
43
+ // ── Implementation ─────────────────────────────────────────────────────────
44
+ const DEFAULT_MAX_TURNS = 90;
45
+ /**
46
+ * Returns the AgentBuilder the daemon dispatcher consumes. The
47
+ * returned closure is invoked once per trigger event the
48
+ * dispatcher claims; each call constructs a fresh AidenAgent with
49
+ * daemon-flavored options.
50
+ */
51
+ function buildDaemonAgentBuilder(deps) {
52
+ const log = deps.log ?? ((msg) => process.stderr.write(msg + '\n'));
53
+ const maxTurns = deps.maxTurns ?? DEFAULT_MAX_TURNS;
54
+ return async (input) => {
55
+ const turnStartMs = Date.now();
56
+ // Resolve a provider adapter for the chosen (provider, model)
57
+ // pair. The resolver's resolve() returns an adapter wrapper;
58
+ // the underlying connection pool is shared per Q-P7b-2(a).
59
+ // If resolution fails (model not configured, OAuth expired),
60
+ // fall back to the REPL's existing adapter so the daemon turn
61
+ // still runs against SOMETHING usable.
62
+ let adapter;
63
+ try {
64
+ adapter = await deps.resolver.resolve({
65
+ providerId: input.resolvedModel.provider,
66
+ modelId: input.resolvedModel.model,
67
+ paths: deps.paths,
68
+ });
69
+ }
70
+ catch {
71
+ adapter = deps.fallbackAdapter;
72
+ }
73
+ // Approval engine — fresh per turn so the session-scoped
74
+ // allowlist doesn't bleed across daemon turns.
75
+ const approvalEngine = new approvalEngine_1.ApprovalEngine('smart');
76
+ approvalEngine['callbacks'] = input.approvalCallbacks;
77
+ // Per-turn promptBuilderOptions — same snapshot the REPL uses,
78
+ // only the providerId/modelId fields overridden to reflect the
79
+ // daemon's resolved model. The MemoryManager is shared (read-
80
+ // only access via loadSnapshot()), so memory-dirty propagation
81
+ // is the REPL agent's concern; daemon agent reads a fresh
82
+ // snapshot on each `runConversation` call and discards.
83
+ const pbOpts = {
84
+ ...deps.promptBuilderOptions,
85
+ providerId: input.resolvedModel.provider,
86
+ modelId: input.resolvedModel.model,
87
+ };
88
+ const agent = new aidenAgent_1.AidenAgent({
89
+ provider: adapter,
90
+ // v4.6 Phase 1 — 'daemon' context filter excludes REPL-only
91
+ // tools (`spawn_sub_agent` per Q6). Tools without an explicit
92
+ // `contexts` field stay visible to both REPL and daemon.
93
+ tools: deps.toolRegistry.getSchemas(undefined, 'daemon'),
94
+ toolExecutor: deps.toolExecutor,
95
+ maxTurns,
96
+ auxiliaryClient: deps.auxiliaryClient,
97
+ // v4.5 Phase 7 — explicit sessionId option threads per-trigger
98
+ // keying through to v4.4 docker session reuse + v4.3 browser
99
+ // observer + v4.2 TurnState.
100
+ sessionId: input.sessionId,
101
+ // Daemon mode wires the dispatcher's run_events hooks here.
102
+ // No display-side onToolCall wrapping — the dispatcher's
103
+ // emission is the only consumer.
104
+ onToolCall: input.hooks.onToolCall,
105
+ onBudgetWarning: input.hooks.onBudgetWarning,
106
+ promptBuilder: deps.promptBuilder,
107
+ promptBuilderOptions: pbOpts,
108
+ providerId: input.resolvedModel.provider,
109
+ modelId: input.resolvedModel.model,
110
+ resolveVerifiedFlag: deps.resolveVerifiedFlag,
111
+ resolveToolset: deps.resolveToolset,
112
+ resolveMutates: deps.resolveMutates,
113
+ // Memory snapshot refresh — daemon agent doesn't track dirty
114
+ // bits because each instance is short-lived; we provide the
115
+ // refresh callback so honestyEnforcement-style consumers (when
116
+ // we add them) can still rebuild.
117
+ refreshMemorySnapshot: () => deps.memoryManager.loadSnapshot(),
118
+ // Scope cuts (Phase 7b): no plannerGuard, no honestyEnforcement,
119
+ // no skillTeacher, no skillMiner. These add LLM calls + state
120
+ // that don't fit the daemon's "fire and act" pattern.
121
+ });
122
+ // Q-P7b-4(b) — minimal per-turn stdout line for tail-friendly
123
+ // operator debugging. Fires on builder exit; the dispatcher's
124
+ // `dispatcher:completed` run_event carries the same data in
125
+ // structured form for sqlite queries.
126
+ //
127
+ // We attach this as a post-construct log via the abort signal's
128
+ // 'abort' event; if the runner aborts the turn (budget watcher
129
+ // tripped), the line still surfaces. The dispatcher emits its
130
+ // own log; the goal here is a one-liner the operator can grep.
131
+ //
132
+ // Note: the actual finishReason + duration is the dispatcher's
133
+ // job to log after runConversation returns — we don't have
134
+ // that info here in the builder closure. Phase 7b ships the
135
+ // "starting turn" line; "completed turn" is handled inside
136
+ // realAgentRunner.ts.
137
+ // Q-P7b-4(b) stdout one-liner. sessionId follows
138
+ // `trigger:<source>:<sourceKey>:<hash>` so operators can grep by
139
+ // source. Per-turn duration + finishReason are logged separately
140
+ // by the dispatcher's realAgentRunner.ts when the turn completes.
141
+ log(`[daemon-turn] starting sessionId=${input.sessionId} model=${input.resolvedModel.provider}/${input.resolvedModel.model} policy=${input.approvalPolicy}`);
142
+ void turnStartMs; // kept for symmetry; the dispatcher computes its own duration
143
+ return agent;
144
+ };
145
+ }
@@ -30,7 +30,7 @@ exports.PREVIOUS_BUNDLED_SOULS = exports.DEFAULT_SOUL_MD = exports.BUNDLED_SOUL_
30
30
  // <act_dont_ask>. ensureSoulMdSeeded compares this against the user's
31
31
  // on-disk SOUL.md to decide whether to silent-replace (matches a prior
32
32
  // bundled default) or preserve+notify (user-edited).
33
- exports.BUNDLED_SOUL_VERSION = 'v4.1.5';
33
+ exports.BUNDLED_SOUL_VERSION = 'v4.6.0';
34
34
  exports.DEFAULT_SOUL_MD = `You are Aiden — a local-first AI agent built by Shiva Deore at Taracod.
35
35
 
36
36
  Identity: