aiden-runtime 4.9.1 → 4.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -1
- package/dist/cli/v4/aidenPrompt.js +12 -0
- package/dist/cli/v4/chatSession.js +41 -13
- package/dist/cli/v4/commands/channel.js +4 -6
- package/dist/cli/v4/commands/cron.js +6 -1
- package/dist/cli/v4/commands/daemonDoctor.js +5 -5
- package/dist/cli/v4/commands/daemonStatus.js +1 -1
- package/dist/cli/v4/commands/greeter.js +86 -0
- package/dist/cli/v4/commands/help.js +2 -0
- package/dist/cli/v4/commands/index.js +4 -0
- package/dist/cli/v4/commands/mcp.js +2 -2
- package/dist/cli/v4/commands/plugins.js +4 -6
- package/dist/cli/v4/commands/trigger.js +18 -18
- package/dist/cli/v4/confirmPrompt.js +67 -0
- package/dist/cli/v4/greeter/history.js +134 -0
- package/dist/cli/v4/greeter/index.js +147 -0
- package/dist/cli/v4/greeter/scan.js +140 -0
- package/dist/cli/v4/greeter/selectOffer.js +118 -0
- package/dist/cli/v4/greeter/templates.js +51 -0
- package/dist/cli/v4/greeter/types.js +23 -0
- package/dist/core/v4/daemon/db/migrations.js +398 -398
- package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +10 -10
- package/dist/core/v4/daemon/incarnationStore.js +9 -9
- package/dist/core/v4/daemon/runs/attemptStore.js +8 -8
- package/dist/core/v4/daemon/runs/reclaim.js +12 -12
- package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +19 -19
- package/dist/core/v4/daemon/spans/spanStore.js +14 -14
- package/dist/core/v4/daemon/triggerBus.js +61 -61
- package/dist/core/v4/hooks/auditQuery.js +11 -11
- package/dist/core/v4/hooks/dispatcher.js +13 -13
- package/dist/core/v4/hooks/registry.js +8 -8
- package/dist/core/v4/mcp/transport.js +9 -9
- package/dist/core/v4/update/executeInstall.js +29 -18
- package/dist/core/v4/update/recoveryScript.js +70 -0
- package/dist/core/v4/util/spawnCommand.js +151 -0
- package/package.json +1 -1
- package/themes/default.yaml +52 -52
- package/themes/dracula.yaml +32 -32
- package/themes/light.yaml +32 -32
- package/themes/monochrome.yaml +31 -31
- package/themes/tokyo-night.yaml +32 -32
package/README.md
CHANGED
|
@@ -105,6 +105,13 @@ Windows · Linux · WSL · macOS (API Mode)
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
https://github.com/user-attachments/assets/1081e5c5-f1ec-4980-b710-1640981ec58b
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
108
115
|
> A semi-autonomous AI agent that runs on your machine. Touches your files, browser, and shell. Remembers what matters. Built solo. Open source. Still rough in spots.
|
|
109
116
|
|
|
110
117
|
<br>
|
|
@@ -130,7 +137,13 @@ Drop a file in `~/Documents/inbox/anything.txt` and Aiden acts on it. The agent
|
|
|
130
137
|
|
|
131
138
|
<br>
|
|
132
139
|
|
|
133
|
-
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
https://github.com/user-attachments/assets/7a66bc19-8b17-4b01-be85-3aa5945a1b3b
|
|
145
|
+
|
|
146
|
+
|
|
134
147
|
|
|
135
148
|
<br>
|
|
136
149
|
|
|
@@ -219,6 +232,13 @@ Full v4.5 internals: [`docs/v4.5/`](docs/v4.5/) (overview, triggers, architectur
|
|
|
219
232
|
| **MCP** | Model Context Protocol bridge — stdio + HTTP transports, schema discovery, tool dispatch. |
|
|
220
233
|
| **Security moat** | Tiered approval engine (`safe` / `caution` / `dangerous`), dangerous-command pattern classifier, honesty enforcement (post-loop scan rewrites false claims), memory guard, planner-guard tool narrowing, SSRF-safe URL fetcher, secret/PII pre-write scanner, skill-teacher (auto-create skills from successful flows). |
|
|
221
234
|
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
https://github.com/user-attachments/assets/a76bf4a5-28ca-43b5-8975-5ef0a66ee90d
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
222
242
|
<br>
|
|
223
243
|
|
|
224
244
|
## Architecture
|
|
@@ -229,6 +249,13 @@ Detailed diagrams + module map in [`docs/v4.5/architecture.md`](docs/v4.5/archit
|
|
|
229
249
|
|
|
230
250
|
<br>
|
|
231
251
|
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
https://github.com/user-attachments/assets/323c9aa7-959a-425a-a5b3-4bae2b1a14bc
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
232
259
|
## Install + first run
|
|
233
260
|
|
|
234
261
|
### Linux / WSL / macOS (one-line)
|
|
@@ -285,6 +312,10 @@ Remove-Item -Recurse -Force $env:LOCALAPPDATA\aiden
|
|
|
285
312
|
<img width="938" height="1049" alt="preview (3)" src="https://github.com/user-attachments/assets/4e32ae38-74ad-433d-b986-0a15bc2dffec" />
|
|
286
313
|
|
|
287
314
|
|
|
315
|
+
|
|
316
|
+
https://github.com/user-attachments/assets/398e1d48-cc5a-4fb5-a195-05dbef824198
|
|
317
|
+
|
|
318
|
+
|
|
288
319
|
## Recommended terminal setup
|
|
289
320
|
|
|
290
321
|
For best visual rendering, Aiden looks crispest with:
|
|
@@ -488,6 +519,21 @@ Common issues live in [`docs/v4.5/troubleshooting.md`](docs/v4.5/troubleshooting
|
|
|
488
519
|
- **`/help` doesn't list a command** — that command likely needs an active session field; run from a real REPL
|
|
489
520
|
- **`npm install` permission errors on Windows** — install into a real folder (not a drive root like `S:\`)
|
|
490
521
|
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
https://github.com/user-attachments/assets/3acc997f-1d71-45d1-9955-11b67abd0c50
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
https://github.com/user-attachments/assets/9e734168-cf76-4cc0-975a-379e5402ee90
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
https://github.com/user-attachments/assets/5e7011a5-630d-43bd-8ed3-67084c7645db
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
491
537
|
<br>
|
|
492
538
|
|
|
493
539
|
## The book
|
|
@@ -291,6 +291,18 @@ exports.default = (0, core_1.createPrompt)((config, done) => {
|
|
|
291
291
|
line = `${message} ${theme.style.answer(value)}`;
|
|
292
292
|
}
|
|
293
293
|
else {
|
|
294
|
+
// v4.9.2 Slice 2 (commit 0d0668f1) attempted to fix cursor
|
|
295
|
+
// misalignment by post-pending cursorBackward(ghost.length).
|
|
296
|
+
// Live-REPL diagnostic proved the fix is structurally inert:
|
|
297
|
+
// @inquirer/core's screen-manager.js:56 appends an ABSOLUTE
|
|
298
|
+
// cursorTo(this.cursorPos.cols) AFTER our content, overriding
|
|
299
|
+
// any cursor-positioning escape we emit inline. The real fix
|
|
300
|
+
// requires either rendering the ghost via a side-channel post-
|
|
301
|
+
// render write or moving it out of the inline line entirely —
|
|
302
|
+
// both need the proper save/restore refactor scheduled for v4.10
|
|
303
|
+
// once the prompt has a real screen-manager-aware test harness.
|
|
304
|
+
// Reverted here so the shipped v4.9.2 doesn't carry a "fix" that
|
|
305
|
+
// doesn't fix anything. Bug D status: known, deferred.
|
|
294
306
|
const ghostStr = ghost ? dim(ghost) : '';
|
|
295
307
|
line = `${prefix} ${message}${value}${ghostStr}`;
|
|
296
308
|
}
|
|
@@ -82,6 +82,8 @@ const progressBar_1 = require("./display/progressBar");
|
|
|
82
82
|
const uiBuild_1 = require("./uiBuild");
|
|
83
83
|
const sessionSummaryGate_1 = require("./sessionSummaryGate");
|
|
84
84
|
const aidenPrompt_1 = __importDefault(require("./aidenPrompt"));
|
|
85
|
+
const confirmPrompt_1 = require("./confirmPrompt");
|
|
86
|
+
const greeter_1 = require("./greeter");
|
|
85
87
|
const historyStore_1 = require("./historyStore");
|
|
86
88
|
const modelMetadata_1 = require("../../core/v4/modelMetadata");
|
|
87
89
|
// v4.1.3-prebump: classify provider errors so the catch path can show
|
|
@@ -496,17 +498,21 @@ class ChatSession {
|
|
|
496
498
|
agent: this.opts.agent,
|
|
497
499
|
pluginLoader: this.opts.pluginLoader,
|
|
498
500
|
channelManager: this.opts.channelManager,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
501
|
+
// v4.9.2 Slice 3 — UX-rebuilt confirmation primitive.
|
|
502
|
+
// The stdin/keypress mechanics worked correctly all along;
|
|
503
|
+
// users simply couldn't see the prompt was open. The
|
|
504
|
+
// extracted `runConfirm` helper now owns the canonical
|
|
505
|
+
// y/N hint, the warn-tinted '?' glyph, the
|
|
506
|
+
// suggestionsDisabled flag (so confirmations skip ghost-
|
|
507
|
+
// match against outer chat history), and the per-input
|
|
508
|
+
// honest cancellation message.
|
|
509
|
+
//
|
|
510
|
+
// Phase 17.1 anchor: the previous primitive read
|
|
511
|
+
// `this.opts.promptApi?` (undefined → silently returned
|
|
512
|
+
// false → "Grant cancelled" before user could type) —
|
|
513
|
+
// fixed by routing through the resolved local `promptApi`.
|
|
514
|
+
// That fix stands; Slice 3 adds the UX layer on top.
|
|
515
|
+
confirm: (msg) => (0, confirmPrompt_1.runConfirm)(msg, promptApi, this.opts.display),
|
|
510
516
|
// Phase 18: raw text prompt for /auth login OAuth code paste.
|
|
511
517
|
prompt: (msg) => promptApi.readLine(msg),
|
|
512
518
|
});
|
|
@@ -1676,6 +1682,22 @@ class ChatSession {
|
|
|
1676
1682
|
}
|
|
1677
1683
|
}
|
|
1678
1684
|
catch { /* never let a missing marker crash boot */ }
|
|
1685
|
+
// v4.9.3 Slice 1b — boot greeter. Silent on first-ever launch (lets
|
|
1686
|
+
// renderFirstRunHint above own boot #1), silent when /greeter off,
|
|
1687
|
+
// silent when no offer wins. Lazy-required so test-harness sessions
|
|
1688
|
+
// without `paths` wired skip the fs cost. Internal errors are
|
|
1689
|
+
// already swallowed inside renderGreeter; outer try/catch is the
|
|
1690
|
+
// belt-and-braces guarantee against a boot-crash regression.
|
|
1691
|
+
try {
|
|
1692
|
+
if (this.opts.paths) {
|
|
1693
|
+
await (0, greeter_1.renderGreeter)({
|
|
1694
|
+
paths: this.opts.paths,
|
|
1695
|
+
version: version_1.VERSION,
|
|
1696
|
+
display: this.opts.display,
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
catch { /* never let the greeter crash boot */ }
|
|
1679
1701
|
// v4.9.0 pre-ship UI: hint moved BEFORE the closing rule so the
|
|
1680
1702
|
// rule sits adjacent to the active prompt (it becomes the visual
|
|
1681
1703
|
// top of the prompt zone). New order: blank · hint · blank · rule.
|
|
@@ -2085,9 +2107,15 @@ function createDefaultPromptApi(opts = {}) {
|
|
|
2085
2107
|
// aidenPrompt component (ghost text + slash dropdown + history nav).
|
|
2086
2108
|
const useLegacyPrompt = (0, uiBuild_1.isNoUiMode)() || !opts.commands;
|
|
2087
2109
|
return {
|
|
2088
|
-
async readLine(prompt) {
|
|
2110
|
+
async readLine(prompt, readOpts) {
|
|
2089
2111
|
try {
|
|
2090
|
-
|
|
2112
|
+
// v4.9.2 Slice 3 — confirmation prompts (suggestionsDisabled)
|
|
2113
|
+
// always route through the legacy inquirer input path. No
|
|
2114
|
+
// ghost-text (would autocomplete from outer chat history —
|
|
2115
|
+
// wrong context for a y/n question), no slash dropdown
|
|
2116
|
+
// (irrelevant for confirmations). Inquirer's plain input is
|
|
2117
|
+
// the well-tested baseline for single-shot questions.
|
|
2118
|
+
if (readOpts?.suggestionsDisabled || useLegacyPrompt) {
|
|
2091
2119
|
return (await inq.input({ message: prompt, theme: promptTheme })) ?? '';
|
|
2092
2120
|
}
|
|
2093
2121
|
// Fetch history just-in-time so each read sees the latest
|
|
@@ -255,10 +255,9 @@ async function telegramRemove(ctx) {
|
|
|
255
255
|
return;
|
|
256
256
|
}
|
|
257
257
|
const proceed = await confirm('Remove the Telegram bot token? This stops polling.');
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
// v4.9.2 Slice 3 — confirm() now owns the rejection message.
|
|
259
|
+
if (!proceed)
|
|
260
260
|
return;
|
|
261
|
-
}
|
|
262
261
|
// Stop the live adapter first so polling actually halts even if the
|
|
263
262
|
// .env write fails for some reason.
|
|
264
263
|
const manager = ctx.channelManager;
|
|
@@ -297,10 +296,9 @@ async function telegramTakeover(ctx) {
|
|
|
297
296
|
const proceed = confirm
|
|
298
297
|
? await confirm('Take over Telegram polling? This will boot any other Aiden instance off the bot.')
|
|
299
298
|
: true;
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
// v4.9.2 Slice 3 — confirm() now owns the rejection message.
|
|
300
|
+
if (!proceed)
|
|
302
301
|
return;
|
|
303
|
-
}
|
|
304
302
|
const spinner = display.startSpinner('Reclaiming Telegram polling…');
|
|
305
303
|
let result;
|
|
306
304
|
try {
|
|
@@ -327,8 +327,13 @@ async function cmdRemove(ctx, args) {
|
|
|
327
327
|
else if (ctx.confirm) {
|
|
328
328
|
ok = await ctx.confirm(question);
|
|
329
329
|
}
|
|
330
|
+
// v4.9.2 Slice 3 — when ctx.confirm was the source, the primitive
|
|
331
|
+
// already printed a per-input rejection message. The ctx.prompt
|
|
332
|
+
// branch above does its own y/N parsing, so it still owns its own
|
|
333
|
+
// "Cancelled." line.
|
|
330
334
|
if (!ok) {
|
|
331
|
-
ctx.
|
|
335
|
+
if (ctx.prompt)
|
|
336
|
+
ctx.display.dim('Cancelled.');
|
|
332
337
|
return;
|
|
333
338
|
}
|
|
334
339
|
if ((0, cronManager_1.deleteJob)(job.id)) {
|
|
@@ -85,7 +85,7 @@ function collectDoctorChecks(rootDir) {
|
|
|
85
85
|
detail: `current=${ver} latest=${migrations_1.LATEST_SCHEMA_VERSION}`,
|
|
86
86
|
fixable: false });
|
|
87
87
|
// 3. Recent incarnation
|
|
88
|
-
const inc = db.prepare(`SELECT incarnation_id, started_at, ended_at, exit_reason FROM daemon_incarnations
|
|
88
|
+
const inc = db.prepare(`SELECT incarnation_id, started_at, ended_at, exit_reason FROM daemon_incarnations
|
|
89
89
|
ORDER BY started_at DESC LIMIT 1`).get();
|
|
90
90
|
if (!inc) {
|
|
91
91
|
checks.push({ name: 'recent incarnation', status: 'warn',
|
|
@@ -100,7 +100,7 @@ function collectDoctorChecks(rootDir) {
|
|
|
100
100
|
}
|
|
101
101
|
// 4. Recent crashes (24h)
|
|
102
102
|
const sinceIso = new Date(Date.now() - TWENTY_FOUR_HOURS_MS).toISOString();
|
|
103
|
-
const crashes = db.prepare(`SELECT COUNT(*) AS c FROM daemon_incarnations
|
|
103
|
+
const crashes = db.prepare(`SELECT COUNT(*) AS c FROM daemon_incarnations
|
|
104
104
|
WHERE exit_reason = 'crash' AND started_at > ?`).get(sinceIso);
|
|
105
105
|
checks.push({ name: 'recent crashes (24h)',
|
|
106
106
|
status: crashes.c === 0 ? 'ok' : crashes.c > 3 ? 'warn' : 'ok',
|
|
@@ -111,7 +111,7 @@ function collectDoctorChecks(rootDir) {
|
|
|
111
111
|
if (tableExists(db, 'run_attempts')) {
|
|
112
112
|
const currentInc = inc?.incarnation_id ?? '';
|
|
113
113
|
const cutoffIso = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
114
|
-
stuckAttempts = db.prepare(`SELECT COUNT(*) AS c FROM run_attempts
|
|
114
|
+
stuckAttempts = db.prepare(`SELECT COUNT(*) AS c FROM run_attempts
|
|
115
115
|
WHERE status='running' AND incarnation_id != ? AND started_at < ?`).get(currentInc, cutoffIso).c;
|
|
116
116
|
}
|
|
117
117
|
checks.push({ name: 'stuck attempts',
|
|
@@ -125,7 +125,7 @@ function collectDoctorChecks(rootDir) {
|
|
|
125
125
|
let orphanSpans = 0;
|
|
126
126
|
if (tableExists(db, 'spans')) {
|
|
127
127
|
const currentInc = inc?.incarnation_id ?? '';
|
|
128
|
-
orphanSpans = db.prepare(`SELECT COUNT(*) AS c FROM spans
|
|
128
|
+
orphanSpans = db.prepare(`SELECT COUNT(*) AS c FROM spans
|
|
129
129
|
WHERE status IS NULL AND ended_at IS NULL AND incarnation_id != ?`).get(currentInc).c;
|
|
130
130
|
}
|
|
131
131
|
checks.push({ name: 'orphan spans',
|
|
@@ -147,7 +147,7 @@ function collectDoctorChecks(rootDir) {
|
|
|
147
147
|
// 8. Stale-claimed trigger events
|
|
148
148
|
let staleClaimed = 0;
|
|
149
149
|
if (tableExists(db, 'trigger_events')) {
|
|
150
|
-
staleClaimed = db.prepare(`SELECT COUNT(*) AS c FROM trigger_events
|
|
150
|
+
staleClaimed = db.prepare(`SELECT COUNT(*) AS c FROM trigger_events
|
|
151
151
|
WHERE status='claimed' AND claim_expires_at IS NOT NULL AND claim_expires_at < ?`).get(Date.now()).c;
|
|
152
152
|
}
|
|
153
153
|
checks.push({ name: 'stale-claimed trigger events',
|
|
@@ -150,7 +150,7 @@ function readSnapshot() {
|
|
|
150
150
|
// Recent runs (last 3).
|
|
151
151
|
const recentRuns = (() => {
|
|
152
152
|
try {
|
|
153
|
-
const rows = db.prepare(`SELECT id, status, finish_reason, started_at, completed_at FROM runs
|
|
153
|
+
const rows = db.prepare(`SELECT id, status, finish_reason, started_at, completed_at FROM runs
|
|
154
154
|
ORDER BY id DESC LIMIT 3`).all();
|
|
155
155
|
return rows.map((r) => ({
|
|
156
156
|
id: r.id,
|
|
@@ -0,0 +1,86 @@
|
|
|
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/greeter.ts — v4.9.3 SLICE 1a.
|
|
10
|
+
*
|
|
11
|
+
* `/greeter on | off | status` — REPL controls for the boot greeter.
|
|
12
|
+
* - on → set disabled: false; init the file if missing
|
|
13
|
+
* - off → confirm-gated (uses v4.9.2 Slice 3 ctx.confirm), set
|
|
14
|
+
* disabled: true
|
|
15
|
+
* - status → print current state + last greeting + offer summary
|
|
16
|
+
*
|
|
17
|
+
* Slice 1a: the slash command operates on the history file even
|
|
18
|
+
* though the greeter doesn't fire on boot yet (Slice 1b wires that).
|
|
19
|
+
* `/greeter off` BEFORE Slice 1b still durably disables, so when
|
|
20
|
+
* Slice 1b lands the user's choice is already honored.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.greeter = void 0;
|
|
24
|
+
const history_1 = require("../greeter/history");
|
|
25
|
+
exports.greeter = {
|
|
26
|
+
name: 'greeter',
|
|
27
|
+
description: 'Manage the boot greeter. Actions: on, off, status.',
|
|
28
|
+
category: 'system',
|
|
29
|
+
icon: '👋',
|
|
30
|
+
handler: async (ctx) => {
|
|
31
|
+
if (!ctx.paths) {
|
|
32
|
+
ctx.display.printError('Cannot read greeter state — paths not wired in this session.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const sub = (ctx.args[0] ?? 'status').toLowerCase();
|
|
36
|
+
if (sub === 'on') {
|
|
37
|
+
const h = (await (0, history_1.readHistory)(ctx.paths)) ?? initial();
|
|
38
|
+
h.disabled = false;
|
|
39
|
+
await (0, history_1.writeHistory)(ctx.paths, h);
|
|
40
|
+
ctx.display.success('Greeter on. Next boot will check for noticeable changes.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (sub === 'off') {
|
|
44
|
+
if (!ctx.confirm) {
|
|
45
|
+
ctx.display.printError('Cannot confirm in this context.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const proceed = await ctx.confirm('Turn the boot greeter off? You can re-enable with /greeter on.');
|
|
49
|
+
if (!proceed)
|
|
50
|
+
return; // confirm() already printed the rejection reason
|
|
51
|
+
const h = (await (0, history_1.readHistory)(ctx.paths)) ?? initial();
|
|
52
|
+
h.disabled = true;
|
|
53
|
+
await (0, history_1.writeHistory)(ctx.paths, h);
|
|
54
|
+
ctx.display.success('Greeter off. No greeting on boot until /greeter on.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (sub === 'status') {
|
|
58
|
+
const h = await (0, history_1.readHistory)(ctx.paths);
|
|
59
|
+
if (!h) {
|
|
60
|
+
ctx.display.dim('Greeter has not been initialized yet (no boots since v4.9.3).');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const state = h.disabled ? 'off' : 'on';
|
|
64
|
+
const accepted = h.offers.filter((o) => o.response === 'accepted').length;
|
|
65
|
+
const ignored = h.offers.filter((o) => o.response === 'ignored').length;
|
|
66
|
+
const pending = h.offers.filter((o) => !o.response).length;
|
|
67
|
+
ctx.display.write('\n Greeter status:\n');
|
|
68
|
+
ctx.display.write(` state: ${state}\n`);
|
|
69
|
+
ctx.display.write(` first launch: ${h.firstLaunchAt}\n`);
|
|
70
|
+
ctx.display.write(` last greeting: ${h.lastGreetingAt}\n`);
|
|
71
|
+
ctx.display.write(` offers: ${h.offers.length} (${accepted} accepted · ${ignored} ignored · ${pending} pending)\n\n`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
ctx.display.printError(`Unknown greeter action '${sub}'.`, 'Try: /greeter on | off | status');
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
function initial() {
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
return {
|
|
80
|
+
v: 1,
|
|
81
|
+
firstLaunchAt: now,
|
|
82
|
+
lastGreetingAt: now,
|
|
83
|
+
offers: [],
|
|
84
|
+
disabled: false,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -79,6 +79,8 @@ exports.SUBSECTION_MAP = {
|
|
|
79
79
|
// v4.9.1 amendment — REPL surfaces for memory + hooks (daemon already mapped).
|
|
80
80
|
memory: 'System',
|
|
81
81
|
hooks: 'System',
|
|
82
|
+
// v4.9.3 Slice 1b — boot greeter management.
|
|
83
|
+
greeter: 'System',
|
|
82
84
|
// ── Authentication ──
|
|
83
85
|
auth: 'Authentication',
|
|
84
86
|
// ── Help ──
|
|
@@ -111,6 +111,8 @@ const memorySlash_1 = require("./memorySlash");
|
|
|
111
111
|
Object.defineProperty(exports, "memory", { enumerable: true, get: function () { return memorySlash_1.memory; } });
|
|
112
112
|
const hooksSlash_1 = require("./hooksSlash");
|
|
113
113
|
Object.defineProperty(exports, "hooks", { enumerable: true, get: function () { return hooksSlash_1.hooks; } });
|
|
114
|
+
// v4.9.3 Slice 1b — boot greeter management.
|
|
115
|
+
const greeter_1 = require("./greeter");
|
|
114
116
|
/** All built-in system commands, in canonical order. */
|
|
115
117
|
exports.allCommands = [
|
|
116
118
|
help_1.help,
|
|
@@ -166,6 +168,8 @@ exports.allCommands = [
|
|
|
166
168
|
// v4.9.1 amendment — REPL slash surfaces mirroring CLI subcommands.
|
|
167
169
|
memorySlash_1.memory,
|
|
168
170
|
hooksSlash_1.hooks,
|
|
171
|
+
// v4.9.3 Slice 1b — boot greeter management.
|
|
172
|
+
greeter_1.greeter,
|
|
169
173
|
clear_1.clear,
|
|
170
174
|
quit_1.quit,
|
|
171
175
|
];
|
|
@@ -266,8 +266,8 @@ async function wireSubagentFanout(opts) {
|
|
|
266
266
|
// WAL-coexistence model as REPL — connection.ts caches per-path.
|
|
267
267
|
const mcpInstanceId = `mcp-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
|
|
268
268
|
const mcpDb = (0, daemon_1.openDaemonDb)((0, daemon_1.daemonDbPath)(opts.paths.root));
|
|
269
|
-
mcpDb.prepare(`INSERT OR IGNORE INTO daemon_instances
|
|
270
|
-
(instance_id, pid, hostname, started_at, last_heartbeat, version)
|
|
269
|
+
mcpDb.prepare(`INSERT OR IGNORE INTO daemon_instances
|
|
270
|
+
(instance_id, pid, hostname, started_at, last_heartbeat, version)
|
|
271
271
|
VALUES (?, ?, ?, ?, ?, ?)`).run(mcpInstanceId, process.pid, node_os_1.default.hostname(), Date.now(), Date.now(), version_1.VERSION);
|
|
272
272
|
const mcpRunStore = (0, daemon_1.createRunStore)({ db: mcpDb });
|
|
273
273
|
// v4.6 Phase 3b — self-improvement loop singleton against the
|
|
@@ -161,10 +161,9 @@ exports.plugins = {
|
|
|
161
161
|
ctx.display.write('\n');
|
|
162
162
|
const confirmFn = ctx.confirm ?? (async () => false);
|
|
163
163
|
const allow = await confirmFn(`Install ${manifest.name} with the listed permissions? [y/N] `);
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
// v4.9.2 Slice 3 — confirm() now owns the rejection message.
|
|
165
|
+
if (!allow)
|
|
166
166
|
return {};
|
|
167
|
-
}
|
|
168
167
|
// Copy into the user plugins dir.
|
|
169
168
|
const dst = node_path_1.default.join(ctx.paths.pluginsDir, manifest.name);
|
|
170
169
|
try {
|
|
@@ -258,10 +257,9 @@ exports.plugins = {
|
|
|
258
257
|
const allow = await confirmFn(isUpgrade
|
|
259
258
|
? `Grant the listed permissions (including ${newPerms.length} new)? [y/N] `
|
|
260
259
|
: `Grant the listed permissions? [y/N] `);
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
// v4.9.2 Slice 3 — confirm() now owns the rejection message.
|
|
261
|
+
if (!allow)
|
|
263
262
|
return {};
|
|
264
|
-
}
|
|
265
263
|
await (0, plugins_1.saveGrantedPermissions)(dir, entry.manifest.permissions);
|
|
266
264
|
// Reload so the new state takes effect.
|
|
267
265
|
await ctx.pluginLoader.teardown();
|
|
@@ -79,9 +79,9 @@ async function runTriggerSubcommand(action, args, argv, opts = {}) {
|
|
|
79
79
|
spec.paths = spec.paths.map((p) => node_path_1.default.resolve(p));
|
|
80
80
|
const id = (0, node_crypto_1.randomUUID)();
|
|
81
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)
|
|
82
|
+
db.prepare(`INSERT INTO triggers
|
|
83
|
+
(id, source, name, spec_json, enabled, prompt_template, deliver_only,
|
|
84
|
+
created_at, updated_at)
|
|
85
85
|
VALUES (?, 'file', ?, ?, ?, ?, 0, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, now, now);
|
|
86
86
|
out(`trigger added: ${id} (${a.name})\n`);
|
|
87
87
|
out('Restart the daemon to activate the watcher.\n');
|
|
@@ -182,11 +182,11 @@ async function runTriggerSubcommand(action, args, argv, opts = {}) {
|
|
|
182
182
|
return 1;
|
|
183
183
|
}
|
|
184
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
|
|
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
190
|
LIMIT 50`).all(`${prefix}%`);
|
|
191
191
|
if (rows.length === 0) {
|
|
192
192
|
out(`No run events recorded for trigger ${id} (${trig.name}).\n`);
|
|
@@ -213,10 +213,10 @@ async function runTriggerSubcommand(action, args, argv, opts = {}) {
|
|
|
213
213
|
return 1;
|
|
214
214
|
}
|
|
215
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
|
|
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
220
|
LIMIT 50`).all(`${prefix}%`);
|
|
221
221
|
if (rows.length === 0) {
|
|
222
222
|
out(`No runs recorded for trigger ${id} (${trig.name}).\n`);
|
|
@@ -261,9 +261,9 @@ function runAddWebhook(db, argv, out, err) {
|
|
|
261
261
|
});
|
|
262
262
|
const id = (0, node_crypto_1.randomUUID)();
|
|
263
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)
|
|
264
|
+
db.prepare(`INSERT INTO triggers
|
|
265
|
+
(id, source, name, spec_json, enabled, prompt_template, deliver_only,
|
|
266
|
+
created_at, updated_at)
|
|
267
267
|
VALUES (?, 'webhook', ?, ?, ?, ?, ?, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, spec.deliverOnly ? 1 : 0, now, now);
|
|
268
268
|
const cfg = (0, daemon_1.getDaemonConfig)();
|
|
269
269
|
const host = process.env.AIDEN_DAEMON_BIND ?? '127.0.0.1';
|
|
@@ -359,9 +359,9 @@ async function runAddEmail(db, argv, out, err) {
|
|
|
359
359
|
}
|
|
360
360
|
const id = (0, node_crypto_1.randomUUID)();
|
|
361
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)
|
|
362
|
+
db.prepare(`INSERT INTO triggers
|
|
363
|
+
(id, source, name, spec_json, enabled, prompt_template, deliver_only,
|
|
364
|
+
created_at, updated_at)
|
|
365
365
|
VALUES (?, 'email', ?, ?, ?, ?, ?, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, spec.deliverOnly ? 1 : 0, now, now);
|
|
366
366
|
out(`trigger added: ${id} (${a.name})\n`);
|
|
367
367
|
out(`imap host: ${spec.imap.host}:${spec.imap.port}${spec.imap.tls ? ' (TLS)' : ''}\n`);
|
|
@@ -0,0 +1,67 @@
|
|
|
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/confirmPrompt.ts — v4.9.2 SLICE 3.
|
|
10
|
+
*
|
|
11
|
+
* The slash-command `ctx.confirm()` primitive, extracted from the
|
|
12
|
+
* chatSession closure so it has one source of truth + is unit-testable
|
|
13
|
+
* without spinning up a full REPL.
|
|
14
|
+
*
|
|
15
|
+
* Behaviour:
|
|
16
|
+
* - Canonicalises the y/n hint: strips any caller-appended ` (y/N) `
|
|
17
|
+
* / ` [y/N] ` / ` (Y/n) ` so the primitive can append exactly one
|
|
18
|
+
* ` (y/N) ` in canonical lowercase-y / capital-N form.
|
|
19
|
+
* - Prefixes with a warn-tinted `?` glyph so the confirmation chrome
|
|
20
|
+
* is visually distinct from the main ▲ chat prompt (Slice 3 root
|
|
21
|
+
* cause: users couldn't tell a prompt was open).
|
|
22
|
+
* - Routes through `promptApi.readLine` with `suggestionsDisabled:true`
|
|
23
|
+
* so the inquirer-input path runs (no ghost-text from outer chat
|
|
24
|
+
* history, no slash dropdown — irrelevant for y/n).
|
|
25
|
+
* - Emits a per-input cancellation reason:
|
|
26
|
+
* empty / Enter alone → "Cancelled (press 'y' to confirm; …)"
|
|
27
|
+
* 'n' / 'no' → "Cancelled." (deliberate decline)
|
|
28
|
+
* other non-y → `Cancelled ("<x>" not recognized — …)`
|
|
29
|
+
* null / non-string → "Cancelled (no input)."
|
|
30
|
+
* Callers no longer print their own "Cancelled." line — the
|
|
31
|
+
* primitive owns the rejection message.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.runConfirm = runConfirm;
|
|
35
|
+
/** Strip any caller-appended `(y/N)` / `[y/N]` / `(Y/n)` so we can
|
|
36
|
+
* re-append the canonical hint without duplication. */
|
|
37
|
+
const TRAILING_YN_HINT_RE = /\s*[\[(](y\/[nN]|Y\/n)[\])]\s*$/i;
|
|
38
|
+
/**
|
|
39
|
+
* Run a single confirmation prompt. Resolves to `true` on `y` / `yes`
|
|
40
|
+
* (case insensitive, trimmed); `false` on anything else, with a
|
|
41
|
+
* specific cancellation line written to `display.dim()`.
|
|
42
|
+
*
|
|
43
|
+
* Never throws — readLine errors and non-string returns degrade to
|
|
44
|
+
* `false` with an honest "no input" reason.
|
|
45
|
+
*/
|
|
46
|
+
async function runConfirm(msg, promptApi, display) {
|
|
47
|
+
const stripped = msg.replace(TRAILING_YN_HINT_RE, '').trimEnd();
|
|
48
|
+
const decorated = `${display.paint('?', 'warn')} ${stripped} (y/N) `;
|
|
49
|
+
const r = await promptApi.readLine(decorated, { suggestionsDisabled: true });
|
|
50
|
+
if (typeof r !== 'string') {
|
|
51
|
+
display.dim('Cancelled (no input).');
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const trimmed = r.trim();
|
|
55
|
+
if (/^(y|yes)$/i.test(trimmed))
|
|
56
|
+
return true;
|
|
57
|
+
if (trimmed === '') {
|
|
58
|
+
display.dim(`Cancelled (press 'y' to confirm; Enter alone = no).`);
|
|
59
|
+
}
|
|
60
|
+
else if (/^(n|no)$/i.test(trimmed)) {
|
|
61
|
+
display.dim('Cancelled.');
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
display.dim(`Cancelled ("${trimmed}" not recognized — expected y/yes/n/no).`);
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|