aiden-runtime 4.1.1 → 4.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +78 -26
  2. package/dist/cli/v4/aidenCLI.js +169 -9
  3. package/dist/cli/v4/callbacks.js +20 -2
  4. package/dist/cli/v4/chatSession.js +644 -16
  5. package/dist/cli/v4/commands/auth.js +6 -3
  6. package/dist/cli/v4/commands/doctor.js +23 -27
  7. package/dist/cli/v4/commands/help.js +4 -0
  8. package/dist/cli/v4/commands/index.js +10 -1
  9. package/dist/cli/v4/commands/model.js +30 -1
  10. package/dist/cli/v4/commands/reloadSoul.js +37 -0
  11. package/dist/cli/v4/commands/update.js +102 -0
  12. package/dist/cli/v4/defaultSoul.js +68 -2
  13. package/dist/cli/v4/display/capabilityCard.js +135 -0
  14. package/dist/cli/v4/display/sessionEndCard.js +127 -0
  15. package/dist/cli/v4/display/toolTrail.js +172 -0
  16. package/dist/cli/v4/display.js +492 -142
  17. package/dist/cli/v4/doctor.js +472 -58
  18. package/dist/cli/v4/doctorLiveness.js +65 -10
  19. package/dist/cli/v4/promotionPrompt.js +332 -0
  20. package/dist/cli/v4/providerBootSelector.js +144 -0
  21. package/dist/cli/v4/replyRenderer.js +311 -20
  22. package/dist/cli/v4/sessionSummaryGate.js +66 -0
  23. package/dist/cli/v4/skinEngine.js +14 -3
  24. package/dist/cli/v4/toolPreview.js +153 -0
  25. package/dist/core/tools/nowPlaying.js +7 -15
  26. package/dist/core/v4/aidenAgent.js +91 -29
  27. package/dist/core/v4/capabilities.js +89 -0
  28. package/dist/core/v4/contextCompressor.js +25 -8
  29. package/dist/core/v4/distillationIndex.js +167 -0
  30. package/dist/core/v4/distillationStore.js +98 -0
  31. package/dist/core/v4/logger/logger.js +40 -9
  32. package/dist/core/v4/promotionCandidates.js +234 -0
  33. package/dist/core/v4/promptBuilder.js +145 -1
  34. package/dist/core/v4/sessionDistiller.js +452 -0
  35. package/dist/core/v4/skillMining/skillMiner.js +43 -6
  36. package/dist/core/v4/skillOutcomeTracker.js +323 -0
  37. package/dist/core/v4/subsystemHealth.js +143 -0
  38. package/dist/core/v4/toolRegistry.js +16 -1
  39. package/dist/core/v4/update/executeInstall.js +233 -0
  40. package/dist/core/version.js +1 -1
  41. package/dist/moat/memoryGuard.js +111 -0
  42. package/dist/moat/plannerGuard.js +19 -0
  43. package/dist/moat/skillTeacher.js +14 -5
  44. package/dist/providers/v4/chatCompletionsAdapter.js +9 -0
  45. package/dist/providers/v4/errors.js +112 -4
  46. package/dist/providers/v4/modelDefaults.js +65 -0
  47. package/dist/providers/v4/registry.js +9 -2
  48. package/dist/providers/v4/runtimeResolver.js +6 -0
  49. package/dist/tools/v4/index.js +80 -1
  50. package/dist/tools/v4/memory/memoryRemove.js +57 -2
  51. package/dist/tools/v4/memory/sessionSummary.js +151 -0
  52. package/dist/tools/v4/sessions/recallSession.js +177 -0
  53. package/dist/tools/v4/sessions/sessionSearch.js +5 -1
  54. package/dist/tools/v4/system/_psHelpers.js +123 -0
  55. package/dist/tools/v4/system/aidenSelfUpdate.js +162 -0
  56. package/dist/tools/v4/system/appClose.js +79 -0
  57. package/dist/tools/v4/system/appInput.js +154 -0
  58. package/dist/tools/v4/system/appLaunch.js +218 -0
  59. package/dist/tools/v4/system/clipboardRead.js +54 -0
  60. package/dist/tools/v4/system/clipboardWrite.js +84 -0
  61. package/dist/tools/v4/system/mediaKey.js +109 -0
  62. package/dist/tools/v4/system/mediaSessions.js +163 -0
  63. package/dist/tools/v4/system/mediaTransport.js +211 -0
  64. package/dist/tools/v4/system/osProcessList.js +99 -0
  65. package/dist/tools/v4/system/screenshot.js +106 -0
  66. package/dist/tools/v4/system/volumeSet.js +157 -0
  67. package/package.json +4 -1
  68. package/skills/system_control.md +185 -69
@@ -232,12 +232,19 @@ exports.PROVIDER_REGISTRY = {
232
232
  apiMode: 'chat_completions',
233
233
  baseUrl: 'https://api.deepseek.com/v1',
234
234
  apiKeyEnvVar: 'DEEPSEEK_API_KEY',
235
- description: 'DeepSeek direct API — V3 chat + R1 reasoning.',
235
+ description: 'DeepSeek direct API — V4 Pro reasoning flagship + legacy aliases.',
236
236
  tier: 'paid',
237
237
  hasFreeTier: false,
238
238
  docsUrl: 'https://api-docs.deepseek.com/',
239
239
  supportsToolCalling: true,
240
- modelIds: ['deepseek-chat', 'deepseek-reasoner'],
240
+ // Phase v4.1.2-deepseek: `deepseek-v4-pro` prepended as the new
241
+ // flagship — becomes the auto-pick default for new users via
242
+ // pickProbeModel(). Legacy `deepseek-chat` and `deepseek-reasoner`
243
+ // retained for back-compat (still functional aliases of the V4
244
+ // flash family per DeepSeek docs; deprecated-but-live). Removal
245
+ // is its own deprecation slice. Per-call thinking + reasoning
246
+ // _effort defaults for v4-pro live in providers/v4/modelDefaults.ts.
247
+ modelIds: ['deepseek-v4-pro', 'deepseek-chat', 'deepseek-reasoner'],
241
248
  },
242
249
  mistral: {
243
250
  id: 'mistral',
@@ -39,6 +39,7 @@ const chatCompletionsAdapter_1 = require("./chatCompletionsAdapter");
39
39
  const anthropicAdapter_1 = require("./anthropicAdapter");
40
40
  const codexResponsesAdapter_1 = require("./codexResponsesAdapter");
41
41
  const ollamaPromptToolsAdapter_1 = require("./ollamaPromptToolsAdapter");
42
+ const modelDefaults_1 = require("./modelDefaults");
42
43
  const tokenStore_1 = require("../../core/v4/auth/tokenStore");
43
44
  class RuntimeResolver {
44
45
  constructor(credentialResolver) {
@@ -61,6 +62,11 @@ class RuntimeResolver {
61
62
  model: model.id,
62
63
  providerName: entry.id,
63
64
  extraHeaders: entry.extraHeaders,
65
+ // Phase v4.1.2-deepseek: per-model body defaults (e.g.
66
+ // DeepSeek V4-Pro's mandatory thinking + reasoning_effort).
67
+ // Undefined for models without registered defaults — adapter
68
+ // skips the merge in that case.
69
+ defaultExtraBody: (0, modelDefaults_1.getModelDefaults)(entry.id, model.id)?.extraBody,
64
70
  });
65
71
  case 'anthropic_messages':
66
72
  if (!credentials.apiKey) {
@@ -21,7 +21,8 @@
21
21
  * Status: PHASE 8.
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.memoryRemoveTool = exports.memoryReplaceTool = exports.memoryAddTool = exports.processWaitTool = exports.processKillTool = exports.processLogReadTool = exports.processListTool = exports.processSpawnTool = exports.executeCodeTool = exports.shellExecTool = exports.naturalEventsTool = exports.nowPlayingTool = exports.systemInfoTool = exports.makeLookupToolSchema = exports.skillManageTool = exports.skillViewTool = exports.skillsListTool = exports.sessionListTool = exports.sessionSearchTool = exports.browserCloseTool = exports.browserScrollTool = exports.browserFillTool = exports.browserTypeTool = exports.browserClickTool = exports.browserNavigateTool = exports.browserGetUrlTool = exports.browserExtractTool = exports.browserScreenshotTool = exports.fileCopyTool = exports.fileMoveTool = exports.fileDeleteTool = exports.filePatchTool = exports.fileWriteTool = exports.fileListTool = exports.fileReadTool = exports.deepResearchTool = exports.webPageTool = exports.webFetchTool = exports.webSearchTool = exports.makeSubagentFanoutTool = void 0;
24
+ exports.memoryReplaceTool = exports.memoryAddTool = exports.processWaitTool = exports.processKillTool = exports.processLogReadTool = exports.processListTool = exports.processSpawnTool = exports.executeCodeTool = exports.shellExecTool = exports.appInputTool = exports.mediaTransportTool = exports.mediaSessionsTool = exports.clipboardWriteTool = exports.clipboardReadTool = exports.appCloseTool = exports.appLaunchTool = exports.volumeSetTool = exports.mediaKeyTool = exports.osProcessListTool = exports.screenshotTool = exports.naturalEventsTool = exports.nowPlayingTool = exports.systemInfoTool = exports.makeLookupToolSchema = exports.skillManageTool = exports.skillViewTool = exports.skillsListTool = exports.sessionListTool = exports.sessionSearchTool = exports.browserCloseTool = exports.browserScrollTool = exports.browserFillTool = exports.browserTypeTool = exports.browserClickTool = exports.browserNavigateTool = exports.browserGetUrlTool = exports.browserExtractTool = exports.browserScreenshotTool = exports.fileCopyTool = exports.fileMoveTool = exports.fileDeleteTool = exports.filePatchTool = exports.fileWriteTool = exports.fileListTool = exports.fileReadTool = exports.deepResearchTool = exports.webPageTool = exports.webFetchTool = exports.webSearchTool = exports.makeSubagentFanoutTool = void 0;
25
+ exports.sessionSummaryTool = exports.memoryRemoveTool = void 0;
25
26
  exports.registerReadOnlyTools = registerReadOnlyTools;
26
27
  exports.registerWriteTools = registerWriteTools;
27
28
  exports.registerAllTools = registerAllTools;
@@ -49,6 +50,7 @@ const browserScroll_1 = require("./browser/browserScroll");
49
50
  const browserClose_1 = require("./browser/browserClose");
50
51
  const sessionSearch_1 = require("./sessions/sessionSearch");
51
52
  const sessionList_1 = require("./sessions/sessionList");
53
+ const recallSession_1 = require("./sessions/recallSession");
52
54
  const skillsList_1 = require("./skills/skillsList");
53
55
  const skillView_1 = require("./skills/skillView");
54
56
  const skillManage_1 = require("./skills/skillManage");
@@ -56,6 +58,25 @@ const lookupToolSchema_1 = require("./skills/lookupToolSchema");
56
58
  const systemInfo_1 = require("./system/systemInfo");
57
59
  const nowPlaying_1 = require("./system/nowPlaying");
58
60
  const naturalEvents_1 = require("./system/naturalEvents");
61
+ // Phase v4.1.2-followup-3 computer-control bundle.
62
+ const screenshot_1 = require("./system/screenshot");
63
+ const osProcessList_1 = require("./system/osProcessList");
64
+ const mediaKey_1 = require("./system/mediaKey");
65
+ const volumeSet_1 = require("./system/volumeSet");
66
+ const appLaunch_1 = require("./system/appLaunch");
67
+ const appClose_1 = require("./system/appClose");
68
+ const clipboardRead_1 = require("./system/clipboardRead");
69
+ const clipboardWrite_1 = require("./system/clipboardWrite");
70
+ // v4.1.4-media — three-layer media-control bundle.
71
+ // Layer 2 (OS media session): mediaSessions (read) + mediaTransport (write).
72
+ // Layer 3 fallback (mediaKey, blind keystroke) remains unchanged.
73
+ // Layer 1 (semantic API) is per-app and out of this slice.
74
+ const mediaSessions_1 = require("./system/mediaSessions");
75
+ const mediaTransport_1 = require("./system/mediaTransport");
76
+ const appInput_1 = require("./system/appInput");
77
+ // Phase v4.1.2-update — natural-language self-update entry point.
78
+ // Routes through the same shared executeInstall executor as `/update install`.
79
+ const aidenSelfUpdate_1 = require("./system/aidenSelfUpdate");
59
80
  const shellExec_1 = require("./terminal/shellExec");
60
81
  const executeCode_1 = require("./executeCode");
61
82
  const processSpawn_1 = require("./process/processSpawn");
@@ -66,6 +87,7 @@ const processWait_1 = require("./process/processWait");
66
87
  const memoryAdd_1 = require("./memory/memoryAdd");
67
88
  const memoryReplace_1 = require("./memory/memoryReplace");
68
89
  const memoryRemove_1 = require("./memory/memoryRemove");
90
+ const sessionSummary_1 = require("./memory/sessionSummary");
69
91
  const subagentFanout_1 = require("./subagent/subagentFanout");
70
92
  /**
71
93
  * Register every read-only tool into `registry`. The
@@ -94,11 +116,24 @@ function registerReadOnlyTools(registry) {
94
116
  registry.register(browserGetUrl_1.browserGetUrlTool);
95
117
  registry.register(sessionSearch_1.sessionSearchTool);
96
118
  registry.register(sessionList_1.sessionListTool);
119
+ // Phase v4.1.2-memory-C: recall_session reads SessionDistillation
120
+ // files written by Phase A+B. Sits alongside session_search — the
121
+ // two have distinct purposes (FTS5-over-messages vs ranked
122
+ // distillation summaries); descriptions force the right model
123
+ // choice.
124
+ registry.register(recallSession_1.recallSessionTool);
97
125
  registry.register(skillsList_1.skillsListTool);
98
126
  registry.register(skillView_1.skillViewTool);
99
127
  registry.register(systemInfo_1.systemInfoTool);
100
128
  registry.register(nowPlaying_1.nowPlayingTool);
101
129
  registry.register(naturalEvents_1.naturalEventsTool);
130
+ // Phase v4.1.2-followup-3 — computer-control read-only tools.
131
+ registry.register(screenshot_1.screenshotTool);
132
+ registry.register(osProcessList_1.osProcessListTool);
133
+ registry.register(clipboardRead_1.clipboardReadTool);
134
+ // v4.1.4-media — GSMTC session enumeration (read). Pair with
135
+ // mediaTransport (write) in the write-tools registration below.
136
+ registry.register(mediaSessions_1.mediaSessionsTool);
102
137
  registry.register((0, lookupToolSchema_1.makeLookupToolSchema)(registry));
103
138
  // Phase v4.1-subagent — register a stub for subagent_fanout so its
104
139
  // schema is visible to the agent loop, the MCP server, and the
@@ -158,9 +193,27 @@ function registerWriteTools(registry) {
158
193
  registry.register(memoryAdd_1.memoryAddTool);
159
194
  registry.register(memoryReplace_1.memoryReplaceTool);
160
195
  registry.register(memoryRemove_1.memoryRemoveTool);
196
+ // Phase v4.1.2 alive-core: cross-session continuity via /quit auto-summary.
197
+ registry.register(sessionSummary_1.sessionSummaryTool);
161
198
  // Phase 10: skill_manage — mutating, also goes through the approval
162
199
  // engine. skills_list / skill_view stay in registerReadOnlyTools.
163
200
  registry.register(skillManage_1.skillManageTool);
201
+ // Phase v4.1.2-update: natural-language entry to the same install
202
+ // executor that /update install uses. Two-step confirmation gate
203
+ // (confirm:false → status; confirm:true → install).
204
+ registry.register(aidenSelfUpdate_1.aidenSelfUpdateTool);
205
+ // Phase v4.1.2-followup-3 — computer-control mutating tools. All
206
+ // route through the approval engine like every other write.
207
+ registry.register(mediaKey_1.mediaKeyTool);
208
+ registry.register(volumeSet_1.volumeSetTool);
209
+ registry.register(appLaunch_1.appLaunchTool);
210
+ registry.register(appClose_1.appCloseTool);
211
+ registry.register(clipboardWrite_1.clipboardWriteTool);
212
+ // v4.1.4-media — verified GSMTC transport (replaces mediaKey for
213
+ // the "name an app, play/pause it" case) + focused-window SendKeys
214
+ // (escape hatch when GSMTC doesn't enumerate the surface).
215
+ registry.register(mediaTransport_1.mediaTransportTool);
216
+ registry.register(appInput_1.appInputTool);
164
217
  }
165
218
  /** Register every v4 tool. Most callers want this. */
166
219
  function registerAllTools(registry) {
@@ -227,6 +280,30 @@ var nowPlaying_2 = require("./system/nowPlaying");
227
280
  Object.defineProperty(exports, "nowPlayingTool", { enumerable: true, get: function () { return nowPlaying_2.nowPlayingTool; } });
228
281
  var naturalEvents_2 = require("./system/naturalEvents");
229
282
  Object.defineProperty(exports, "naturalEventsTool", { enumerable: true, get: function () { return naturalEvents_2.naturalEventsTool; } });
283
+ // Phase v4.1.2-followup-3 computer-control bundle exports.
284
+ var screenshot_2 = require("./system/screenshot");
285
+ Object.defineProperty(exports, "screenshotTool", { enumerable: true, get: function () { return screenshot_2.screenshotTool; } });
286
+ var osProcessList_2 = require("./system/osProcessList");
287
+ Object.defineProperty(exports, "osProcessListTool", { enumerable: true, get: function () { return osProcessList_2.osProcessListTool; } });
288
+ var mediaKey_2 = require("./system/mediaKey");
289
+ Object.defineProperty(exports, "mediaKeyTool", { enumerable: true, get: function () { return mediaKey_2.mediaKeyTool; } });
290
+ var volumeSet_2 = require("./system/volumeSet");
291
+ Object.defineProperty(exports, "volumeSetTool", { enumerable: true, get: function () { return volumeSet_2.volumeSetTool; } });
292
+ var appLaunch_2 = require("./system/appLaunch");
293
+ Object.defineProperty(exports, "appLaunchTool", { enumerable: true, get: function () { return appLaunch_2.appLaunchTool; } });
294
+ var appClose_2 = require("./system/appClose");
295
+ Object.defineProperty(exports, "appCloseTool", { enumerable: true, get: function () { return appClose_2.appCloseTool; } });
296
+ var clipboardRead_2 = require("./system/clipboardRead");
297
+ Object.defineProperty(exports, "clipboardReadTool", { enumerable: true, get: function () { return clipboardRead_2.clipboardReadTool; } });
298
+ var clipboardWrite_2 = require("./system/clipboardWrite");
299
+ Object.defineProperty(exports, "clipboardWriteTool", { enumerable: true, get: function () { return clipboardWrite_2.clipboardWriteTool; } });
300
+ // v4.1.4-media exports — three-layer media-control bundle.
301
+ var mediaSessions_2 = require("./system/mediaSessions");
302
+ Object.defineProperty(exports, "mediaSessionsTool", { enumerable: true, get: function () { return mediaSessions_2.mediaSessionsTool; } });
303
+ var mediaTransport_2 = require("./system/mediaTransport");
304
+ Object.defineProperty(exports, "mediaTransportTool", { enumerable: true, get: function () { return mediaTransport_2.mediaTransportTool; } });
305
+ var appInput_2 = require("./system/appInput");
306
+ Object.defineProperty(exports, "appInputTool", { enumerable: true, get: function () { return appInput_2.appInputTool; } });
230
307
  var shellExec_2 = require("./terminal/shellExec");
231
308
  Object.defineProperty(exports, "shellExecTool", { enumerable: true, get: function () { return shellExec_2.shellExecTool; } });
232
309
  var executeCode_2 = require("./executeCode");
@@ -247,3 +324,5 @@ var memoryReplace_2 = require("./memory/memoryReplace");
247
324
  Object.defineProperty(exports, "memoryReplaceTool", { enumerable: true, get: function () { return memoryReplace_2.memoryReplaceTool; } });
248
325
  var memoryRemove_2 = require("./memory/memoryRemove");
249
326
  Object.defineProperty(exports, "memoryRemoveTool", { enumerable: true, get: function () { return memoryRemove_2.memoryRemoveTool; } });
327
+ var sessionSummary_2 = require("./memory/sessionSummary");
328
+ Object.defineProperty(exports, "sessionSummaryTool", { enumerable: true, get: function () { return sessionSummary_2.sessionSummaryTool; } });
@@ -12,14 +12,37 @@
12
12
  * Returns `verified: true` only after the post-write read confirms
13
13
  * the text is gone from the file.
14
14
  *
15
- * Status: PHASE 9.
15
+ * Phase v4.1.2-bug-X: user-approved durable facts (anything in
16
+ * MEMORY.md `## Durable facts`) are protected from autonomous
17
+ * deletion. A subsequent session on a weak model (llama-3.3 in the
18
+ * smoke test) called memory_remove on a Phase-D-promoted fact with
19
+ * reasoning "outdated" — violating Phase D's opt-in trust contract.
20
+ *
21
+ * The guard is STRICT: if the requested substring appears ANYWHERE
22
+ * in the `## Durable facts` body, the call is rejected. Substring
23
+ * removal operates whole-file, so partial protection would still
24
+ * nuke the durable copy as side-effect. Hard rejection (vs propose-
25
+ * and-defer) is honest: model must surface to user; only the user
26
+ * (editing MEMORY.md directly, or via a future `/forget` slash
27
+ * command) can revoke durable content.
28
+ *
29
+ * Status: PHASE 9 (PHASE v4.1.2-bug-X: section protection).
16
30
  */
17
31
  Object.defineProperty(exports, "__esModule", { value: true });
18
32
  exports.memoryRemoveTool = void 0;
33
+ const memoryGuard_1 = require("../../../moat/memoryGuard");
34
+ /** Section in MEMORY.md that Phase D promotion writes to. */
35
+ const DURABLE_FACTS_HEADER = '## Durable facts';
19
36
  exports.memoryRemoveTool = {
20
37
  schema: {
21
38
  name: 'memory_remove',
22
- description: 'Remove an entry from MEMORY.md or USER.md by substring match. Returns verified=true only after the change is confirmed on disk.',
39
+ description: 'Remove an entry from MEMORY.md or USER.md by substring match. ' +
40
+ 'CANNOT remove entries in MEMORY.md `## Durable facts` — those are ' +
41
+ 'user-approved facts the user explicitly promoted; only the user ' +
42
+ 'can revoke them by editing MEMORY.md directly. If you think a ' +
43
+ 'durable fact is outdated, surface that to the user and ask them ' +
44
+ 'to confirm; do not propose autonomous removal. Returns ' +
45
+ 'verified=true only after the change is confirmed on disk.',
23
46
  inputSchema: {
24
47
  type: 'object',
25
48
  properties: {
@@ -42,6 +65,38 @@ exports.memoryRemoveTool = {
42
65
  }
43
66
  const file = args.file === 'user' ? 'user' : 'memory';
44
67
  const text = String(args.text ?? '');
68
+ // Phase v4.1.2-bug-X: durable-section protection. Only applies
69
+ // to MEMORY.md (USER.md has no section structure today). The
70
+ // check requires snapshot access — when ctx.memory is not
71
+ // wired (test contexts, future surface refactors), we fall
72
+ // through to the existing guardedRemove behavior. Real CLI
73
+ // sessions wire memoryManager, so production paths get the
74
+ // guard. Documented intentional fall-through.
75
+ if (file === 'memory' && ctx.memory) {
76
+ try {
77
+ const snap = await ctx.memory.loadSnapshot();
78
+ const memoryMd = snap?.memoryMd ?? '';
79
+ if (memoryMd && (0, memoryGuard_1.containsInSection)(memoryMd, text, DURABLE_FACTS_HEADER)) {
80
+ const preview = text.length > 60
81
+ ? `${text.slice(0, 60)}…`
82
+ : text;
83
+ return {
84
+ success: false,
85
+ verified: false,
86
+ error: `Cannot remove "${preview}" — it's in MEMORY.md ` +
87
+ `\`${DURABLE_FACTS_HEADER}\`, which holds user-approved facts. ` +
88
+ `Only the user can revoke these. Ask them to confirm removal ` +
89
+ `in their next message; do not propose autonomous deletion.`,
90
+ file,
91
+ protectedSection: DURABLE_FACTS_HEADER,
92
+ };
93
+ }
94
+ }
95
+ catch {
96
+ // Snapshot read failed — fall through to old behavior rather
97
+ // than block legitimate removals on transient I/O errors.
98
+ }
99
+ }
45
100
  const r = await ctx.memoryGuard.guardedRemove(file, text);
46
101
  return {
47
102
  success: r.ok,
@@ -0,0 +1,151 @@
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
+ * tools/v4/memory/sessionSummary.ts — Phase v4.1.2 alive-core.
10
+ *
11
+ * `session_summary` — append a five-bullet summary of the current
12
+ * session to MEMORY.md under a `## Recent sessions` section.
13
+ *
14
+ * Why this exists: until v4.1.2, "what did we work on last session?"
15
+ * was unanswerable — MEMORY.md held durable facts but no rolling
16
+ * conversation log. After this tool runs at /quit (or on demand), the
17
+ * NEXT session's PromptBuilder injects MEMORY.md as a slot, and the
18
+ * model can read the summary back as ambient context.
19
+ *
20
+ * Design:
21
+ *
22
+ * - The model is responsible for generating the five bullets. It
23
+ * already has the full conversation context in its message
24
+ * history, so this tool's job is *persistence* — not LLM dispatch.
25
+ * This avoids threading the AuxiliaryClient into ToolContext just
26
+ * for one tool and keeps the verify-on-disk contract clean.
27
+ *
28
+ * - Section rotation: append the new entry at the top of the
29
+ * section (most-recent-first), keep the most recent 10, drop the
30
+ * rest. Bound the size so MEMORY.md doesn't grow indefinitely.
31
+ *
32
+ * - Write goes through `MemoryGuard.replaceSection`, which preserves
33
+ * the standard `verified: true` contract that
34
+ * HonestyEnforcement relies on.
35
+ */
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.sessionSummaryTool = void 0;
38
+ const RECENT_SESSIONS_HEADER = '## Recent sessions';
39
+ const MAX_RECENT_ENTRIES = 10;
40
+ /**
41
+ * Render one summary entry: timestamp header + bullets. Trims and
42
+ * normalises so two adjacent entries don't collide on whitespace.
43
+ */
44
+ function formatEntry(bullets, when) {
45
+ const stamp = when.toISOString().replace(/\.\d+Z$/, 'Z'); // second precision
46
+ const cleaned = bullets
47
+ .map((b) => b.trim())
48
+ .filter((b) => b.length > 0)
49
+ .map((b) => (b.startsWith('-') ? b : `- ${b}`));
50
+ return [`### ${stamp}`, ...cleaned].join('\n');
51
+ }
52
+ /**
53
+ * Split an existing Recent-sessions body into entries (each headed by
54
+ * a `### ` timestamp line). The most-recent entry is index 0.
55
+ */
56
+ function parseEntries(body) {
57
+ const trimmed = body.trim();
58
+ if (!trimmed)
59
+ return [];
60
+ // Split on `### ` at line start; first chunk is empty when the body
61
+ // starts with the marker, which we filter out.
62
+ const parts = trimmed
63
+ .split(/^### /m)
64
+ .map((p) => p.trim())
65
+ .filter((p) => p.length > 0)
66
+ .map((p) => `### ${p}`);
67
+ return parts;
68
+ }
69
+ exports.sessionSummaryTool = {
70
+ schema: {
71
+ name: 'session_summary',
72
+ description: 'Append a five-bullet summary of the current session to MEMORY.md ' +
73
+ '(under "## Recent sessions"). The next session will see it as ambient ' +
74
+ 'context. Call this at the end of a meaningful session, or right before ' +
75
+ 'the user types /quit. You craft the bullets — be concise and concrete: ' +
76
+ 'what we worked on, decisions made, files changed, problems solved, open items.',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ bullets: {
81
+ type: 'array',
82
+ description: 'Exactly five concise bullets (3-15 words each) summarising the session. ' +
83
+ 'Focus on what will be useful for the next session.',
84
+ items: {
85
+ type: 'string',
86
+ description: 'One bullet. Plain prose; leading "- " optional (added if missing).',
87
+ },
88
+ },
89
+ trigger: {
90
+ type: 'string',
91
+ enum: ['manual', 'auto-quit'],
92
+ description: 'Diagnostic only — whether the model invoked this directly ' +
93
+ '("manual") or the REPL auto-triggered it on /quit ("auto-quit"). ' +
94
+ 'Defaults to "manual" when omitted.',
95
+ },
96
+ },
97
+ required: ['bullets'],
98
+ },
99
+ },
100
+ category: 'write',
101
+ mutates: true,
102
+ toolset: 'memory',
103
+ async execute(args, ctx) {
104
+ if (!ctx.memoryGuard) {
105
+ return { success: false, error: 'memory guard not configured' };
106
+ }
107
+ if (!ctx.memory) {
108
+ return { success: false, error: 'memory manager not configured' };
109
+ }
110
+ const rawBullets = Array.isArray(args.bullets) ? args.bullets : [];
111
+ const bullets = rawBullets
112
+ .filter((b) => typeof b === 'string')
113
+ .map((b) => b.trim())
114
+ .filter((b) => b.length > 0);
115
+ if (bullets.length === 0) {
116
+ return {
117
+ success: false,
118
+ error: 'session_summary requires at least one non-empty bullet',
119
+ };
120
+ }
121
+ const now = new Date();
122
+ const newEntry = formatEntry(bullets, now);
123
+ // Read current MEMORY.md to find existing Recent-sessions body.
124
+ const snap = await ctx.memory.loadSnapshot();
125
+ const memoryMd = snap.memoryMd ?? '';
126
+ // Pull existing section body (if any). The header consumes its
127
+ // line; the capture group grabs everything until the next h2 or
128
+ // EOF. Whitespace-only bodies are captured as empty after trim.
129
+ const headerEscaped = RECENT_SESSIONS_HEADER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
130
+ // Note: NO `m` flag — we want `$` to mean end-of-string, not
131
+ // end-of-line. With `m`, the lookahead `$` matches before every
132
+ // newline and we capture only the first body line instead of the
133
+ // whole section.
134
+ const sectionRe = new RegExp(`${headerEscaped}[^\\n]*\\n([\\s\\S]*?)(?=\\n## |$)`);
135
+ const match = memoryMd.match(sectionRe);
136
+ const existingBody = match ? (match[1] ?? '').trim() : '';
137
+ const existingEntries = parseEntries(existingBody);
138
+ // Most-recent-first ordering, capped to 10.
139
+ const combined = [newEntry, ...existingEntries].slice(0, MAX_RECENT_ENTRIES);
140
+ const newBody = combined.join('\n\n');
141
+ const result = await ctx.memoryGuard.replaceSection('memory', RECENT_SESSIONS_HEADER, newBody);
142
+ return {
143
+ success: result.ok,
144
+ verified: result.verified,
145
+ error: result.ok ? undefined : result.reason,
146
+ entries: combined.length,
147
+ trigger: args.trigger ?? 'manual',
148
+ timestamp: now.toISOString(),
149
+ };
150
+ },
151
+ };
@@ -0,0 +1,177 @@
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
+ * tools/v4/sessions/recallSession.ts — Phase v4.1.2-memory-C.
10
+ *
11
+ * `recall_session` — return ranked SessionDistillation summaries for
12
+ * past sessions matching the user's query (or just the most recent N
13
+ * when no query is supplied).
14
+ *
15
+ * Coexists with `session_search`:
16
+ * - session_search → FTS5 over message TEXT in SessionStore.
17
+ * Returns per-message snippets. Use when the user wants the exact
18
+ * words of a past message.
19
+ * - recall_session → ranked DISTILLATIONS by TOPIC. Returns
20
+ * structured per-session summaries (decisions, open items, files
21
+ * touched). Use when the user wants context on what HAPPENED in
22
+ * past sessions.
23
+ *
24
+ * Index strategy: scan-all. Reads every distillation JSON from
25
+ * `<paths.root>/distillations/` per query. Expected file count is
26
+ * <1000 per user; sub-100ms at that scale. When telemetry shows
27
+ * latency >500ms, the escalation path is direct migration to SQLite
28
+ * FTS5 — JSON-index intermediate is intentionally skipped.
29
+ */
30
+ var __importDefault = (this && this.__importDefault) || function (mod) {
31
+ return (mod && mod.__esModule) ? mod : { "default": mod };
32
+ };
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.__testFs = exports.recallSessionTool = void 0;
35
+ exports.getDistillationsDir = getDistillationsDir;
36
+ const node_path_1 = __importDefault(require("node:path"));
37
+ const node_fs_1 = require("node:fs");
38
+ const distillationStore_1 = require("../../../core/v4/distillationStore");
39
+ const distillationIndex_1 = require("../../../core/v4/distillationIndex");
40
+ const DEFAULT_LIMIT = 5;
41
+ const MAX_LIMIT = 25;
42
+ exports.recallSessionTool = {
43
+ schema: {
44
+ name: 'recall_session',
45
+ description: 'Recall past SESSIONS by topic. Returns ranked summaries — ' +
46
+ 'decisions made, files touched, open items, tool usage — from ' +
47
+ 'previously persisted session distillations. ' +
48
+ 'For the EXACT WORDS of a past message, call `session_search` ' +
49
+ 'instead (FTS5 over message text). For "what happened" / "what ' +
50
+ 'did we work on" / "what was unfinished", use this tool.',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ query: {
55
+ type: 'string',
56
+ description: 'Optional keyword filter. Case-insensitive substring match ' +
57
+ 'across keywords, bullets, decisions, open_items, and ' +
58
+ 'tool names. Omit to get the most recent sessions.',
59
+ },
60
+ limit: {
61
+ type: 'number',
62
+ description: `Maximum number of matches to return. Default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}.`,
63
+ },
64
+ days: {
65
+ type: 'number',
66
+ description: 'Optional recency window in days. Drops distillations older ' +
67
+ 'than this before ranking. Omit for no time filter.',
68
+ },
69
+ include_full: {
70
+ type: 'boolean',
71
+ description: 'When true, each match also carries tools_used + keywords ' +
72
+ '(useful when the agent needs granular tool history). ' +
73
+ 'Default false to keep responses compact.',
74
+ },
75
+ },
76
+ },
77
+ },
78
+ category: 'read',
79
+ mutates: false,
80
+ toolset: 'sessions',
81
+ async execute(args, ctx) {
82
+ if (!ctx.paths?.root) {
83
+ return {
84
+ success: false,
85
+ error: 'recall_session requires resolved aiden paths',
86
+ };
87
+ }
88
+ const dir = node_path_1.default.join(ctx.paths.root, 'distillations');
89
+ // Read everything off disk first. Each failure (malformed JSON,
90
+ // EACCES on individual files) is skipped silently; the diagnostic
91
+ // for "files exist but couldn't be read" is the gap between
92
+ // scanned (id count) and dists.length (parse-success count).
93
+ let ids;
94
+ try {
95
+ ids = await (0, distillationStore_1.listDistillationIds)(dir);
96
+ }
97
+ catch (err) {
98
+ const code = err.code;
99
+ if (code === 'ENOENT') {
100
+ // No distillations directory yet — first-run case is
101
+ // success with zero matches, NOT a failure.
102
+ return {
103
+ success: true,
104
+ query: typeof args.query === 'string' ? args.query : undefined,
105
+ matches: [],
106
+ total_found: 0,
107
+ scanned: 0,
108
+ };
109
+ }
110
+ return {
111
+ success: false,
112
+ error: `Failed to enumerate distillations: ${err.message}`,
113
+ };
114
+ }
115
+ const dists = [];
116
+ for (const id of ids) {
117
+ try {
118
+ const d = await (0, distillationStore_1.readDistillation)(dir, id);
119
+ if (d)
120
+ dists.push(d);
121
+ }
122
+ catch {
123
+ // One bad file shouldn't prevent the agent from seeing the
124
+ // rest. The user can diagnose via `aiden doctor` (the file
125
+ // is still on disk).
126
+ }
127
+ }
128
+ const recallQuery = {
129
+ query: typeof args.query === 'string' ? args.query : undefined,
130
+ limit: typeof args.limit === 'number' ? args.limit : undefined,
131
+ days: typeof args.days === 'number' ? args.days : undefined,
132
+ include_full: args.include_full === true,
133
+ };
134
+ const ranked = (0, distillationIndex_1.rankDistillations)(dists, recallQuery);
135
+ // v4.1.3-repl-polish: mark degraded when any matched session was
136
+ // distilled with the Phase A+B `partial: true` flag (LLM-timeout
137
+ // path — deterministic fields present, semantic bullets/decisions
138
+ // may be empty). The model still gets the full match list; the
139
+ // trail row renders yellow so the user knows recall completed
140
+ // against partial data.
141
+ const partialCount = ranked.matches.filter((m) => m.partial === true).length;
142
+ const degraded = partialCount > 0;
143
+ return {
144
+ success: true,
145
+ query: recallQuery.query,
146
+ matches: ranked.matches,
147
+ total_found: ranked.total_found,
148
+ // scanned reflects files we attempted to read — useful diagnostic.
149
+ // If scanned > ranked.total_found AND dists.length < scanned, the
150
+ // delta is malformed files; the agent can suggest running aiden
151
+ // doctor to inspect.
152
+ scanned: ids.length,
153
+ ...(degraded && {
154
+ degraded: true,
155
+ degradedReason: partialCount === 1
156
+ ? '1 matched session has partial distillation data'
157
+ : `${partialCount} matched sessions have partial distillation data`,
158
+ }),
159
+ };
160
+ // Note re: subsystem health — wire-up happens at the runtime
161
+ // construction layer (cli/v4/aidenCLI.ts) where the registry is
162
+ // built. The tool itself stays pure of registry-knowledge for
163
+ // testability; the registry caller decides whether to wrap the
164
+ // file-read errors in a tracker.
165
+ },
166
+ };
167
+ // Expose the directory path for runtime wire-up. Tools that want to
168
+ // register recall_session with the slice3 SubsystemHealthRegistry
169
+ // pass this helper to the tracker so they get health snapshots without
170
+ // hard-coding the path in two places.
171
+ function getDistillationsDir(rootDir) {
172
+ return node_path_1.default.join(rootDir, 'distillations');
173
+ }
174
+ // Re-export read-only fs surface used by tests under controlled
175
+ // fixtures. Production code never imports from here; the tool calls
176
+ // fs directly.
177
+ exports.__testFs = node_fs_1.promises;
@@ -22,7 +22,11 @@ const MAX_LIMIT = 50;
22
22
  exports.sessionSearchTool = {
23
23
  schema: {
24
24
  name: 'session_search',
25
- description: 'Search past conversation sessions by keyword (FTS5 full-text). Returns matching message snippets with the session id and timestamp. Use to recall something the user said in an earlier conversation.',
25
+ description: 'Search past conversation MESSAGES by keyword (FTS5 full-text). ' +
26
+ 'Returns matching message SNIPPETS with the session id and timestamp. ' +
27
+ 'Use when you need the exact words a past message contained. ' +
28
+ 'For topic-level recall of what happened in past sessions (decisions, ' +
29
+ 'files touched, open items), call `recall_session` instead.',
26
30
  inputSchema: {
27
31
  type: 'object',
28
32
  properties: {