aiden-runtime 4.1.4 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +250 -847
  2. package/dist/api/server.js +32 -5
  3. package/dist/cli/v4/aidenCLI.js +379 -53
  4. package/dist/cli/v4/callbacks.js +248 -0
  5. package/dist/cli/v4/chatSession.js +292 -4
  6. package/dist/cli/v4/commands/_runtimeToggleHelpers.js +92 -0
  7. package/dist/cli/v4/commands/browserDepth.js +45 -0
  8. package/dist/cli/v4/commands/cron.js +264 -0
  9. package/dist/cli/v4/commands/daemon.js +541 -0
  10. package/dist/cli/v4/commands/daemonStatus.js +253 -0
  11. package/dist/cli/v4/commands/help.js +7 -0
  12. package/dist/cli/v4/commands/index.js +20 -1
  13. package/dist/cli/v4/commands/runs.js +203 -0
  14. package/dist/cli/v4/commands/sandbox.js +48 -0
  15. package/dist/cli/v4/commands/suggestions.js +68 -0
  16. package/dist/cli/v4/commands/tce.js +41 -0
  17. package/dist/cli/v4/commands/trigger.js +378 -0
  18. package/dist/cli/v4/commands/update.js +95 -3
  19. package/dist/cli/v4/daemonAgentBuilder.js +142 -0
  20. package/dist/cli/v4/defaultSoul.js +75 -3
  21. package/dist/cli/v4/display/capabilityCard.js +26 -0
  22. package/dist/cli/v4/display/progressBar.js +41 -8
  23. package/dist/cli/v4/display.js +258 -15
  24. package/dist/cli/v4/replyRenderer.js +31 -23
  25. package/dist/cli/v4/toolPreview.js +10 -0
  26. package/dist/cli/v4/updateBootPrompt.js +170 -0
  27. package/dist/core/playwrightBridge.js +129 -0
  28. package/dist/core/toolRegistry.js +7 -1
  29. package/dist/core/v4/aidenAgent.js +371 -4
  30. package/dist/core/v4/browserState.js +436 -0
  31. package/dist/core/v4/checkpoint.js +79 -0
  32. package/dist/core/v4/daemon/bootstrap.js +604 -0
  33. package/dist/core/v4/daemon/cleanShutdown.js +154 -0
  34. package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
  35. package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
  36. package/dist/core/v4/daemon/cron/migration.js +199 -0
  37. package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
  38. package/dist/core/v4/daemon/daemonConfig.js +90 -0
  39. package/dist/core/v4/daemon/db/connection.js +106 -0
  40. package/dist/core/v4/daemon/db/migrations.js +296 -0
  41. package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
  42. package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
  43. package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
  44. package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
  45. package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
  46. package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
  47. package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
  48. package/dist/core/v4/daemon/dispatcher/index.js +53 -0
  49. package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
  50. package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
  51. package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
  52. package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
  53. package/dist/core/v4/daemon/drain.js +156 -0
  54. package/dist/core/v4/daemon/eventLoopLag.js +73 -0
  55. package/dist/core/v4/daemon/health.js +159 -0
  56. package/dist/core/v4/daemon/idempotencyStore.js +204 -0
  57. package/dist/core/v4/daemon/index.js +179 -0
  58. package/dist/core/v4/daemon/instanceTracker.js +99 -0
  59. package/dist/core/v4/daemon/resourceRegistry.js +150 -0
  60. package/dist/core/v4/daemon/restartCode.js +32 -0
  61. package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
  62. package/dist/core/v4/daemon/runStore.js +114 -0
  63. package/dist/core/v4/daemon/runtimeLock.js +167 -0
  64. package/dist/core/v4/daemon/signals.js +50 -0
  65. package/dist/core/v4/daemon/supervisor.js +272 -0
  66. package/dist/core/v4/daemon/triggerBus.js +279 -0
  67. package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
  68. package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
  69. package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
  70. package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
  71. package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
  72. package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
  73. package/dist/core/v4/daemon/triggers/email/index.js +332 -0
  74. package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
  75. package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
  76. package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
  77. package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
  78. package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
  79. package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
  80. package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
  81. package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
  82. package/dist/core/v4/daemon/triggers/webhook.js +376 -0
  83. package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
  84. package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
  85. package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
  86. package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
  87. package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
  88. package/dist/core/v4/daemon/types.js +15 -0
  89. package/dist/core/v4/dockerSession.js +461 -0
  90. package/dist/core/v4/dryRun.js +117 -0
  91. package/dist/core/v4/failureClassifier.js +779 -0
  92. package/dist/core/v4/loopTrace.js +257 -0
  93. package/dist/core/v4/recoveryReport.js +449 -0
  94. package/dist/core/v4/runtimeToggles.js +187 -0
  95. package/dist/core/v4/sandboxConfig.js +285 -0
  96. package/dist/core/v4/sandboxFs.js +316 -0
  97. package/dist/core/v4/suggestionCatalog.js +41 -0
  98. package/dist/core/v4/suggestionEngine.js +210 -0
  99. package/dist/core/v4/toolRegistry.js +18 -0
  100. package/dist/core/v4/turnState.js +587 -0
  101. package/dist/core/v4/update/checkUpdate.js +63 -3
  102. package/dist/core/v4/update/installMethodDetect.js +115 -0
  103. package/dist/core/v4/update/registryClient.js +121 -0
  104. package/dist/core/v4/update/skipState.js +75 -0
  105. package/dist/core/v4/verifier.js +448 -0
  106. package/dist/core/version.js +1 -1
  107. package/dist/core/webSearch.js +64 -24
  108. package/dist/tools/v4/browser/_observer.js +224 -0
  109. package/dist/tools/v4/browser/browserBlocker.js +396 -0
  110. package/dist/tools/v4/browser/browserClick.js +18 -1
  111. package/dist/tools/v4/browser/browserClose.js +18 -1
  112. package/dist/tools/v4/browser/browserExtract.js +5 -1
  113. package/dist/tools/v4/browser/browserFill.js +17 -1
  114. package/dist/tools/v4/browser/browserGetUrl.js +5 -1
  115. package/dist/tools/v4/browser/browserNavigate.js +16 -1
  116. package/dist/tools/v4/browser/browserScreenshot.js +5 -1
  117. package/dist/tools/v4/browser/browserScroll.js +18 -1
  118. package/dist/tools/v4/browser/browserType.js +17 -1
  119. package/dist/tools/v4/browser/captchaCheck.js +5 -1
  120. package/dist/tools/v4/executeCode.js +1 -0
  121. package/dist/tools/v4/files/fileCopy.js +56 -2
  122. package/dist/tools/v4/files/fileDelete.js +38 -1
  123. package/dist/tools/v4/files/fileList.js +12 -1
  124. package/dist/tools/v4/files/fileMove.js +59 -2
  125. package/dist/tools/v4/files/filePatch.js +43 -1
  126. package/dist/tools/v4/files/fileRead.js +12 -1
  127. package/dist/tools/v4/files/fileWrite.js +41 -1
  128. package/dist/tools/v4/index.js +71 -58
  129. package/dist/tools/v4/memory/memoryAdd.js +14 -0
  130. package/dist/tools/v4/memory/memoryRemove.js +14 -0
  131. package/dist/tools/v4/memory/memoryReplace.js +15 -0
  132. package/dist/tools/v4/memory/sessionSummary.js +12 -0
  133. package/dist/tools/v4/process/processKill.js +19 -0
  134. package/dist/tools/v4/process/processList.js +1 -0
  135. package/dist/tools/v4/process/processLogRead.js +1 -0
  136. package/dist/tools/v4/process/processSpawn.js +13 -0
  137. package/dist/tools/v4/process/processWait.js +1 -0
  138. package/dist/tools/v4/sessions/recallSession.js +1 -0
  139. package/dist/tools/v4/sessions/sessionList.js +1 -0
  140. package/dist/tools/v4/sessions/sessionSearch.js +1 -0
  141. package/dist/tools/v4/skills/lookupToolSchema.js +2 -0
  142. package/dist/tools/v4/skills/skillManage.js +13 -0
  143. package/dist/tools/v4/skills/skillView.js +1 -0
  144. package/dist/tools/v4/skills/skillsList.js +1 -0
  145. package/dist/tools/v4/subagent/subagentFanout.js +1 -0
  146. package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
  147. package/dist/tools/v4/system/appClose.js +13 -0
  148. package/dist/tools/v4/system/appInput.js +13 -0
  149. package/dist/tools/v4/system/appLaunch.js +13 -0
  150. package/dist/tools/v4/system/clipboardRead.js +1 -0
  151. package/dist/tools/v4/system/clipboardWrite.js +14 -0
  152. package/dist/tools/v4/system/mediaKey.js +12 -0
  153. package/dist/tools/v4/system/mediaSessions.js +1 -0
  154. package/dist/tools/v4/system/mediaTransport.js +13 -0
  155. package/dist/tools/v4/system/naturalEvents.js +1 -0
  156. package/dist/tools/v4/system/nowPlaying.js +1 -0
  157. package/dist/tools/v4/system/osProcessList.js +1 -0
  158. package/dist/tools/v4/system/screenshot.js +1 -0
  159. package/dist/tools/v4/system/systemInfo.js +1 -0
  160. package/dist/tools/v4/system/volumeSet.js +17 -0
  161. package/dist/tools/v4/terminal/shellExec.js +81 -9
  162. package/dist/tools/v4/web/deepResearch.js +1 -0
  163. package/dist/tools/v4/web/openUrl.js +1 -0
  164. package/dist/tools/v4/web/webFetch.js +1 -0
  165. package/dist/tools/v4/web/webPage.js +1 -0
  166. package/dist/tools/v4/web/webSearch.js +1 -0
  167. package/dist/tools/v4/web/youtubeSearch.js +1 -0
  168. package/package.json +7 -1
  169. package/plugins/aiden-plugin-cdp-browser/.granted-permissions.json +8 -0
@@ -30,8 +30,8 @@ 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.4';
34
- exports.DEFAULT_SOUL_MD = `You are Aiden — a local-first AI agent built by Taracod.
33
+ exports.BUNDLED_SOUL_VERSION = 'v4.5.0';
34
+ exports.DEFAULT_SOUL_MD = `You are Aiden — a local-first AI agent built by Shiva Deore at Taracod.
35
35
 
36
36
  Identity:
37
37
  - You run on the user's machine, native Windows/Linux/macOS (not WSL2).
@@ -47,7 +47,7 @@ Voice:
47
47
 
48
48
  Behavior:
49
49
  - Default to action over discussion. The user wants results.
50
- - When asked who you are, identify as Aiden. Not "a large language model."
50
+ - When asked who you are, identify as Aiden, built by Shiva Deore at Taracod. Not "a large language model."
51
51
  - When asked what you can do, mention specific skills/tools, not generic capabilities.
52
52
  - If user mentions trading/NSE/markets, you have specialized skills for that.
53
53
 
@@ -318,6 +318,78 @@ the tool calls within a single turn instead of returning halfway and
318
318
  asking the user what to do next.
319
319
  </keep_going>
320
320
 
321
+ Limits:
322
+ - You can't bypass approval prompts for dangerous commands.
323
+ - You don't lie to look smart. If you don't know, you say so.
324
+ `,
325
+ // v4.1.4 default — Voice rewrite ("Match the user's energy. When the
326
+ // user asks a thoughtful question…") + EXECUTION_DISCIPLINE_PROSE
327
+ // softening. Identity attribution still "built by Taracod" only —
328
+ // v4.1.5 adds "Shiva Deore at" to both identity lines. Users who
329
+ // installed any v4.1.4.x build have this verbatim text on disk;
330
+ // silent-upgrade picks them up here.
331
+ //
332
+ // CATCH-UP NOTE: this entry was inadvertently omitted from the
333
+ // v4.1.4 ship (the snapshot appended at that time matched v4.1.2's
334
+ // Voice block, not the v4.1.4 rewrite). v4.1.5 appends it so the
335
+ // migration path catches v4.1.4 installs correctly.
336
+ `You are Aiden — a local-first AI agent built by Taracod.
337
+
338
+ Identity:
339
+ - You run on the user's machine, native Windows/Linux/macOS (not WSL2).
340
+ - You have 72 bundled skills + access to install more via skills.sh.
341
+ - You remember past sessions via persistent storage.
342
+ - You have 40 tools spanning files, browser, terminal, web, memory.
343
+
344
+ Voice:
345
+ - Match the user's energy. When the user asks a thoughtful question (opinion, exploration, comparison), engage thoughtfully. When the user asks transactionally, stay tight.
346
+ - On thoughtful questions, share the reasoning before the answer — what you considered, what you discarded, why.
347
+ - Honest above all — if you didn't do something, say so. If you're not sure, say so.
348
+ - You never claim to "have run" a tool unless the trace shows it.
349
+
350
+ Behavior:
351
+ - Default to action over discussion. The user wants results.
352
+ - When asked who you are, identify as Aiden. Not "a large language model."
353
+ - When asked what you can do, mention specific skills/tools, not generic capabilities.
354
+ - If user mentions trading/NSE/markets, you have specialized skills for that.
355
+
356
+ <act_dont_ask>
357
+ When a request has an obvious default interpretation, act on it
358
+ immediately instead of asking for clarification. Examples:
359
+ - "play me a popular song" / "play X on youtube" → load skill_view(media-search)
360
+ and follow it. Substitute fuzzy phrases ("popular song") with a specific
361
+ chart-topper BEFORE searching, then open_url a /watch?v= URL once.
362
+ NEVER search verbatim "popular song" — that returns articles, not music.
363
+ - "what files are in my Downloads?" → file_list on Downloads. Don't ask
364
+ "which user?" — it's the current user.
365
+ - "is port 443 open?" → check this machine. Don't ask "open where?"
366
+ Only ask for clarification when the ambiguity genuinely changes which
367
+ tool you would call.
368
+ </act_dont_ask>
369
+
370
+ <prerequisite_checks>
371
+ Before acting, check whether prerequisite discovery, lookup, or
372
+ context-gathering steps are needed. If a step depends on output from a
373
+ prior step, resolve that dependency first. Don't skip prerequisite
374
+ steps just because the final action seems obvious.
375
+ </prerequisite_checks>
376
+
377
+ <missing_context>
378
+ If required context is missing, do NOT guess or hallucinate. Use the
379
+ appropriate lookup tool when missing information is retrievable
380
+ (file_read, file_list, web_search, fetch_url, session_search,
381
+ system_info). Ask a clarifying question ONLY when no tool can resolve
382
+ the ambiguity.
383
+ </missing_context>
384
+
385
+ <keep_going>
386
+ Work autonomously until the task is fully resolved. Don't stop with a
387
+ plan — execute it. Multi-step tasks (open browser → search → click
388
+ result; or list files → read each → summarise) are expected; chain
389
+ the tool calls within a single turn instead of returning halfway and
390
+ asking the user what to do next.
391
+ </keep_going>
392
+
321
393
  Limits:
322
394
  - You can't bypass approval prompts for dangerous commands.
323
395
  - You don't lie to look smart. If you don't know, you say so.
@@ -67,6 +67,32 @@ function renderCapabilityCard(data, colorize) {
67
67
  // Compose the inner rows that boxSharp will wrap. Each row is the
68
68
  // CONTENT (no border) — boxSharp adds the side borders + padding.
69
69
  const rows = [];
70
+ // v4.2 Phase 3 — optional "what happened" one-liner above the
71
+ // canStill section. Rendered as a muted-tone line so it reads as
72
+ // context, not action. Skipped cleanly when absent → v4.1.3
73
+ // capability-card behaviour preserved for non-Phase-3 callers.
74
+ if (data.whatHappened) {
75
+ rows.push('');
76
+ rows.push(colorize(truncToContent(data.whatHappened), 'muted'));
77
+ }
78
+ // v4.2 Phase 3 — optional failure-category pill row. Each entry
79
+ // renders as `<category>(<count>)` separated by " · " bullets.
80
+ // Pre-sorted by the generator (desc count then category priority);
81
+ // renderer just formats. Skipped cleanly when absent.
82
+ if (data.failuresByCategory && data.failuresByCategory.length > 0) {
83
+ const pills = data.failuresByCategory
84
+ .map((p) => `${p.category}(${p.count})`)
85
+ .join(' · ');
86
+ const label = colorize('Failures:', 'error');
87
+ rows.push(`${label} ${truncToContent(pills)}`);
88
+ }
89
+ // v4.3 Phase 5 — optional one-line browser context summary
90
+ // (active tab, blocker kind, other-tab count, stale-ref retries).
91
+ // Already pre-formatted by recoveryReport's `buildBrowserContextLine`.
92
+ // Rendered as a muted-tone line below the Failures: row when present.
93
+ if (data.browserContext) {
94
+ rows.push(colorize(truncToContent(data.browserContext), 'muted'));
95
+ }
70
96
  if (data.canStill.length > 0) {
71
97
  rows.push('');
72
98
  rows.push(heading('Can still:'));
@@ -52,8 +52,22 @@ const display_1 = require("../display");
52
52
  */
53
53
  const BAR_CELLS = 10;
54
54
  /** Glyphs. Chosen for clean visual weight at the standard mono font. */
55
- const FILLED = '▰';
56
- const EMPTY = '▱';
55
+ // v4.1.5 Phase 1d (Q-P1) — glyph palette switch.
56
+ //
57
+ // Was `▰` (U+25B0 BLACK RECTANGLE) + `▱` (U+25B1 WHITE RECTANGLE),
58
+ // from Unicode's "Geometric Shapes" block. NOT in CP437; Windows
59
+ // legacy console fonts (Lucida Console / Consolas defaults) render
60
+ // them as tofu boxes. User visual smoke confirmed the regression
61
+ // on Windows Terminal + ConPTY combinations.
62
+ //
63
+ // New: `▓` (U+2593 DARK SHADE) + `░` (U+2591 LIGHT SHADE), from
64
+ // Unicode's "Block Elements" block AND CP437 — universally
65
+ // supported on every Windows console going back to DOS. Same shade
66
+ // family the existing `statusFooter` (display.ts:773) has shipped
67
+ // with since v3 without ever being garbled. Cleaner palette
68
+ // coherence across all bar surfaces.
69
+ const FILLED = '▓';
70
+ const EMPTY = '░';
57
71
  /**
58
72
  * Create a progress-bar handle bound to a writable stream + skin.
59
73
  * The bar paints on the next `update` call — there's no initial
@@ -89,19 +103,38 @@ function createProgressBar(out, skin) {
89
103
  const gutter = (0, frame_1.getIndent)(0);
90
104
  return `${gutter}${bar} ${label}`;
91
105
  };
106
+ // v4.1.5 Part 1a — Issue M (Windows ConPTY buffering fix).
107
+ //
108
+ // Mirrors the activityIndicator pattern: the bar OWNS one terminal
109
+ // row. Every paint ends with `\n` so the buffer flushes; the cursor
110
+ // sits on the row BELOW the bar while it's visible. Erase walks
111
+ // up + clears the bar's row, leaving the cursor at col 0 of the
112
+ // now-empty row for the caller's next write. See activityIndicator
113
+ // for the full rationale (Windows ConPTY buffers no-newline writes
114
+ // until a `\n` arrives — without this, long-running stream paints
115
+ // never visually appeared).
116
+ const ANSI_UP_ERASE = '\x1b[1A\x1b[2K';
92
117
  const paint = () => {
93
118
  if (!isTty || hidden)
94
119
  return;
95
- // `\r\x1b[K` — carriage return + erase to end of line, then
96
- // rewrite. Same single-line overwrite pattern as the activity
97
- // indicator and tool-row live tick.
98
- out.write(`\r\x1b[K${buildLine()}`);
99
- printed = true;
120
+ if (printed) {
121
+ // Subsequent paint: walk up to the bar's row, clear it, rewrite,
122
+ // drop a newline so the cursor lands on the row BELOW the bar.
123
+ out.write(`${ANSI_UP_ERASE}${buildLine()}\n`);
124
+ }
125
+ else {
126
+ // First paint: just write the bar + `\n`. Cursor moves to the
127
+ // row below, ready for subsequent walk-up-and-erase ticks.
128
+ out.write(`${buildLine()}\n`);
129
+ printed = true;
130
+ }
100
131
  lastPaintTokens = outputTokens;
101
132
  };
102
133
  const erase = () => {
134
+ // Walk up to the bar's row and clear it. No trailing `\n` — the
135
+ // caller will write content here and include its own newline.
103
136
  if (isTty && printed)
104
- out.write('\r\x1b[K');
137
+ out.write(ANSI_UP_ERASE);
105
138
  };
106
139
  return {
107
140
  update(n, max) {
@@ -18,12 +18,13 @@
18
18
  *
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.TOOL_ROW_ARG_CAP = exports.TOOL_ROW_NAME_PAD = exports.Display = exports.SPINNER_PHRASES = exports.TOOL_ICONS = void 0;
21
+ exports.TRAIL_HIDE_TOOLS = exports.TOOL_ROW_ARG_CAP = exports.TOOL_ROW_NAME_PAD = exports.Display = exports.SPINNER_PHRASES = exports.TOOL_ICONS = void 0;
22
22
  exports.iconForTool = iconForTool;
23
23
  exports.detectConfiguredChannels = detectConfiguredChannels;
24
24
  exports.summarizeConfiguredChannels = summarizeConfiguredChannels;
25
25
  exports.summarizeChannelState = summarizeChannelState;
26
26
  exports.voiceIndicator = voiceIndicator;
27
+ exports.makeNoOpToolRowHandle = makeNoOpToolRowHandle;
27
28
  exports.previewToolArgs = previewToolArgs;
28
29
  exports.verbForActivity = verbForActivity;
29
30
  exports.isPreFramedLine = isPreFramedLine;
@@ -522,14 +523,21 @@ class Display {
522
523
  return (0, box_1.truncateVisible)(text, INTERIOR);
523
524
  return text + ' '.repeat(INTERIOR - v);
524
525
  };
526
+ // v4.5 TUI polish — add a leading + trailing blank line so the
527
+ // box has visual breathing room from the lines above/below, and
528
+ // a trailing interior blank so contact info doesn't crowd the
529
+ // bottom border.
525
530
  return [
531
+ '',
526
532
  lidIndent + lid,
527
533
  wallIndent + pipe + ' '.repeat(INTERIOR) + pipe,
528
534
  wallIndent + pipe + padInner(` ${heart} ${val('Built solo')}`) + pipe,
529
535
  wallIndent + pipe + padInner(` ${lab('GitHub:')} ${val('github.com/taracodlabs/aiden')}`) + pipe,
530
536
  wallIndent + pipe + padInner(` ${lab('Web:')} ${val('aiden.taracod.com')}`) + pipe,
531
537
  wallIndent + pipe + padInner(` ${lab('Contact:')} ${val('contact@taracod.com')}`) + pipe,
538
+ wallIndent + pipe + ' '.repeat(INTERIOR) + pipe,
532
539
  wallIndent + pipe + lid + pipe,
540
+ '',
533
541
  ].join('\n');
534
542
  }
535
543
  /**
@@ -772,7 +780,21 @@ class Display {
772
780
  * content below MUST call `pause()` first; otherwise their content
773
781
  * lands on the indicator line and the next tick clobbers it.
774
782
  */
775
- activityIndicator(initialVerb = 'thinking') {
783
+ /**
784
+ * v4.1.5 Issue K — wave-bar option.
785
+ *
786
+ * When `opts.waveBar === true` (DEFAULT), the indicator paints a
787
+ * second row BELOW the verb line — a 10-cell `▰▱` snake-scroll
788
+ * heartbeat that gives visible motion during long pre-first-token
789
+ * gaps even when the verb doesn't change. The bar is NOT progress:
790
+ * it's a constant-cadence heartbeat (250ms shared with the dot
791
+ * pulse), explicitly not a percentage indicator.
792
+ *
793
+ * Pass `{ waveBar: false }` for back-compat with v4.1.4 tests that
794
+ * assert single-row geometry. Production callers (chatSession) get
795
+ * the wave bar by default.
796
+ */
797
+ activityIndicator(initialVerb = 'thinking', opts = {}) {
776
798
  const sk = this.skin;
777
799
  const out = this.out;
778
800
  const isTty = !!out.isTTY;
@@ -799,6 +821,13 @@ class Display {
799
821
  // feedback; a separate bottom-of-screen footer can be added in
800
822
  // v4.1.5 if wanted, but it must NOT be glued to the indicator.
801
823
  const glyph = sk.applyColors('▲', 'brand');
824
+ // v4.1.5 Issue K — wave-bar state. Snake-scroll: a 3-cell `▰`
825
+ // block slides across 10 cells, wrapping at the right edge. Same
826
+ // 250ms tick as the verb dot pulse — one timer drives both rows.
827
+ const waveBarEnabled = opts.waveBar !== false; // default true
828
+ const WAVE_CELLS = 10;
829
+ const WAVE_BLOCK = 3;
830
+ let waveFrame = 0;
802
831
  const buildLine = () => {
803
832
  const dots = '.'.repeat(dotFrame); // 0..3 dots
804
833
  const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
@@ -808,14 +837,92 @@ class Display {
808
837
  // `▲ {verb}{dots-padded-to-3}{elapsed?}`
809
838
  return `${glyph} ${verb}${dots.padEnd(3, ' ')}${elapsedStr}`;
810
839
  };
840
+ /**
841
+ * v4.1.5 Issue K — render the wave-bar row. A 3-cell `▰` block at
842
+ * positions `[waveFrame, waveFrame+1, waveFrame+2]` mod 10. The
843
+ * filled cells paint brand orange, empty cells paint warm-muted.
844
+ * Same width + glyph set as the token progress bar so the two
845
+ * rows feel like a coherent palette (one is heartbeat, the other
846
+ * is real progress).
847
+ *
848
+ * Heartbeat semantics: this is NOT progress. The wave moves at a
849
+ * constant 250ms cadence regardless of any backend metric. It
850
+ * exists purely so the user sees motion during the unobservable
851
+ * TTFT (time-to-first-token) wait. The verb row above carries
852
+ * any real lifecycle signal via `setVerb()`.
853
+ */
854
+ const buildWave = () => {
855
+ // v4.1.5 Phase 1d (Q-P1) — glyph palette switch. Was `▰`/`▱`
856
+ // (U+25B0/B1, Geometric Shapes) which legacy Windows console
857
+ // fonts render as tofu. Now `▓`/`░` (U+2593/91, Block Elements
858
+ // — in CP437, universally supported). Matches the existing
859
+ // statusFooter chrome that's shipped since v3 without ever
860
+ // being garbled.
861
+ const filled = new Set();
862
+ for (let i = 0; i < WAVE_BLOCK; i += 1) {
863
+ filled.add((waveFrame + i) % WAVE_CELLS);
864
+ }
865
+ // Render cells in order so the snake-scroll visually slides:
866
+ // we paint cell-by-cell with the right color, joined into one
867
+ // string. ANSI runs reset per cell — slight overhead but keeps
868
+ // glyph order true to position. Brand orange filled, warm-muted
869
+ // empty.
870
+ const cells = [];
871
+ for (let c = 0; c < WAVE_CELLS; c += 1) {
872
+ cells.push(filled.has(c)
873
+ ? sk.applyColors('▓', 'brand')
874
+ : sk.applyColors('░', 'muted'));
875
+ }
876
+ return cells.join('');
877
+ };
878
+ // v4.1.5 Part 1a — Issue M (Windows ConPTY buffering fix).
879
+ //
880
+ // Prior pattern wrote `\r\x1b[K{indicator}` with NO trailing
881
+ // newline. On Windows ConPTY, `process.stdout` buffers no-newline
882
+ // writes — none of the 60 indicator ticks during a 15s gap
883
+ // actually rendered. The final reply's `\n` chars eventually
884
+ // flushed the buffer, but by then the indicator's stop()-erase
885
+ // had also been buffered + flushed, so the user saw 15s of blank
886
+ // followed by the reply dumping all at once.
887
+ //
888
+ // Fix: indicator OWNS one terminal row. Every write that paints
889
+ // the indicator ends with `\n`, which forces a flush on every
890
+ // platform. The cursor sits on the LINE BELOW the indicator
891
+ // while it's running (one visible empty row gap). When the
892
+ // indicator stops/pauses, we walk back UP to the indicator's
893
+ // row and erase it — the cursor then sits at col 0 of that
894
+ // (now empty) row, ready for the caller to write whatever
895
+ // content follows (header, tool row, stream output).
896
+ //
897
+ // ANSI primitives:
898
+ // `\x1b[1A` — cursor up 1 line
899
+ // `\x1b[2K` — erase the whole current line
900
+ // Sequence on tick: walk up → erase → paint → `\n` → cursor below.
901
+ // Sequence on erase: walk up → erase (no newline). Cursor on the
902
+ // now-empty indicator row, ready for caller.
903
+ const ANSI_UP_ERASE = '\x1b[1A\x1b[2K';
811
904
  const renderTick = () => {
812
905
  if (stopped || paused || !isTty)
813
906
  return;
814
907
  dotFrame = (dotFrame + 1) % 4;
815
- // `\r\x1b[K`carriage return + clear line then write the
816
- // fresh indicator. No newline at end: cursor stays at end of
817
- // the indicator line, ready for the next overwrite.
818
- out.write(`\r\x1b[K${buildLine()}`);
908
+ // v4.1.5 Issue K — wave snake-scroll advances 1 cell per tick.
909
+ // Same 250ms cadence as the dot pulse, so both rows move in
910
+ // visible lockstep. Modulo WAVE_CELLS wraps the leading block
911
+ // back to the left edge.
912
+ waveFrame = (waveFrame + 1) % WAVE_CELLS;
913
+ if (waveBarEnabled) {
914
+ // 2-row layout: walk up TWO rows (two separate up-1+erase
915
+ // sequences, which keeps the `\x1b[1A\x1b[2K` substring
916
+ // assertion-compatible), repaint both, drop newlines so the
917
+ // cursor lands on the row below the wave bar.
918
+ out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}` +
919
+ `${buildLine()}\n` +
920
+ `${buildWave()}\n`);
921
+ }
922
+ else {
923
+ // Single-row layout (back-compat with v4.1.4 tests).
924
+ out.write(`${ANSI_UP_ERASE}${buildLine()}\n`);
925
+ }
819
926
  };
820
927
  const startTick = () => {
821
928
  if (stopped || !isTty || tickTimer !== null)
@@ -829,12 +936,41 @@ class Display {
829
936
  }
830
937
  };
831
938
  const eraseLine = () => {
832
- if (isTty && printed)
833
- out.write('\r\x1b[K');
939
+ // Walk up to the indicator's row(s) + erase, then drop ONE
940
+ // newline so the cursor lands on a blank line BELOW the
941
+ // indicator's old footprint. v4.1.6 polish: previous behavior
942
+ // left the cursor at col 0 of the just-erased row so caller
943
+ // writes (agentHeader, tool row, etc.) sat tight against where
944
+ // the indicator had been. v4.1.5 visual smoke flagged the
945
+ // wave-bar→`┃ Aiden` proximity as feeling cramped. The
946
+ // trailing `\n` gains one visible blank row of breathing space
947
+ // AND adds another Windows ConPTY flush trigger (Issue M).
948
+ //
949
+ // v4.1.5 Issue K — with wave bar enabled, walk up 2 rows (two
950
+ // up-1+erase sequences). Without the bar, walk up 1 row.
951
+ if (!isTty || !printed)
952
+ return;
953
+ if (waveBarEnabled) {
954
+ out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}\n`);
955
+ }
956
+ else {
957
+ out.write(`${ANSI_UP_ERASE}\n`);
958
+ }
834
959
  };
835
- // Initial paint — only on TTY.
960
+ // Initial paint — only on TTY. Indicator + `\n` so the buffer
961
+ // flushes and the cursor sits on the row below, ready for the
962
+ // first tick to walk back up.
963
+ //
964
+ // v4.1.5 Issue K — when wave bar is enabled, paint TWO rows:
965
+ // verb row + wave row, each with trailing `\n`. Cursor lands on
966
+ // the row below the wave bar. The first tick will walk up 2.
836
967
  if (isTty) {
837
- out.write(buildLine());
968
+ if (waveBarEnabled) {
969
+ out.write(`${buildLine()}\n${buildWave()}\n`);
970
+ }
971
+ else {
972
+ out.write(`${buildLine()}\n`);
973
+ }
838
974
  printed = true;
839
975
  startTick();
840
976
  }
@@ -861,9 +997,18 @@ class Display {
861
997
  return;
862
998
  // Caller has just finished writing its own content (typically
863
999
  // ending with `\n`), so the cursor is on a fresh line below
864
- // whatever was there. Render the indicator there and arm the
865
- // tick again.
866
- out.write(buildLine());
1000
+ // whatever was there. Paint the indicator + `\n` to claim the
1001
+ // current row(s) and leave the cursor on the row below — same
1002
+ // invariant the initial paint and tick maintain. Trailing `\n`
1003
+ // also flushes Windows ConPTY buffering (Issue M).
1004
+ //
1005
+ // v4.1.5 Issue K — repaint BOTH rows when wave bar enabled.
1006
+ if (waveBarEnabled) {
1007
+ out.write(`${buildLine()}\n${buildWave()}\n`);
1008
+ }
1009
+ else {
1010
+ out.write(`${buildLine()}\n`);
1011
+ }
867
1012
  printed = true;
868
1013
  startTick();
869
1014
  },
@@ -894,6 +1039,31 @@ class Display {
894
1039
  // completion so each line in the log carries the final state — no
895
1040
  // ANSI cursor games on a dumb sink.
896
1041
  toolRow(name, args) {
1042
+ // v4.1.5 Phase 1d (Q-Q2-a) — TRAIL_HIDE_TOOLS suppression.
1043
+ //
1044
+ // Some tools are pure agent plumbing — the model calls them to
1045
+ // introspect its own registry, not to do user-visible work.
1046
+ // `lookup_tool_schema` is the canonical case: during planning
1047
+ // the agent may invoke it 30+ times to discover unfamiliar tool
1048
+ // shapes. Each call is a sub-millisecond in-memory lookup, but
1049
+ // they flood the visible trail with noise that obscures the
1050
+ // actual user-relevant tool calls.
1051
+ //
1052
+ // Short-circuit: hidden tools get a NO-OP handle that satisfies
1053
+ // the `ToolRowHandle` contract (ok/fail/degraded/retry/blocked/
1054
+ // emptyRetry/emptyFail all defined but write nothing). The
1055
+ // execution path itself is unaffected — the agent still calls
1056
+ // the tool, the planner / skill-enforcement trackers still
1057
+ // record it. Only the visual row is suppressed.
1058
+ //
1059
+ // CRITICAL invariant: `setBeforeFirstToolHook` is fired by
1060
+ // callbacks.ts BEFORE `toolRow()` is called (see callbacks.ts
1061
+ // onToolCall 'before' branch), so `turnHadTools` flips even for
1062
+ // hidden tools. The separator logic stays correct regardless of
1063
+ // whether ONLY hidden tools fired this turn.
1064
+ if (exports.TRAIL_HIDE_TOOLS.has(name)) {
1065
+ return makeNoOpToolRowHandle();
1066
+ }
897
1067
  const sk = this.skin;
898
1068
  // ── Build the fixed left portion (icon + verb + detail) ────────────
899
1069
  // v4.1.3-repl-polish: icons default ON; set AIDEN_UI_ICONS=0 to
@@ -995,8 +1165,31 @@ class Display {
995
1165
  writeFinal(`ok ${formatToolDuration(durationMs)} after ${retries} ${retries === 1 ? 'retry' : 'retries'}`, 'warn');
996
1166
  }
997
1167
  else {
998
- // Clean successSILENT. Erase on TTY; emit nothing on non-TTY.
999
- eraseLast();
1168
+ // v4.1.5 Issue N persistent tool trail in scrollback.
1169
+ //
1170
+ // Prior behaviour: silent erase on clean success (`eraseLast()`
1171
+ // with no replacement write). Tool rows for successful tools
1172
+ // vanished, leaving only the markdown reply visible afterward.
1173
+ // The user couldn't see WHAT actions Aiden took unless a tool
1174
+ // failed or degraded.
1175
+ //
1176
+ // Fix: replace the silent erase with a completed-state row
1177
+ // painted entirely in warm-muted (`#b8a89a` from v4.1.4). The
1178
+ // duration suffix replaces the live `running Ns…` chrome; the
1179
+ // whole row reads "done" via reduced visual weight. Failed /
1180
+ // degraded / retry outcomes keep their existing coloured paint
1181
+ // (error red, degraded yellow, warn amber) — only clean success
1182
+ // shifts from "silent" to "muted-persistent."
1183
+ //
1184
+ // The persistence mechanism is the existing `writeFinal` path:
1185
+ // it walks up + erases the running row, then writes the final
1186
+ // row with trailing `\n`. The row sits in scrollback because
1187
+ // `streamComplete` rerenders only the post-tool stream chunk
1188
+ // (via `streamLineCount` which was reset to 0 inside
1189
+ // `commitStreamChunk` before this row wrote). No additional
1190
+ // isolation machinery needed — already verified by 13/13
1191
+ // `smoke-stream-rerender.ts` regressions.
1192
+ writeFinal(formatToolDuration(durationMs), 'muted');
1000
1193
  }
1001
1194
  },
1002
1195
  fail(durationMs, retries = 0) {
@@ -1698,6 +1891,56 @@ function renderRmsBar(rms) {
1698
1891
  exports.TOOL_ROW_NAME_PAD = toolTrail_1.TRAIL_VERB_PAD;
1699
1892
  /** @deprecated Use TRAIL_DETAIL_CAP. */
1700
1893
  exports.TOOL_ROW_ARG_CAP = toolTrail_1.TRAIL_DETAIL_CAP;
1894
+ /**
1895
+ * v4.1.5 Phase 1d (Q-Q2-a) — names of tools that should be SUPPRESSED
1896
+ * from the visible tool-trail row, even though they still execute
1897
+ * normally through the agent loop.
1898
+ *
1899
+ * The canonical case is `lookup_tool_schema`: the agent calls it
1900
+ * during planning to introspect tool registry entries (in-memory
1901
+ * registry get, sub-millisecond per call). On complex prompts the
1902
+ * model may fire it 30+ times in a row, flooding the visible trail
1903
+ * with rows that don't represent user-meaningful work. Suppressing
1904
+ * them keeps the trail focused on the tools that did real work
1905
+ * (web_search, file_read, etc.).
1906
+ *
1907
+ * Suppression happens at `Display.toolRow()` entry — it returns a
1908
+ * no-op handle that satisfies the `ToolRowHandle` contract but
1909
+ * never writes to stdout. The agent's `callbacks.onToolCall`
1910
+ * dispatch is unchanged: `setBeforeFirstToolHook` still fires (so
1911
+ * `turnHadTools` flips for the separator-emission logic), and
1912
+ * skill-enforcement / honesty-trace tracking still records the
1913
+ * call. Only the visual row is hidden.
1914
+ *
1915
+ * Exported as a `Set` so callers can mutate at runtime if they
1916
+ * need to hide additional tools (e.g. user customization, MCP
1917
+ * plumbing tools). Mutation-of-shared-state is intentional — there's
1918
+ * no per-session config plumbing for "trail hidden tools" yet, so
1919
+ * the env-var pattern (`AIDEN_TRAIL_HIDE=tool1,tool2`) would be the
1920
+ * v4.1.6 evolution.
1921
+ */
1922
+ exports.TRAIL_HIDE_TOOLS = new Set([
1923
+ 'lookup_tool_schema',
1924
+ ]);
1925
+ /**
1926
+ * v4.1.5 Phase 1d helper — produces a `ToolRowHandle` that satisfies
1927
+ * the contract but writes nothing. Used by hidden tools (see
1928
+ * `TRAIL_HIDE_TOOLS`) and as a safe fallback. All methods are inert.
1929
+ *
1930
+ * Pure — no side effects, no closures over Display state. Safe to
1931
+ * call from any thread / phase.
1932
+ */
1933
+ function makeNoOpToolRowHandle() {
1934
+ return {
1935
+ ok: () => { },
1936
+ fail: () => { },
1937
+ degraded: () => { },
1938
+ retry: () => { },
1939
+ blocked: () => { },
1940
+ emptyRetry: () => { },
1941
+ emptyFail: () => { },
1942
+ };
1943
+ }
1701
1944
  /**
1702
1945
  * Build a compact, single-line preview of the tool's arguments. Picks
1703
1946
  * the most informative scalar fields when the args are an object, then
@@ -43,32 +43,38 @@ function paint(kind) {
43
43
  return (text) => (0, skinEngine_1.getSkinEngine)().applyColors(text, kind);
44
44
  }
45
45
  /**
46
- * v4.1.3-essentials: bold (`**foo**`) markdown emphasis renders as
47
- * ANSI bold + underline. Previously painted 'brand' (orange) which
48
- * collided with the heading hierarchy. Briefly tried bold + bright-
49
- * white; landed on bold + underline because underline carries
50
- * emphasis without consuming a color slotthe palette stays
51
- * available for state semantics (yellow=degraded, red=error, etc.).
46
+ * v4.1.3-essentials → v4.5 TUI polish: bold (`**foo**`) markdown
47
+ * emphasis renders as plain ANSI bold. Earlier iterations tried
48
+ * 'brand' (orange, collided with heading hierarchy), bright-white
49
+ * (low contrast on dark themes), and bold+underline (made bulleted
50
+ * list items look like clickable hyperlinksuser feedback after
51
+ * v4.5 Phase 8 stabilisation).
52
52
  *
53
- * ANSI sequence: `\x1b[1m\x1b[4m{text}\x1b[24m\x1b[22m` bold ON +
54
- * underline ON, then underline OFF + bold OFF. Reset order matters
55
- * (underline first, bold second) so the closing codes don't reorder
56
- * styles surprisingly on terminals that batch SGR updates.
53
+ * Landed on bold-only: weight carries emphasis, no color slot
54
+ * consumed, and no underline confusion with terminal URL/path
55
+ * auto-highlight features.
57
56
  *
58
- * Bypasses the skin system intentionally bold-as-underline is an
59
- * opinionated default for this slice. Same caveat as the prior bold-
60
- * as-color iteration: nested markdown loses the outer style after
61
- * close (pre-existing limitation of the painter-stack architecture).
57
+ * ANSI sequence: `\x1b[1m{text}\x1b[22m` bold ON, bold OFF.
58
+ *
59
+ * Bypasses the skin system intentionally emphasis is an
60
+ * opinionated default for this slice. Same caveat as the prior
61
+ * bold-as-color iteration: nested markdown loses the outer style
62
+ * after close (pre-existing limitation of the painter-stack
63
+ * architecture).
62
64
  *
63
65
  * Honors `NO_COLOR=1` per the standard (skips the wrap entirely).
64
- * Strictly speaking `NO_COLOR` is about color and underline isn't
65
- * a color, but the wrap still emits ANSI escapes; honoring the env
66
- * var keeps output paste-safe in scripted contexts.
66
+ * Strictly speaking `NO_COLOR` is about color, but the wrap still
67
+ * emits ANSI escapes; honoring the env var keeps output paste-safe
68
+ * in scripted contexts.
69
+ *
70
+ * Function name: `paintEmphasis` rather than `paintBold` because
71
+ * the latter is already taken by a different (parameterised) helper
72
+ * below.
67
73
  */
68
- function paintBoldUnderline(text) {
74
+ function paintEmphasis(text) {
69
75
  if (process.env.NO_COLOR && process.env.NO_COLOR !== '')
70
76
  return text;
71
- return `\x1b[1m\x1b[4m${text}\x1b[24m\x1b[22m`;
77
+ return `\x1b[1m${text}\x1b[22m`;
72
78
  }
73
79
  /**
74
80
  * v4.1.3-essentials reply-polish: bold-on + skin paint + bold-off.
@@ -367,10 +373,12 @@ function getReplyRenderer() {
367
373
  hr: () => paint('muted')('─'.repeat((0, frame_1.getBodyWidth)())) + '\n',
368
374
  listitem: renderListItem,
369
375
  paragraph: (text) => `${text}\n\n`,
370
- // v4.1.3-essentials: bold renders as ANSI bold + underline
371
- // (was 'brand' / orange, then bright-white; landed on underline
372
- // so the color palette stays available for state semantics).
373
- strong: paintBoldUnderline,
376
+ // v4.1.3-essentials → v4.5 TUI polish: bold renders as plain ANSI
377
+ // bold. Earlier iterations tried orange (collision with headings),
378
+ // bright-white (low contrast), and bold+underline (made bulleted
379
+ // **labels** look like clickable links — user feedback after v4.5
380
+ // Phase 8 stabilisation). Weight alone carries emphasis.
381
+ strong: paintEmphasis,
374
382
  em: paint('muted'),
375
383
  // v4.1.3-essentials reply-polish: inline `` `code` `` — strip
376
384
  // the literal backticks (used to leak into the visible output)
@@ -69,6 +69,16 @@ exports.TOOL_PRIMARY_ARG = {
69
69
  skill_view: 'name',
70
70
  skill_manage: 'action',
71
71
  skills_list: '',
72
+ // v4.1.5 Phase 1d (Q-Q1-a) — registry introspection tool. Args
73
+ // shape: `{ toolName: 'web_search' }`. The agent uses this to
74
+ // discover unfamiliar tool schemas during planning. Surface the
75
+ // target tool name so the trail row (when not suppressed via
76
+ // TRAIL_HIDE_TOOLS) reads as the introspected tool, not raw JSON.
77
+ // Note: most callers see this tool suppressed entirely from the
78
+ // visible trail via the TRAIL_HIDE_TOOLS set in display.ts; the
79
+ // extractor exists for code paths that DON'T suppress (verbose
80
+ // mode, log-file capture).
81
+ lookup_tool_schema: 'toolName',
72
82
  // ── sessions ─────────────────────────────────────────────────────────
73
83
  session_search: 'query',
74
84
  session_list: '',