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
@@ -69,6 +69,11 @@ exports.renderProgressBar = renderProgressBar;
69
69
  exports.formatTokens = formatTokens;
70
70
  exports.formatDuration = formatDuration;
71
71
  exports.renderMemoryConfirmations = renderMemoryConfirmations;
72
+ // v4.1.5+ Path A: env-var-gated loop trace logger. Captures tool-call
73
+ // sequence + system prompt + memory hashes when a turn shows loop
74
+ // symptoms (10+ calls OR 5+ consecutive same-name). Default off via
75
+ // `AIDEN_DEBUG_LOOP=1` env-var. Zero overhead when disabled.
76
+ const loopTrace_1 = require("../../core/v4/loopTrace");
72
77
  const display_1 = require("./display");
73
78
  // v4.1.4 Part 1.6 — per-turn token progress bar. Fed by `onProgress`
74
79
  // events from the streaming adapter; hidden when the adapter doesn't
@@ -867,6 +872,21 @@ class ChatSession {
867
872
  }
868
873
  }
869
874
  async runAgentTurn(userInput) {
875
+ // v4.5 Phase 8b — daemon-scheduling intent check on the user's
876
+ // initial message. Classifies regex hits like "every day at",
877
+ // "watch this folder", "when an email arrives" — and queues a
878
+ // tip to render at the END of the agent's response (so it
879
+ // doesn't crowd the agent's actual reply). Engine handles
880
+ // budget + dismissal.
881
+ let _deferredTip = null;
882
+ try {
883
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
884
+ const { getSuggestionEngine } = require('../../core/v4/suggestionEngine');
885
+ const t = getSuggestionEngine().checkInitialMessage(userInput);
886
+ if (t)
887
+ _deferredTip = t;
888
+ }
889
+ catch { /* defensive — never block a turn on a suggestion */ }
870
890
  // Phase 30.2.1 — explore mode: short-circuit BEFORE building the
871
891
  // turn-status spinner / agent call. The wizard skipped, so there's
872
892
  // no real provider to talk to. Print a friendly redirect to /setup
@@ -934,6 +954,62 @@ class ChatSession {
934
954
  const indicator = this.opts.display.activityIndicator('thinking');
935
955
  let indicatorStopped = false;
936
956
  let streamingActive = false;
957
+ // v4.1.5 Issue O — track whether this turn had any tool calls so
958
+ // we can emit a single muted rule between the tool trail and the
959
+ // reply header. Set true when the first tool's `before` phase
960
+ // fires (via the existing beforeFirstToolHook plumbing). Emitted
961
+ // once per turn — `separatorEmitted` gates idempotency against
962
+ // both streaming and non-streaming paths reaching the same hook.
963
+ //
964
+ // v4.1.5 Phase 1d (Q-OBV-b) — multi-tool separator regression:
965
+ // the prior v4.1.5 Phase 1c emission point was the streaming
966
+ // `onFirstDelta` callback, but that fires PER provider call
967
+ // (the agent resets `firstDeltaFired` each callProvider
968
+ // invocation), and on multi-tool turns where the model emits
969
+ // no preamble in early iterations + no preamble in the final
970
+ // reply iteration either, the relative ordering of "first
971
+ // delta" vs "first tool" could leave the flag/idempotency
972
+ // gate in an unexpected state. Definitive fix: tie emission
973
+ // to the FIRST STREAM BYTE LANDING ON SCREEN, which only
974
+ // happens once per turn regardless of how many provider
975
+ // iterations occurred. `firstStreamByteSeen` is the new gate;
976
+ // separator fires from inside `onDelta` BEFORE `streamPartial`
977
+ // writes the agent header.
978
+ let turnHadTools = false;
979
+ let separatorEmitted = false;
980
+ let firstStreamByteSeen = false;
981
+ // v4.1.5+ Path A: per-turn loop tracer (env-var gated, default off).
982
+ // Captures tool-call sequence + assembled system prompt + memory
983
+ // hashes + recent skills when a turn trips loop thresholds. The
984
+ // `onLoopWarning` callback surfaces a one-line dim hint to the
985
+ // user when consecutive-same-tool count crosses 8 — gives them a
986
+ // chance to Ctrl+C before the agent burns more budget.
987
+ const loopTracer = new loopTrace_1.LoopTracer({
988
+ paths: this.opts.paths,
989
+ providerId: this.currentProviderId,
990
+ modelId: this.currentModelId,
991
+ onLoopWarning: (line) => {
992
+ try {
993
+ this.opts.display.dim(line);
994
+ }
995
+ catch { /* defensive */ }
996
+ },
997
+ });
998
+ if (loopTracer.isEnabled()) {
999
+ loopTracer.setHistory(baseHistory);
1000
+ }
1001
+ const emitToolReplySeparator = () => {
1002
+ if (separatorEmitted || !turnHadTools)
1003
+ return;
1004
+ separatorEmitted = true;
1005
+ // Same chrome pattern as the existing pre-turn rule (line ~1100)
1006
+ // and the post-reply rule (line ~1297): two-space indent + the
1007
+ // body-width muted rule + newline. The 2-space indent is the
1008
+ // legacy convention used by adjacent rules; the v4.1.5 frame
1009
+ // gutter (3) is consciously NOT applied here so all three rules
1010
+ // in a turn share one left edge.
1011
+ this.opts.display.write(` ${this.opts.display.rule()}\n`);
1012
+ };
937
1013
  const stopIndicatorOnce = () => {
938
1014
  if (indicatorStopped)
939
1015
  return;
@@ -946,12 +1022,55 @@ class ChatSession {
946
1022
  this.opts.callbacks.setActivityIndicatorHooks?.({});
947
1023
  }
948
1024
  catch { /* defensive */ }
1025
+ // v4.1.5 Issue K — also clear the phase-verb sink so lifecycle
1026
+ // events fired during async cleanup don't try to update a
1027
+ // stopped indicator.
1028
+ try {
1029
+ this.opts.callbacks.setPhaseVerbHook?.(undefined);
1030
+ }
1031
+ catch { /* defensive */ }
1032
+ // v4.1.5+ Path A — clear the loop-trace sink so subsequent
1033
+ // turns don't fire into a stale tracer. Note: this clears the
1034
+ // HOOK, not the tracer's accumulated state — finalize() still
1035
+ // runs at end-of-try below to write the snapshot if thresholds
1036
+ // tripped.
1037
+ try {
1038
+ this.opts.callbacks.setToolTraceHook?.({});
1039
+ }
1040
+ catch { /* defensive */ }
949
1041
  };
1042
+ // v4.1.5 Issue K — wire the per-turn phase-verb sink. Each
1043
+ // AidenAgent lifecycle event (memory refresh start, prompt built,
1044
+ // provider request start) flows through CliCallbacks and lands
1045
+ // here as a verb string ("refreshing memory" / "preparing prompt"
1046
+ // / "calling provider"). The closure captures the per-turn
1047
+ // indicator handle so verb mutations stay scoped to this turn.
1048
+ this.opts.callbacks.setPhaseVerbHook?.((verb) => {
1049
+ if (indicatorStopped)
1050
+ return;
1051
+ indicator.setVerb(verb);
1052
+ });
1053
+ // v4.1.5+ Path A — wire the loop-trace sink. Fires for EVERY tool
1054
+ // call (including hidden ones) so the trace captures the full
1055
+ // agent loop. Defensive — when AIDEN_DEBUG_LOOP is unset, the
1056
+ // tracer's `startTool`/`endTool` short-circuit immediately.
1057
+ this.opts.callbacks.setToolTraceHook?.({
1058
+ before: (id, name) => loopTracer.startTool(id, name),
1059
+ after: (id, name, args) => loopTracer.endTool(id, name, args),
1060
+ });
950
1061
  // Phase 23.5 carried forward: stop the indicator the moment the
951
1062
  // first tool row prints — the row itself is the activity surface
952
1063
  // during a tool. Part 1.6 then resumes via `afterEachTool` so the
953
1064
  // post-tool gap has its own indicator paint.
954
- this.opts.callbacks.setBeforeFirstToolHook?.(stopIndicatorOnce);
1065
+ //
1066
+ // v4.1.5 Issue O — also flip `turnHadTools = true` so the
1067
+ // separator emits before the reply header. Single hook captures
1068
+ // "any tool ran this turn" cleanly (it only fires for the FIRST
1069
+ // tool of the turn — subsequent tools don't re-trigger).
1070
+ this.opts.callbacks.setBeforeFirstToolHook?.(() => {
1071
+ turnHadTools = true;
1072
+ stopIndicatorOnce();
1073
+ });
955
1074
  // Part 1.6: pause/resume hooks around every tool row. The
956
1075
  // `beforeTool` hook fires before EACH tool row writes (not just
957
1076
  // the first), so multi-tool sequences also keep the indicator
@@ -991,10 +1110,32 @@ class ChatSession {
991
1110
  ? () => {
992
1111
  stopIndicatorOnce();
993
1112
  streamingActive = true;
1113
+ // v4.1.5 Phase 1d (Q-OBV-b) — separator emission MOVED
1114
+ // out of onFirstDelta because that callback fires per
1115
+ // provider-call iteration (firstDeltaFired resets each
1116
+ // callProvider invocation). The separator-emit now
1117
+ // lives in onDelta below, gated by `firstStreamByteSeen`
1118
+ // which only flips once per turn.
994
1119
  }
995
1120
  : undefined,
996
1121
  onDelta: streamingEnabled
997
1122
  ? (text) => {
1123
+ // v4.1.5 Phase 1d (Q-OBV-b) — definitive separator
1124
+ // emission point. This is the FIRST text byte landing
1125
+ // on screen this turn. Fires the muted rule BEFORE
1126
+ // streamPartial writes the `┃ Aiden` header so the
1127
+ // visual order is:
1128
+ // ┊ tool rows...
1129
+ // ──────────── ← separator
1130
+ // ┃ Aiden
1131
+ // {text}
1132
+ // Idempotent via `firstStreamByteSeen` + the
1133
+ // `separatorEmitted` flag inside emitToolReplySeparator.
1134
+ // No-op when no tool fired (turnHadTools=false).
1135
+ if (!firstStreamByteSeen) {
1136
+ firstStreamByteSeen = true;
1137
+ emitToolReplySeparator();
1138
+ }
998
1139
  // v4.1.4 Part 1.6: bar lives ABOVE streamed text. Hide
999
1140
  // it before each delta writes so the stream output
1000
1141
  // doesn't land on the bar's line. The bar repaints on
@@ -1052,11 +1193,56 @@ class ChatSession {
1052
1193
  if (result.toolCallTrace && result.toolCallTrace.length > 0) {
1053
1194
  this.sessionToolTrace.push(...result.toolCallTrace);
1054
1195
  }
1055
- // When streaming was active and emitted the final content already,
1056
- // skip the markdown re-render we'd otherwise duplicate text.
1057
- if (result.finalContent && !streamingActive) {
1196
+ // v4.1.6 spike (TCE) tool-loop terminal surface. When the
1197
+ // agent ended the turn via the recovery controller's surface
1198
+ // stage, render a structured-failure card instead of the
1199
+ // (empty) reply. Same chrome as auth / platform capability
1200
+ // cards — fits the established Aiden UX language for
1201
+ // "the action you wanted didn't happen, here's why and what
1202
+ // you can do." Surface BEFORE the tool→reply separator path
1203
+ // below because there's no agent reply to introduce.
1204
+ if (result.finishReason === 'tool_loop' && result.toolLoopCard) {
1205
+ // Emit the muted rule so the card visually separates from
1206
+ // the tool trail above it.
1207
+ emitToolReplySeparator();
1208
+ this.opts.display.capabilityCard(result.toolLoopCard);
1209
+ }
1210
+ else if (result.finalContent && !streamingActive) {
1211
+ // When streaming was active and emitted the final content
1212
+ // already, skip the markdown re-render — we'd otherwise
1213
+ // duplicate text.
1214
+ //
1215
+ // v4.1.5 Issue O — non-streaming reply path. Emit the muted
1216
+ // rule between the tool trail and the agent header before
1217
+ // the one-shot reply lands. Idempotent + tool-gated by
1218
+ // `emitToolReplySeparator`.
1219
+ emitToolReplySeparator();
1058
1220
  this.opts.display.write(this.opts.display.agentTurn(result.finalContent));
1059
1221
  }
1222
+ // v4.1.6 Polish 2 — post-render skill-proposal handler.
1223
+ // The agent loop now SKIPS the inquirer prompt when a
1224
+ // prompt callback is wired, surfacing the SkillProposal
1225
+ // here instead. We fire the prompt AFTER the agent reply
1226
+ // has rendered so the user sees the answer before being
1227
+ // asked "save this as a reusable skill?" — fixing the
1228
+ // v4.1.5 visual-smoke regression where the prompt fired
1229
+ // mid-turn and clobbered the reply.
1230
+ //
1231
+ // Wrapped in try/catch so a buggy proposal flow never
1232
+ // breaks the chat loop. A successful save surfaces a
1233
+ // dim confirmation line that fits the established
1234
+ // memory-confirmation chrome.
1235
+ if (result.skillProposal && this.opts.callbacks?.handleSkillProposal) {
1236
+ try {
1237
+ const saveResult = await this.opts.callbacks.handleSkillProposal(result.skillProposal);
1238
+ if (saveResult?.created && saveResult.skillName) {
1239
+ this.opts.display.dim(` ✓ Saved as skill: ${saveResult.skillName}`);
1240
+ }
1241
+ }
1242
+ catch {
1243
+ /* defensive — never let proposal flow break the chat loop */
1244
+ }
1245
+ }
1060
1246
  if (this.sessionId) {
1061
1247
  // Only persist the new tail of messages — what got added this turn.
1062
1248
  const newSlice = this.history.slice(turnStart);
@@ -1064,10 +1250,36 @@ class ChatSession {
1064
1250
  }
1065
1251
  this.setStatusState({ kind: 'ready' });
1066
1252
  this.lastTurnElapsedMs = Date.now() - turnStartedAt;
1253
+ // v4.5 Phase 8b — surface a deferred daemon-scheduling tip
1254
+ // queued at turn start. Renders AFTER the agent's response per
1255
+ // Q-P8b-3(b) — the user reads the answer first, then sees the
1256
+ // ambient capability hint.
1257
+ if (_deferredTip) {
1258
+ try {
1259
+ this.opts.display.dim(_deferredTip.message);
1260
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1261
+ const { getSuggestionEngine } = require('../../core/v4/suggestionEngine');
1262
+ getSuggestionEngine().recordFired(_deferredTip.slot);
1263
+ }
1264
+ catch { /* defensive */ }
1265
+ _deferredTip = null;
1266
+ }
1067
1267
  // Tier-3.1a: dim full-width rule between the agent reply and the
1068
1268
  // post-turn status footer.
1069
1269
  this.opts.display.write(` ${this.opts.display.rule()}\n`);
1070
1270
  this.renderStatusLine();
1271
+ // v4.1.5+ Path A — finalize the loop trace. No-op if the env
1272
+ // var is unset OR if the turn didn't trip any threshold. When
1273
+ // it DOES emit, the snapshot path goes to a dim status line so
1274
+ // the user (and any teammate they're sharing the log with)
1275
+ // knows where to grab the diagnostic file.
1276
+ try {
1277
+ const snapPath = await loopTracer.finalize();
1278
+ if (snapPath) {
1279
+ this.opts.display.dim(`[loop-trace] wrote ${snapPath}`);
1280
+ }
1281
+ }
1282
+ catch { /* defensive */ }
1071
1283
  }
1072
1284
  catch (err) {
1073
1285
  stopIndicatorOnce();
@@ -1116,6 +1328,16 @@ class ChatSession {
1116
1328
  }
1117
1329
  this.setStatusState({ kind: 'ready' });
1118
1330
  this.lastTurnElapsedMs = Date.now() - turnStartedAt;
1331
+ // v4.1.5+ Path A — finalize the loop trace on the error path
1332
+ // too. Loop patterns that ended in an error are exactly the
1333
+ // ones most worth capturing for diagnosis.
1334
+ try {
1335
+ const snapPath = await loopTracer.finalize();
1336
+ if (snapPath) {
1337
+ this.opts.display.dim(`[loop-trace] wrote ${snapPath}`);
1338
+ }
1339
+ }
1340
+ catch { /* defensive */ }
1119
1341
  }
1120
1342
  }
1121
1343
  // ── Startup card (Phase 26.2.4: neofetch-style sectioned) ──────────
@@ -1196,6 +1418,9 @@ class ChatSession {
1196
1418
  providerOk: !this.opts.unconfigured,
1197
1419
  version: version_1.VERSION,
1198
1420
  }) + '\n');
1421
+ // v4.5 TUI polish — blank line so the status pills row doesn't
1422
+ // crowd the muted source annotation right beneath it.
1423
+ display.write('\n');
1199
1424
  // v4.1.3-prebump: dim source annotation under the pills row so the
1200
1425
  // user can see WHY this provider/model was chosen — closes the
1201
1426
  // information gap that made Case 3 (persisted-config) look like a
@@ -1255,10 +1480,73 @@ class ChatSession {
1255
1480
  }
1256
1481
  // Scroll footer (parchment at ≥80 cols, single-line credits below).
1257
1482
  display.write(display.scrollFooter() + '\n');
1483
+ // v4.5 update system — boxed three-option prompt rendered AFTER
1484
+ // the boot card / status pills (Q-U5b less-intrusive position),
1485
+ // BEFORE the bottomPromptHint. Fires only when:
1486
+ // - update check came back with `updateAvailable && !skipped`
1487
+ // - stdin is a TTY (non-interactive boots short-circuit to 'later')
1488
+ // 5-second timeout defaults to 'later' so a user away from
1489
+ // keyboard isn't held up. Skip-on-'n' writes the version to the
1490
+ // .update_check.json cache so subsequent boots stay quiet until
1491
+ // a newer release ships.
1492
+ try {
1493
+ await this.maybeShowBootUpdatePrompt();
1494
+ }
1495
+ catch { /* never let the update prompt crash boot */ }
1258
1496
  // Bottom prompt hint — final line of the boot card.
1259
1497
  display.write('\n');
1260
1498
  display.write(display.bottomPromptHint() + '\n');
1261
1499
  }
1500
+ /**
1501
+ * v4.5 update system — orchestrates the boot prompt. Lazy-imports
1502
+ * the update modules so non-boot code paths (e.g. test harness
1503
+ * sessions constructed without paths wired) don't pay the cost.
1504
+ */
1505
+ async maybeShowBootUpdatePrompt() {
1506
+ if (!this.opts.paths)
1507
+ return;
1508
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1509
+ const cu = require('../../core/v4/update/checkUpdate');
1510
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1511
+ const md = require('../../core/v4/update/installMethodDetect');
1512
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1513
+ const ss = require('../../core/v4/update/skipState');
1514
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1515
+ const bp = require('./updateBootPrompt');
1516
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1517
+ const ei = require('../../core/v4/update/executeInstall');
1518
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1519
+ const ver = require('../../core/version');
1520
+ const status = await cu.checkForUpdate({ paths: this.opts.paths, installedVersion: ver.VERSION });
1521
+ if (!status.updateAvailable || !status.latest || status.skipped)
1522
+ return;
1523
+ const method = md.detectInstallMethod();
1524
+ const choice = await bp.showBootUpdatePrompt({
1525
+ status, method,
1526
+ display: { write: (s) => this.opts.display.write(s), dim: (s) => this.opts.display.dim(s) },
1527
+ });
1528
+ if (choice === 'install') {
1529
+ if (method.inProcessInstallSupported) {
1530
+ this.opts.display.write(`Installing aiden-runtime ${status.latest}…\n`);
1531
+ const result = await ei.executeInstall({ packageSpec: `aiden-runtime@${status.latest}` });
1532
+ if (result.success) {
1533
+ this.opts.display.write(` ✓ aiden-runtime ${result.installedVersion ?? status.latest} installed.\n`);
1534
+ this.opts.display.dim('Restart Aiden to apply: type /quit then re-run `aiden`.');
1535
+ }
1536
+ else {
1537
+ this.opts.display.warn(result.error ?? 'Install failed (no error message).');
1538
+ }
1539
+ }
1540
+ else {
1541
+ this.opts.display.write(`To update, run:\n ${method.updateCommand(status.latest)}\n`);
1542
+ }
1543
+ }
1544
+ else if (choice === 'skip') {
1545
+ await cu.updateCacheFile(this.opts.paths, (current) => ss.applySkip(current, status.latest));
1546
+ this.opts.display.dim(` skipped ${status.latest}. Boot prompt resumes when a newer version ships.`);
1547
+ }
1548
+ // 'later' = no-op; prompt fires again next session.
1549
+ }
1262
1550
  /** Phase 22 Task 4: state transitions for the right-most segment. */
1263
1551
  setStatusState(state) {
1264
1552
  this.statusState = state;
@@ -0,0 +1,92 @@
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/_runtimeToggleHelpers.ts — v4.5 Phase 8a.
10
+ *
11
+ * Shared `flip` + `printStatus` helpers used by /sandbox, /tce, and
12
+ * /browser-depth. Each subsystem command is a tiny wrapper over
13
+ * these — same on/off/status surface, same status format.
14
+ *
15
+ * The persist path goes through `ConfigManager.set()` + `save()` so
16
+ * the runtime_toggles section is written verbatim to config.yaml.
17
+ * The runtimeToggles singleton's `set()` then fires onChange
18
+ * callbacks for cached consumers (sandboxConfig's singleton).
19
+ *
20
+ * Test seam: `flip` and `printStatus` accept a `ctx` shaped like
21
+ * SlashCommandContext (just `display`, optionally `config`). Tests
22
+ * pass a minimal stub.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.flip = flip;
26
+ exports.printStatus = printStatus;
27
+ exports.parseSubcommand = parseSubcommand;
28
+ const runtimeToggles_1 = require("../../../core/v4/runtimeToggles");
29
+ const LABEL = {
30
+ sandbox: 'Sandbox',
31
+ tce: 'TCE',
32
+ browser_depth: 'Browser depth',
33
+ suggestions: 'Suggestions',
34
+ };
35
+ const CONFIG_DOTTED = {
36
+ sandbox: 'runtime_toggles.sandbox',
37
+ tce: 'runtime_toggles.tce',
38
+ browser_depth: 'runtime_toggles.browser_depth',
39
+ suggestions: 'runtime_toggles.suggestions',
40
+ };
41
+ /**
42
+ * Apply a toggle change. When `ctx.config` is wired, persists to
43
+ * config.yaml. Otherwise the flip is in-process only (current
44
+ * session sees the new value; next process boot doesn't).
45
+ *
46
+ * Returns nothing — prints status via ctx.display.
47
+ */
48
+ async function flip(key, value, ctx) {
49
+ const rt = (0, runtimeToggles_1.getRuntimeToggles)();
50
+ // Persist when a ConfigManager is wired.
51
+ if (ctx.config) {
52
+ try {
53
+ ctx.config.set(CONFIG_DOTTED[key], value);
54
+ await ctx.config.save();
55
+ }
56
+ catch (e) {
57
+ ctx.display.warn(`[${key}] config.yaml save failed (${e instanceof Error ? e.message : String(e)}); ` +
58
+ `flip applies to this session only.`);
59
+ }
60
+ }
61
+ await rt.set(key, value, { persist: false });
62
+ printStatus(key, ctx);
63
+ }
64
+ /**
65
+ * Print the current state of one toggle. One-line output per
66
+ * Q-P8a-2(a):
67
+ *
68
+ * `Sandbox: ON (source: config)`
69
+ *
70
+ * `source` reveals which precedence layer provided the value —
71
+ * critical for debugging "why is it ON when my .env says 0".
72
+ */
73
+ function printStatus(key, ctx) {
74
+ const snap = (0, runtimeToggles_1.getRuntimeToggles)().snapshot()[key];
75
+ const label = LABEL[key];
76
+ const state = snap.value ? 'ON' : 'OFF';
77
+ ctx.display.write(`${label}: ${state} (source: ${snap.source})\n`);
78
+ }
79
+ /**
80
+ * Parse the on/off/status subcommand. Returns null when the input
81
+ * is unrecognised (caller prints usage).
82
+ */
83
+ function parseSubcommand(raw) {
84
+ const s = (raw ?? 'status').toLowerCase();
85
+ if (s === 'on' || s === 'enable' || s === '1' || s === 'true')
86
+ return 'on';
87
+ if (s === 'off' || s === 'disable' || s === '0' || s === 'false')
88
+ return 'off';
89
+ if (s === 'status' || s === '' || s === undefined)
90
+ return 'status';
91
+ return null;
92
+ }
@@ -0,0 +1,45 @@
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/browserDepth.ts — v4.5 Phase 8a.
10
+ *
11
+ * `/browser-depth on|off|status` — flip the v4.3 state-aware
12
+ * browser observer (URL/DOM/iframe-tree capture, stale-ref retry,
13
+ * manual-blocker detection) without restart. Persists to
14
+ * config.yaml. Env var AIDEN_BROWSER_DEPTH always wins.
15
+ *
16
+ * Q-P8a-5(a): named `/browser-depth` to mirror the env var
17
+ * exactly. Reserves `/browser` for future browser-navigation
18
+ * commands so the namespace stays unambiguous.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.browserDepth = void 0;
22
+ const _runtimeToggleHelpers_1 = require("./_runtimeToggleHelpers");
23
+ exports.browserDepth = {
24
+ name: 'browser-depth',
25
+ description: 'Toggle the v4.3 state-aware browser observer.',
26
+ category: 'system',
27
+ icon: '🌐',
28
+ handler: async (ctx) => {
29
+ const sub = (0, _runtimeToggleHelpers_1.parseSubcommand)(ctx.args[0]);
30
+ if (sub === 'on') {
31
+ await (0, _runtimeToggleHelpers_1.flip)('browser_depth', true, ctx);
32
+ return {};
33
+ }
34
+ if (sub === 'off') {
35
+ await (0, _runtimeToggleHelpers_1.flip)('browser_depth', false, ctx);
36
+ return {};
37
+ }
38
+ if (sub === 'status') {
39
+ (0, _runtimeToggleHelpers_1.printStatus)('browser_depth', ctx);
40
+ return {};
41
+ }
42
+ ctx.display.printError('Usage: /browser-depth on|off|status');
43
+ return {};
44
+ },
45
+ };