aiden-runtime 4.0.2 → 4.1.1
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 +19 -11
- package/config/hardware.json +2 -2
- package/dist/api/server.js +50 -52
- package/dist/cli/v4/aidenCLI.js +424 -7
- package/dist/cli/v4/aidenPrompt.js +317 -0
- package/dist/cli/v4/box.js +105 -39
- package/dist/cli/v4/callbacks.js +39 -6
- package/dist/cli/v4/chatSession.js +256 -55
- package/dist/cli/v4/citationFooter.js +97 -0
- package/dist/cli/v4/commands/channel.js +656 -0
- package/dist/cli/v4/commands/clear.js +1 -1
- package/dist/cli/v4/commands/compress.js +1 -1
- package/dist/cli/v4/commands/cron.js +44 -16
- package/dist/cli/v4/commands/fanout.js +236 -0
- package/dist/cli/v4/commands/help.js +15 -4
- package/dist/cli/v4/commands/history.js +84 -0
- package/dist/cli/v4/commands/index.js +16 -1
- package/dist/cli/v4/commands/mcp.js +358 -0
- package/dist/cli/v4/commands/show.js +43 -0
- package/dist/cli/v4/commands/skills.js +169 -4
- package/dist/cli/v4/commands/status.js +84 -0
- package/dist/cli/v4/commands/subagent.js +78 -0
- package/dist/cli/v4/commands/verbose.js +1 -1
- package/dist/cli/v4/commands/voice.js +218 -0
- package/dist/cli/v4/cronCli.js +103 -0
- package/dist/cli/v4/display.js +297 -13
- package/dist/cli/v4/doctor.js +102 -1
- package/dist/cli/v4/doctorLiveness.js +329 -0
- package/dist/cli/v4/envSources.js +105 -0
- package/dist/cli/v4/ghostMatch.js +74 -0
- package/dist/cli/v4/historyStore.js +163 -0
- package/dist/cli/v4/pasteCompression.js +124 -0
- package/dist/cli/v4/pasteIntercept.js +203 -0
- package/dist/cli/v4/replyRenderer.js +209 -0
- package/dist/cli/v4/resizeGuard.js +92 -0
- package/dist/cli/v4/shellInterpolation.js +139 -0
- package/dist/cli/v4/skinEngine.js +21 -1
- package/dist/cli/v4/streamingPrefix.js +121 -0
- package/dist/cli/v4/syntaxHighlight.js +345 -0
- package/dist/cli/v4/table.js +216 -0
- package/dist/cli/v4/themeDetect.js +81 -0
- package/dist/cli/v4/uiBuild.js +74 -0
- package/dist/cli/v4/voiceCli.js +113 -0
- package/dist/cli/v4/voicePromptApi.js +196 -0
- package/dist/core/channels/discord.js +16 -10
- package/dist/core/channels/email.js +13 -9
- package/dist/core/channels/imessage.js +13 -9
- package/dist/core/channels/manager.js +25 -7
- package/dist/core/channels/pdf-extract.js +180 -0
- package/dist/core/channels/photo-vision.js +157 -0
- package/dist/core/channels/signal.js +11 -7
- package/dist/core/channels/slack.js +13 -10
- package/dist/core/channels/telegram-commands.js +154 -0
- package/dist/core/channels/telegram-groups.js +198 -0
- package/dist/core/channels/telegram-rate-limit.js +124 -0
- package/dist/core/channels/telegram.js +1980 -0
- package/dist/core/channels/twilio.js +11 -7
- package/dist/core/channels/webhook.js +9 -5
- package/dist/core/channels/whatsapp.js +15 -11
- package/dist/core/channels/whisper-transcribe.js +163 -0
- package/dist/core/cronManager.js +33 -294
- package/dist/core/gateway.js +29 -8
- package/dist/core/playwrightBridge.js +90 -0
- package/dist/core/v4/aidenAgent.js +35 -0
- package/dist/core/v4/auxiliaryClient.js +2 -2
- package/dist/core/v4/cron/atomicWrite.js +18 -4
- package/dist/core/v4/cron/cronExecute.js +300 -0
- package/dist/core/v4/cron/cronManager.js +502 -0
- package/dist/core/v4/cron/cronState.js +314 -0
- package/dist/core/v4/cron/cronTick.js +90 -0
- package/dist/core/v4/cron/diagnostics.js +104 -0
- package/dist/core/v4/cron/graceWindow.js +79 -0
- package/dist/core/v4/logger/factory.js +110 -0
- package/dist/core/v4/logger/index.js +22 -0
- package/dist/core/v4/logger/logger.js +101 -0
- package/dist/core/v4/logger/sinks/fileSink.js +110 -0
- package/dist/core/v4/logger/sinks/multiSink.js +43 -0
- package/dist/core/v4/logger/sinks/nullSink.js +53 -0
- package/dist/core/v4/logger/sinks/stdSink.js +81 -0
- package/dist/core/v4/mcp/server/diagnostics.js +40 -0
- package/dist/core/v4/mcp/server/skillBridge.js +94 -0
- package/dist/core/v4/mcp/server/stdioServer.js +119 -0
- package/dist/core/v4/mcp/server/toolBridge.js +168 -0
- package/dist/core/v4/platformPaths.js +105 -0
- package/dist/core/v4/providerFallback.js +25 -0
- package/dist/core/v4/skillLoader.js +21 -5
- package/dist/core/v4/skillMining/candidateStore.js +164 -0
- package/dist/core/v4/skillMining/extractorPrompt.js +118 -0
- package/dist/core/v4/skillMining/proposalBuilder.js +140 -0
- package/dist/core/v4/skillMining/skillMiner.js +191 -0
- package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
- package/dist/core/v4/subagent/budget.js +76 -0
- package/dist/core/v4/subagent/diagnostics.js +22 -0
- package/dist/core/v4/subagent/fanout.js +216 -0
- package/dist/core/v4/subagent/merger.js +148 -0
- package/dist/core/v4/subagent/providerRotation.js +54 -0
- package/dist/core/v4/voice/audioStream.js +373 -0
- package/dist/core/v4/voice/cliVoice.js +393 -0
- package/dist/core/v4/voice/diagnostics.js +66 -0
- package/dist/core/v4/voice/ttsStream.js +193 -0
- package/dist/core/version.js +1 -1
- package/dist/core/visionAnalyze.js +291 -90
- package/dist/core/voice/audio.js +61 -5
- package/dist/core/voice/audioBackend.js +134 -0
- package/dist/core/voice/stt.js +61 -6
- package/dist/core/voice/tts.js +19 -3
- package/dist/moat/dangerousPatterns.js +1 -1
- package/dist/providers/v4/codexResponsesAdapter.js +7 -2
- package/dist/providers/v4/errors.js +51 -1
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
- package/dist/tools/v4/index.js +32 -1
- package/dist/tools/v4/subagent/subagentFanout.js +190 -0
- package/package.json +11 -2
|
@@ -0,0 +1,317 @@
|
|
|
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/aidenPrompt.ts — Tier-3.1.1 (v4.1-tier3.1.1)
|
|
10
|
+
*
|
|
11
|
+
* Custom @inquirer/core prompt component that combines:
|
|
12
|
+
* - Standard text input (inquirer.input parity)
|
|
13
|
+
* - Ghost-text overlay for matching slash commands / history
|
|
14
|
+
* - Slash dropdown with ↑/↓ nav, Esc dismiss, description column
|
|
15
|
+
* - History suggestions for non-slash text
|
|
16
|
+
* - Cooperation with pasteIntercept (ghost disabled when a paste
|
|
17
|
+
* label is in the buffer)
|
|
18
|
+
*
|
|
19
|
+
* The prompt returns the typed text on Enter (NOT the ghost). The
|
|
20
|
+
* user must explicitly accept the ghost via Right-arrow or Tab.
|
|
21
|
+
*
|
|
22
|
+
* MCP serve mode never reaches this path — the REPL is gated on
|
|
23
|
+
* `process.stdout.isTTY` and serve mode runs over JSON-RPC stdio.
|
|
24
|
+
*
|
|
25
|
+
* `--no-ui`: the chatSession owner consults `isNoUiMode()` and
|
|
26
|
+
* falls back to the legacy inquirer prompt path when the env-var
|
|
27
|
+
* is set.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
const core_1 = require("@inquirer/core");
|
|
31
|
+
const ghostMatch_1 = require("./ghostMatch");
|
|
32
|
+
const skinEngine_1 = require("./skinEngine");
|
|
33
|
+
const DEFAULT_DROPDOWN_LIMIT = 8;
|
|
34
|
+
/** Strip ANSI for width math. */
|
|
35
|
+
function stripAnsi(s) {
|
|
36
|
+
return s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
37
|
+
}
|
|
38
|
+
/** Visible width — ignore ANSI escape sequences. */
|
|
39
|
+
function vWidth(s) {
|
|
40
|
+
return stripAnsi(s).length;
|
|
41
|
+
}
|
|
42
|
+
/** Build an SGR span using the active skin. */
|
|
43
|
+
function dim(s) {
|
|
44
|
+
return (0, skinEngine_1.getSkinEngine)().applyColors(s, 'muted');
|
|
45
|
+
}
|
|
46
|
+
/** Render a single dropdown row with right-aligned dim description. */
|
|
47
|
+
function renderDropdownRow(cmd, selected, width) {
|
|
48
|
+
const sk = (0, skinEngine_1.getSkinEngine)();
|
|
49
|
+
const marker = selected ? '▸ ' : ' ';
|
|
50
|
+
const nameCell = `/${cmd.name}`;
|
|
51
|
+
const desc = cmd.description ?? '';
|
|
52
|
+
// Reserve 2 chars for marker + 2-space pad before the desc column.
|
|
53
|
+
// The desc column is right-aligned and dim-coloured.
|
|
54
|
+
const lhs = `${marker}${nameCell}`;
|
|
55
|
+
const lhsWidth = vWidth(lhs);
|
|
56
|
+
const padBetween = Math.max(2, width - lhsWidth - vWidth(desc));
|
|
57
|
+
const truncatedDesc = vWidth(desc) > width - lhsWidth - 2
|
|
58
|
+
? desc.slice(0, Math.max(0, width - lhsWidth - 3)) + '…'
|
|
59
|
+
: desc;
|
|
60
|
+
const dimDesc = sk.applyColors(truncatedDesc, 'muted');
|
|
61
|
+
const painted = selected
|
|
62
|
+
? sk.applyColors(lhs, 'brand')
|
|
63
|
+
: lhs;
|
|
64
|
+
return painted + ' '.repeat(padBetween) + dimDesc;
|
|
65
|
+
}
|
|
66
|
+
/** Default 3-tier filter — matches commandRegistry.filter shape. */
|
|
67
|
+
function defaultFilter(cmds, input) {
|
|
68
|
+
const stem = input.startsWith('/') ? input.slice(1) : input;
|
|
69
|
+
const lower = stem.toLowerCase();
|
|
70
|
+
if (!lower)
|
|
71
|
+
return cmds.filter((c) => !c.hidden);
|
|
72
|
+
const visible = cmds.filter((c) => !c.hidden);
|
|
73
|
+
const prefix = visible.filter((c) => c.name.toLowerCase().startsWith(lower) ||
|
|
74
|
+
(c.aliases ?? []).some((a) => a.toLowerCase().startsWith(lower)));
|
|
75
|
+
if (prefix.length > 0)
|
|
76
|
+
return prefix;
|
|
77
|
+
const substring = visible.filter((c) => c.name.toLowerCase().includes(lower) ||
|
|
78
|
+
(c.aliases ?? []).some((a) => a.toLowerCase().includes(lower)));
|
|
79
|
+
if (substring.length > 0)
|
|
80
|
+
return substring;
|
|
81
|
+
return visible.filter((c) => (c.description ?? '').toLowerCase().includes(lower));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The prompt itself. Resolves with the literal user-typed text on
|
|
85
|
+
* Enter. If the dropdown is open and the user pressed Enter on a
|
|
86
|
+
* highlighted row, resolves with `/<row.name>` instead.
|
|
87
|
+
*/
|
|
88
|
+
exports.default = (0, core_1.createPrompt)((config, done) => {
|
|
89
|
+
const theme = (0, core_1.makeTheme)({}, config.theme);
|
|
90
|
+
const [status, setStatus] = (0, core_1.useState)('idle');
|
|
91
|
+
const [value, setValue] = (0, core_1.useState)('');
|
|
92
|
+
const [ghost, setGhost] = (0, core_1.useState)(null);
|
|
93
|
+
const [dropdownOpen, setDropdownOpen] = (0, core_1.useState)(false);
|
|
94
|
+
const [selectedIdx, setSelectedIdx] = (0, core_1.useState)(0);
|
|
95
|
+
const [historyIdx, setHistoryIdx] = (0, core_1.useState)(null);
|
|
96
|
+
// Snapshot of the typed value when the user starts navigating
|
|
97
|
+
// history — restored when they reach the bottom of the stack.
|
|
98
|
+
const historyDraftRef = (0, core_1.useRef)('');
|
|
99
|
+
// Tier-3.1c: paste-burst guard. When bracketed paste mode isn't
|
|
100
|
+
// honoured by the terminal (no CSI 200~/201~ wrap) the paste
|
|
101
|
+
// arrives as raw bytes; the first internal `\n` becomes an Enter
|
|
102
|
+
// event and submits before the user can review. We can't always
|
|
103
|
+
// suppress that at the stdin level, so as a defence-in-depth the
|
|
104
|
+
// prompt records the timestamp of the last NON-Enter keypress and
|
|
105
|
+
// refuses to submit on Enter that arrives within
|
|
106
|
+
// PASTE_BURST_GUARD_MS of it. Real user Enter comes after a
|
|
107
|
+
// pause, so this only blocks the rapid Enter-from-paste path.
|
|
108
|
+
const lastNonEnterKeyMsRef = (0, core_1.useRef)(0);
|
|
109
|
+
const prefix = (0, core_1.usePrefix)({ status, theme });
|
|
110
|
+
const dropdownLimit = config.dropdownLimit ?? DEFAULT_DROPDOWN_LIMIT;
|
|
111
|
+
const filterFn = config.filter
|
|
112
|
+
?? ((input) => defaultFilter(config.commands, input));
|
|
113
|
+
/** Recompute ghost + dropdown for a new value. */
|
|
114
|
+
function rederive(next) {
|
|
115
|
+
setValue(next);
|
|
116
|
+
setGhost((0, ghostMatch_1.findGhost)(next, {
|
|
117
|
+
slashNames: config.commands.map((c) => c.name),
|
|
118
|
+
slashAliases: config.commands.flatMap((c) => [...(c.aliases ?? [])]),
|
|
119
|
+
history: config.history,
|
|
120
|
+
}));
|
|
121
|
+
if (next.startsWith('/')) {
|
|
122
|
+
const matches = filterFn(next);
|
|
123
|
+
const open = matches.length > 0;
|
|
124
|
+
setDropdownOpen(open);
|
|
125
|
+
if (open)
|
|
126
|
+
setSelectedIdx(Math.min(selectedIdx, matches.length - 1));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
setDropdownOpen(false);
|
|
130
|
+
setSelectedIdx(0);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
(0, core_1.useEffect)((rl) => {
|
|
134
|
+
// Initial sync — empty.
|
|
135
|
+
setValue('');
|
|
136
|
+
setGhost(null);
|
|
137
|
+
setDropdownOpen(false);
|
|
138
|
+
void rl;
|
|
139
|
+
}, []);
|
|
140
|
+
(0, core_1.useKeypress)((key, rl) => {
|
|
141
|
+
if (status !== 'idle')
|
|
142
|
+
return;
|
|
143
|
+
// ── Submit ──
|
|
144
|
+
if ((0, core_1.isEnterKey)(key)) {
|
|
145
|
+
// Tier-3.1c: paste-burst guard. If a non-Enter keystroke fired
|
|
146
|
+
// within the last 50ms, this Enter is almost certainly an
|
|
147
|
+
// internal `\n` from an unbracketed paste, not a deliberate
|
|
148
|
+
// user submit. Suppress and let readline keep accumulating
|
|
149
|
+
// bytes — the user will press Enter again once the paste
|
|
150
|
+
// settles.
|
|
151
|
+
const PASTE_BURST_GUARD_MS = 50;
|
|
152
|
+
const sinceLastKey = Date.now() - lastNonEnterKeyMsRef.current;
|
|
153
|
+
if (lastNonEnterKeyMsRef.current > 0 && sinceLastKey < PASTE_BURST_GUARD_MS) {
|
|
154
|
+
// Reset so subsequent rapid Enters are also caught while
|
|
155
|
+
// the burst continues.
|
|
156
|
+
lastNonEnterKeyMsRef.current = Date.now();
|
|
157
|
+
// Resync value in case readline already cleared the line
|
|
158
|
+
// on this Enter (it does — we can't fully prevent that, but
|
|
159
|
+
// we keep the buffered fragments in `value` for context).
|
|
160
|
+
rederive(rl.line);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (dropdownOpen) {
|
|
164
|
+
const matches = filterFn(value);
|
|
165
|
+
const picked = matches[selectedIdx];
|
|
166
|
+
if (picked) {
|
|
167
|
+
// Tier-3.1c: preserve typed args. If the user typed
|
|
168
|
+
// `/skills list` we must submit the literal value, not
|
|
169
|
+
// `/skills` alone — the row pick only short-circuits to
|
|
170
|
+
// the command name when the typed value is JUST the
|
|
171
|
+
// (partial) command without args. Detect args via a
|
|
172
|
+
// whitespace boundary inside `value`.
|
|
173
|
+
const hasArgs = /\s/.test(value);
|
|
174
|
+
const out = hasArgs ? value : `/${picked.name}`;
|
|
175
|
+
setStatus('done');
|
|
176
|
+
setValue(out);
|
|
177
|
+
done(out);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Normal submit — return literal typed text (NOT ghost).
|
|
182
|
+
setStatus('done');
|
|
183
|
+
done(value);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// ── Esc — dismiss dropdown ──
|
|
187
|
+
if (key.name === 'escape') {
|
|
188
|
+
if (dropdownOpen) {
|
|
189
|
+
setDropdownOpen(false);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// No dropdown — let inquirer's default Esc handling run
|
|
193
|
+
// (typically a no-op for an `input`-style prompt).
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// ── Right / Tab — accept ghost ──
|
|
197
|
+
if ((key.name === 'right' || (0, core_1.isTabKey)(key)) && ghost) {
|
|
198
|
+
// Tab unambiguously accepts. Right-arrow only accepts when the
|
|
199
|
+
// cursor is at the END of the line (otherwise the user is mid-
|
|
200
|
+
// edit and right-arrow should move the cursor normally). Cursor
|
|
201
|
+
// position isn't in @inquirer/type's InquirerReadline shape but
|
|
202
|
+
// is on the underlying node readline — read it via cast.
|
|
203
|
+
const cursorPos = rl.cursor ?? rl.line.length;
|
|
204
|
+
const atEnd = cursorPos === rl.line.length;
|
|
205
|
+
if (atEnd || (0, core_1.isTabKey)(key)) {
|
|
206
|
+
const accepted = value + ghost;
|
|
207
|
+
rl.clearLine(0);
|
|
208
|
+
rl.write(accepted);
|
|
209
|
+
rederive(accepted);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ── ↑/↓ ──
|
|
214
|
+
if (key.name === 'up' || key.name === 'down') {
|
|
215
|
+
if (dropdownOpen) {
|
|
216
|
+
const matches = filterFn(value);
|
|
217
|
+
if (matches.length > 0) {
|
|
218
|
+
if (key.name === 'up') {
|
|
219
|
+
setSelectedIdx((selectedIdx - 1 + matches.length) % matches.length);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
setSelectedIdx((selectedIdx + 1) % matches.length);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// History nav (when dropdown closed). Up = older, Down = newer.
|
|
228
|
+
if (config.history.length > 0) {
|
|
229
|
+
if (key.name === 'up') {
|
|
230
|
+
if (historyIdx === null) {
|
|
231
|
+
historyDraftRef.current = value;
|
|
232
|
+
setHistoryIdx(0);
|
|
233
|
+
const next = config.history[0];
|
|
234
|
+
rl.clearLine(0);
|
|
235
|
+
rl.write(next);
|
|
236
|
+
rederive(next);
|
|
237
|
+
}
|
|
238
|
+
else if (historyIdx + 1 < config.history.length) {
|
|
239
|
+
const ni = historyIdx + 1;
|
|
240
|
+
setHistoryIdx(ni);
|
|
241
|
+
const next = config.history[ni];
|
|
242
|
+
rl.clearLine(0);
|
|
243
|
+
rl.write(next);
|
|
244
|
+
rederive(next);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else { // down
|
|
248
|
+
if (historyIdx !== null) {
|
|
249
|
+
if (historyIdx === 0) {
|
|
250
|
+
setHistoryIdx(null);
|
|
251
|
+
const draft = historyDraftRef.current;
|
|
252
|
+
rl.clearLine(0);
|
|
253
|
+
rl.write(draft);
|
|
254
|
+
rederive(draft);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const ni = historyIdx - 1;
|
|
258
|
+
setHistoryIdx(ni);
|
|
259
|
+
const next = config.history[ni];
|
|
260
|
+
rl.clearLine(0);
|
|
261
|
+
rl.write(next);
|
|
262
|
+
rederive(next);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// ── Backspace fast-path so cursor sync stays clean ──
|
|
271
|
+
if ((0, core_1.isBackspaceKey)(key)) {
|
|
272
|
+
// rl.line already updated by the readline event before this
|
|
273
|
+
// handler runs, so we just resync.
|
|
274
|
+
lastNonEnterKeyMsRef.current = Date.now();
|
|
275
|
+
setHistoryIdx(null);
|
|
276
|
+
rederive(rl.line);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// ── Default — sync from rl.line, recompute derived state ──
|
|
280
|
+
// Tier-3.1c: any non-Enter keystroke updates the burst-guard
|
|
281
|
+
// timestamp; the Enter handler reads it to decide whether the
|
|
282
|
+
// submit is a real user Enter or part of a paste burst.
|
|
283
|
+
lastNonEnterKeyMsRef.current = Date.now();
|
|
284
|
+
setHistoryIdx(null);
|
|
285
|
+
rederive(rl.line);
|
|
286
|
+
});
|
|
287
|
+
// ── Render ─────────────────────────────────────────────────────
|
|
288
|
+
const message = theme.style.message(config.message, status);
|
|
289
|
+
let line;
|
|
290
|
+
if (status === 'done') {
|
|
291
|
+
line = `${message} ${theme.style.answer(value)}`;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const ghostStr = ghost ? dim(ghost) : '';
|
|
295
|
+
line = `${prefix} ${message}${value}${ghostStr}`;
|
|
296
|
+
}
|
|
297
|
+
// Footer (dropdown). Returning a tuple `[line, footer]` adds the
|
|
298
|
+
// footer below the input line; inquirer takes care of cursor
|
|
299
|
+
// positioning so the cursor stays on `line`.
|
|
300
|
+
let footer;
|
|
301
|
+
if (dropdownOpen && status === 'idle') {
|
|
302
|
+
const matches = filterFn(value);
|
|
303
|
+
if (matches.length > 0) {
|
|
304
|
+
const visibleCols = process.stdout.columns ?? 100;
|
|
305
|
+
const rowWidth = Math.max(40, Math.min(visibleCols - 4, 100));
|
|
306
|
+
const window = matches.slice(0, dropdownLimit);
|
|
307
|
+
// Clamp selectedIdx into the visible window.
|
|
308
|
+
const safeIdx = Math.min(selectedIdx, window.length - 1);
|
|
309
|
+
const rows = window.map((c, i) => renderDropdownRow(c, i === safeIdx, rowWidth));
|
|
310
|
+
const more = matches.length > window.length
|
|
311
|
+
? ` ${dim(`… ${matches.length - window.length} more`)}`
|
|
312
|
+
: '';
|
|
313
|
+
footer = [...rows, more].filter((r) => r.length > 0).join('\n');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return footer ? [line, footer] : line;
|
|
317
|
+
});
|
package/dist/cli/v4/box.js
CHANGED
|
@@ -6,47 +6,68 @@
|
|
|
6
6
|
* Aiden — local-first agent.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* cli/v4/box.ts —
|
|
9
|
+
* cli/v4/box.ts — sharp + double-line box drawing helpers.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* (
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Tier-3.1 (v4.1-tier3.1) replaced the rounded set (╭╮╰╯) with
|
|
12
|
+
* sharp corners (┌┐└┘) for the default box and added a second
|
|
13
|
+
* double-line variant (╔╗╚╝═║) for emphasis surfaces (e.g. the
|
|
14
|
+
* approval/escalation banner). The default `box*` exports continue
|
|
15
|
+
* to point at the sharp variant so existing callers compile
|
|
16
|
+
* unchanged; `boxSharp*` and `boxDouble*` are explicit aliases for
|
|
17
|
+
* call sites that want to declare intent.
|
|
16
18
|
*
|
|
17
19
|
* Width counts the inner cell only (between the verticals). Content
|
|
18
20
|
* is padded to width-1 so a single leading space gives the box a
|
|
19
21
|
* visual gutter.
|
|
20
22
|
*
|
|
21
|
-
* ANSI awareness
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* drifted inside the visible box top/bottom borders. The helpers
|
|
23
|
+
* ANSI awareness: per-row coloured content (orange ✓ icons, soft-
|
|
24
|
+
* cyan labels) inflates `String.length` from ~50 visible chars to
|
|
25
|
+
* ~120 bytes per row, so byte-based padding under-fills and the
|
|
26
|
+
* closing vertical drifts inside the visible borders. The helpers
|
|
26
27
|
* below measure / truncate against the visible (post-strip)
|
|
27
28
|
* length, so coloured content frames identically to plain content.
|
|
28
29
|
*/
|
|
29
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.boxSharpTopTitled = exports.boxSharpLine = exports.boxSharpBottom = exports.boxSharpTop = void 0;
|
|
30
32
|
exports.visibleLength = visibleLength;
|
|
31
33
|
exports.truncateVisible = truncateVisible;
|
|
32
34
|
exports.boxTop = boxTop;
|
|
33
35
|
exports.boxBottom = boxBottom;
|
|
34
36
|
exports.boxLine = boxLine;
|
|
35
37
|
exports.boxTopTitled = boxTopTitled;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
exports.boxDoubleTop = boxDoubleTop;
|
|
39
|
+
exports.boxDoubleBottom = boxDoubleBottom;
|
|
40
|
+
exports.boxDoubleLine = boxDoubleLine;
|
|
41
|
+
exports.boxDoubleTopTitled = boxDoubleTopTitled;
|
|
42
|
+
exports.boxDouble = boxDouble;
|
|
43
|
+
exports.boxSharp = boxSharp;
|
|
44
|
+
// ── Sharp (default) ──────────────────────────────────────────────
|
|
45
|
+
const SHARP = {
|
|
46
|
+
TL: '┌',
|
|
47
|
+
TR: '┐',
|
|
48
|
+
BL: '└',
|
|
49
|
+
BR: '┘',
|
|
50
|
+
H: '─',
|
|
51
|
+
V: '│',
|
|
52
|
+
};
|
|
53
|
+
// ── Double-line (emphasis) ───────────────────────────────────────
|
|
54
|
+
const DOUBLE = {
|
|
55
|
+
TL: '╔',
|
|
56
|
+
TR: '╗',
|
|
57
|
+
BL: '╚',
|
|
58
|
+
BR: '╝',
|
|
59
|
+
H: '═',
|
|
60
|
+
V: '║',
|
|
61
|
+
};
|
|
42
62
|
/**
|
|
43
63
|
* Strip ANSI CSI escape sequences and return the visible length in
|
|
44
64
|
* Unicode code units (`String.length`). Sufficient for all colour
|
|
45
65
|
* codes we emit (`\x1b[38;2;r;g;bm`, `\x1b[39m`, `\x1b[0m`, etc.).
|
|
46
66
|
*
|
|
47
67
|
* Doesn't try to handle East Asian wide chars / emoji-with-VS16 — we
|
|
48
|
-
* use only single-cell glyphs in box content (✓ ⚠ ✗ ⏵ ▶ ⊕).
|
|
49
|
-
*
|
|
68
|
+
* use only single-cell glyphs in box content (✓ ⚠ ✗ ⏵ ▶ ⊕). Wide-
|
|
69
|
+
* char-aware width is available in `cli/v4/table.ts` via
|
|
70
|
+
* `string-width`, used only by the table renderer.
|
|
50
71
|
*/
|
|
51
72
|
const ANSI_REGEX = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
52
73
|
function visibleLength(s) {
|
|
@@ -55,10 +76,8 @@ function visibleLength(s) {
|
|
|
55
76
|
/**
|
|
56
77
|
* Truncate `s` to `maxVisible` visible columns, preserving any ANSI
|
|
57
78
|
* sequences encountered along the way. When the input contained ANSI
|
|
58
|
-
* codes, an SGR reset is appended so the closing
|
|
59
|
-
* the truncated content's colour.
|
|
60
|
-
* beyond the truncation, so callers building plain rows still see
|
|
61
|
-
* exactly `maxVisible` characters back.
|
|
79
|
+
* codes, an SGR reset is appended so the closing vertical doesn't
|
|
80
|
+
* inherit the truncated content's colour.
|
|
62
81
|
*/
|
|
63
82
|
function truncateVisible(s, maxVisible) {
|
|
64
83
|
if (visibleLength(s) <= maxVisible)
|
|
@@ -84,29 +103,76 @@ function truncateVisible(s, maxVisible) {
|
|
|
84
103
|
}
|
|
85
104
|
return sawAnsi ? out + '\x1b[0m' : out;
|
|
86
105
|
}
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
// ── Generic primitives ───────────────────────────────────────────
|
|
107
|
+
function renderTop(g, width) {
|
|
108
|
+
return g.TL + g.H.repeat(width) + g.TR;
|
|
89
109
|
}
|
|
90
|
-
function
|
|
91
|
-
return BL + H.repeat(width) + BR;
|
|
110
|
+
function renderBottom(g, width) {
|
|
111
|
+
return g.BL + g.H.repeat(width) + g.BR;
|
|
92
112
|
}
|
|
93
|
-
function
|
|
113
|
+
function renderLine(g, content, width) {
|
|
94
114
|
const inner = ' ' + content;
|
|
95
115
|
const visible = visibleLength(inner);
|
|
96
116
|
if (visible >= width) {
|
|
97
|
-
return V + truncateVisible(inner, width) + V;
|
|
117
|
+
return g.V + truncateVisible(inner, width) + g.V;
|
|
98
118
|
}
|
|
99
|
-
return V + inner + ' '.repeat(width - visible) + V;
|
|
119
|
+
return g.V + inner + ' '.repeat(width - visible) + g.V;
|
|
120
|
+
}
|
|
121
|
+
function renderTopTitled(g, title, width) {
|
|
122
|
+
const lhs = `${g.TL}${g.H}${g.H} ${title} `;
|
|
123
|
+
const visibleLhs = 2 + 1 + visibleLength(title) + 1;
|
|
124
|
+
const remaining = Math.max(0, width - visibleLhs);
|
|
125
|
+
return `${lhs}${g.H.repeat(remaining)}${g.TR}`;
|
|
126
|
+
}
|
|
127
|
+
// ── Sharp variant (default) ──────────────────────────────────────
|
|
128
|
+
function boxTop(width) {
|
|
129
|
+
return renderTop(SHARP, width);
|
|
130
|
+
}
|
|
131
|
+
function boxBottom(width) {
|
|
132
|
+
return renderBottom(SHARP, width);
|
|
133
|
+
}
|
|
134
|
+
function boxLine(content, width) {
|
|
135
|
+
return renderLine(SHARP, content, width);
|
|
136
|
+
}
|
|
137
|
+
function boxTopTitled(title, width) {
|
|
138
|
+
return renderTopTitled(SHARP, title, width);
|
|
139
|
+
}
|
|
140
|
+
// Explicit sharp aliases (for call sites that want to declare intent).
|
|
141
|
+
exports.boxSharpTop = boxTop;
|
|
142
|
+
exports.boxSharpBottom = boxBottom;
|
|
143
|
+
exports.boxSharpLine = boxLine;
|
|
144
|
+
exports.boxSharpTopTitled = boxTopTitled;
|
|
145
|
+
// ── Double-line variant ──────────────────────────────────────────
|
|
146
|
+
function boxDoubleTop(width) {
|
|
147
|
+
return renderTop(DOUBLE, width);
|
|
148
|
+
}
|
|
149
|
+
function boxDoubleBottom(width) {
|
|
150
|
+
return renderBottom(DOUBLE, width);
|
|
151
|
+
}
|
|
152
|
+
function boxDoubleLine(content, width) {
|
|
153
|
+
return renderLine(DOUBLE, content, width);
|
|
154
|
+
}
|
|
155
|
+
function boxDoubleTopTitled(title, width) {
|
|
156
|
+
return renderTopTitled(DOUBLE, title, width);
|
|
100
157
|
}
|
|
101
158
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
159
|
+
* Convenience: wrap an array of content rows with double-line
|
|
160
|
+
* borders and an optional title. Returns the full multi-line box
|
|
161
|
+
* as a single string with `\n` separators.
|
|
105
162
|
*/
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
163
|
+
function boxDouble(rows, width, title) {
|
|
164
|
+
const top = title ? boxDoubleTopTitled(title, width) : boxDoubleTop(width);
|
|
165
|
+
const body = rows.map((r) => boxDoubleLine(r, width)).join('\n');
|
|
166
|
+
const bottom = boxDoubleBottom(width);
|
|
167
|
+
return [top, body, bottom].filter(Boolean).join('\n');
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Convenience: wrap an array of content rows with sharp borders
|
|
171
|
+
* and an optional title.
|
|
172
|
+
*/
|
|
173
|
+
function boxSharp(rows, width, title) {
|
|
174
|
+
const top = title ? boxTopTitled(title, width) : boxTop(width);
|
|
175
|
+
const body = rows.map((r) => boxLine(r, width)).join('\n');
|
|
176
|
+
const bottom = boxBottom(width);
|
|
177
|
+
return [top, body, bottom].filter(Boolean).join('\n');
|
|
112
178
|
}
|
package/dist/cli/v4/callbacks.js
CHANGED
|
@@ -33,10 +33,15 @@ async function defaultPrompts() {
|
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
// Tier-3-essentials: terse 4-state ladder labels per dispatch.
|
|
37
|
+
// Once / Session / Always / Deny — same underlying ApprovalDecision
|
|
38
|
+
// values, friendlier wording. Persistence to <aidenHome>/approvals.json
|
|
39
|
+
// is wired in aidenCLI.ts via approvalEngine.callbacks.persistAllow
|
|
40
|
+
// (Phase 16f); session-scope cache in approvalEngine.allowForSession.
|
|
36
41
|
const DECISION_CHOICES = [
|
|
37
|
-
{ name: '
|
|
38
|
-
{ name: '
|
|
39
|
-
{ name: '
|
|
42
|
+
{ name: 'Once', value: 'allow' },
|
|
43
|
+
{ name: 'Session', value: 'allow_session' },
|
|
44
|
+
{ name: 'Always', value: 'allow_always' },
|
|
40
45
|
{ name: 'Deny', value: 'deny' },
|
|
41
46
|
];
|
|
42
47
|
const KNOWN_TIERS = new Set(['safe', 'caution', 'dangerous']);
|
|
@@ -184,6 +189,26 @@ Reply with ONE word: safe, caution, or dangerous.`;
|
|
|
184
189
|
this.display.dim(`[planner] kept ${decision.selectedTools.length} tools (${decision.reason})`);
|
|
185
190
|
}
|
|
186
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* Phase v4.1-skill-mining — post-turn cue when the miner has
|
|
194
|
+
* staged a candidate for `/skills review`. Single dim line, no
|
|
195
|
+
* modal. Pulls the skill name + confidence from the candidate's
|
|
196
|
+
* own SKILL.md (best-effort parse; falls back to id slice).
|
|
197
|
+
*/
|
|
198
|
+
this.onSkillCandidate = (candidate) => {
|
|
199
|
+
let name = candidate.id.slice(0, 8);
|
|
200
|
+
try {
|
|
201
|
+
// Tier-3.1c sweep: do not import here — chatSession's display
|
|
202
|
+
// wraps strings, and the SKILL.md frontmatter is plain enough
|
|
203
|
+
// that a quick regex is fine for the cue line.
|
|
204
|
+
const m = /\bname\s*:\s*([^\n]+)/.exec(candidate.skillContent);
|
|
205
|
+
if (m)
|
|
206
|
+
name = m[1].trim();
|
|
207
|
+
}
|
|
208
|
+
catch { /* fall through */ }
|
|
209
|
+
const conf = candidate.candidateConfidence.toFixed(2);
|
|
210
|
+
this.display.dim(`[skill] candidate '${name}' queued (conf ${conf}) — run /skills review`);
|
|
211
|
+
};
|
|
187
212
|
/** ContextCompressor sink — always shows. */
|
|
188
213
|
this.onCompression = (result) => {
|
|
189
214
|
if (result.refused) {
|
|
@@ -239,14 +264,22 @@ Reply with ONE word: safe, caution, or dangerous.`;
|
|
|
239
264
|
}
|
|
240
265
|
}
|
|
241
266
|
exports.CliCallbacks = CliCallbacks;
|
|
267
|
+
// Tier-3.1 (v4.1-tier3.1): replaced 🟢/🟡/🔴 emoji badges with
|
|
268
|
+
// text-state badges. Each badge is 7 visible chars (pad-aligned) so
|
|
269
|
+
// approval-prompt rows align across tiers. Plain ANSI SGR colour to
|
|
270
|
+
// keep this file dependency-free.
|
|
271
|
+
const ANSI_GREEN = '\x1b[32m';
|
|
272
|
+
const ANSI_YELLOW = '\x1b[33m';
|
|
273
|
+
const ANSI_RED = '\x1b[31m';
|
|
274
|
+
const ANSI_RESET = '\x1b[0m';
|
|
242
275
|
function badgeForTier(tier) {
|
|
243
276
|
switch (tier) {
|
|
244
277
|
case 'safe':
|
|
245
|
-
return
|
|
278
|
+
return `${ANSI_GREEN}[ALLOW]${ANSI_RESET} safe`;
|
|
246
279
|
case 'caution':
|
|
247
|
-
return
|
|
280
|
+
return `${ANSI_YELLOW}[WARN] ${ANSI_RESET} caution`;
|
|
248
281
|
case 'dangerous':
|
|
249
|
-
return
|
|
282
|
+
return `${ANSI_RED}[DENY] ${ANSI_RESET} dangerous`;
|
|
250
283
|
default:
|
|
251
284
|
return '';
|
|
252
285
|
}
|