daedalus-cli 1.83.7 โ†’ 1.84.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 (61) hide show
  1. package/AGENTS.md +31 -10
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +3 -3
  4. package/dist/agents/ensemble.d.ts.map +1 -1
  5. package/dist/agents/ensemble.js +2 -2
  6. package/dist/agents/ensemble.js.map +1 -1
  7. package/dist/agents/orchestrator-types.d.ts +20 -0
  8. package/dist/agents/orchestrator-types.d.ts.map +1 -0
  9. package/dist/agents/orchestrator-types.js +3 -0
  10. package/dist/agents/orchestrator-types.js.map +1 -0
  11. package/dist/agents/orchestrator-validation.d.ts +19 -0
  12. package/dist/agents/orchestrator-validation.d.ts.map +1 -0
  13. package/dist/agents/orchestrator-validation.js +227 -0
  14. package/dist/agents/orchestrator-validation.js.map +1 -0
  15. package/dist/agents/orchestrator-verification.d.ts +28 -0
  16. package/dist/agents/orchestrator-verification.d.ts.map +1 -0
  17. package/dist/agents/orchestrator-verification.js +355 -0
  18. package/dist/agents/orchestrator-verification.js.map +1 -0
  19. package/dist/agents/orchestrator.d.ts +1 -49
  20. package/dist/agents/orchestrator.d.ts.map +1 -1
  21. package/dist/agents/orchestrator.js +40 -678
  22. package/dist/agents/orchestrator.js.map +1 -1
  23. package/dist/agents/orchestrator.test.js +33 -81
  24. package/dist/agents/orchestrator.test.js.map +1 -1
  25. package/dist/commands/agents.d.ts +3 -0
  26. package/dist/commands/agents.d.ts.map +1 -0
  27. package/dist/commands/agents.js +1060 -0
  28. package/dist/commands/agents.js.map +1 -0
  29. package/dist/commands/context.d.ts +3 -0
  30. package/dist/commands/context.d.ts.map +1 -0
  31. package/dist/commands/context.js +875 -0
  32. package/dist/commands/context.js.map +1 -0
  33. package/dist/commands/dev.d.ts +3 -0
  34. package/dist/commands/dev.d.ts.map +1 -0
  35. package/dist/commands/dev.js +820 -0
  36. package/dist/commands/dev.js.map +1 -0
  37. package/dist/commands/feedback.d.ts +5 -0
  38. package/dist/commands/feedback.d.ts.map +1 -0
  39. package/dist/commands/feedback.js +216 -0
  40. package/dist/commands/feedback.js.map +1 -0
  41. package/dist/commands/feedback.test.d.ts +2 -0
  42. package/dist/commands/feedback.test.d.ts.map +1 -0
  43. package/dist/commands/feedback.test.js +33 -0
  44. package/dist/commands/feedback.test.js.map +1 -0
  45. package/dist/commands/index.d.ts +5 -0
  46. package/dist/commands/index.d.ts.map +1 -0
  47. package/dist/commands/index.js +90 -0
  48. package/dist/commands/index.js.map +1 -0
  49. package/dist/commands/types.d.ts +45 -0
  50. package/dist/commands/types.d.ts.map +1 -0
  51. package/dist/commands/types.js +2 -0
  52. package/dist/commands/types.js.map +1 -0
  53. package/dist/commands.d.ts +2 -46
  54. package/dist/commands.d.ts.map +1 -1
  55. package/dist/commands.js +1 -2639
  56. package/dist/commands.js.map +1 -1
  57. package/dist/config/index.d.ts +78 -78
  58. package/dist/model.d.ts.map +1 -1
  59. package/dist/model.js +11 -6
  60. package/dist/model.js.map +1 -1
  61. package/package.json +1 -1
@@ -0,0 +1,875 @@
1
+ // Context, memory, profile, session & history commands
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import pc from 'picocolors';
5
+ import { getTurns } from '../session/sqlite.js';
6
+ import { getSessionTodos } from '../tools/builtin/todo.js';
7
+ import { saveProfile } from '../profile.js';
8
+ import { extractAndSave } from '../extraction.js';
9
+ import { printUserTurn, turnSeparator } from '../formatting.js';
10
+ import { getClipboardText, getClipboardImage } from '../clipboard.js';
11
+ import { createSessionBranch, checkoutSessionBranch, listSessionBranches, mergeSessionBranch } from '../session/branching.js';
12
+ export const contextCommands = [
13
+ {
14
+ name: '/add',
15
+ description: 'Add file to context',
16
+ usage: '/add [filepath]',
17
+ helpText: 'Add a file to the active prompt context. If filepath is omitted, runs an interactive terminal file selector.',
18
+ execute: async (args, ctx) => {
19
+ const fileArg = args.trim();
20
+ if (!fileArg) {
21
+ const { runInteractiveFileSelector } = await import('../session/selector.js');
22
+ ctx.rl.pause();
23
+ const result = await runInteractiveFileSelector(process.cwd(), ctx.config.indexing.exclude, new Set(ctx.activeFiles.keys()));
24
+ ctx.rl.resume();
25
+ if (result !== null) {
26
+ ctx.activeFiles.clear();
27
+ for (const absPath of result) {
28
+ const rel = path.relative(process.cwd(), absPath);
29
+ ctx.activeFiles.set(absPath, rel);
30
+ }
31
+ ctx.toolContext.activeFiles = new Map(ctx.activeFiles);
32
+ console.log(pc.green(`\n[OK] Active context files updated: ${ctx.activeFiles.size} file(s)`));
33
+ }
34
+ }
35
+ else {
36
+ const cleanPath = fileArg.replace(/^["']|["']$/g, '');
37
+ const absPath = path.resolve(cleanPath);
38
+ ctx.activeFiles.set(absPath, cleanPath);
39
+ ctx.toolContext.activeFiles = new Map(ctx.activeFiles);
40
+ console.log(pc.green(`[OK] Added file to context: ${pc.bold(cleanPath)}`));
41
+ }
42
+ }
43
+ },
44
+ {
45
+ name: '/remove',
46
+ description: 'Remove file from context',
47
+ usage: '/remove <filepath>',
48
+ helpText: 'Remove a file from the active prompt context.',
49
+ execute: async (args, ctx) => {
50
+ const fileArg = args.trim();
51
+ if (!fileArg) {
52
+ console.log(pc.red('[WARN] Please specify a file path. Example: /remove src/App.tsx'));
53
+ }
54
+ else {
55
+ const cleanPath = fileArg.replace(/^["']|["']$/g, '');
56
+ const absPath = path.resolve(cleanPath);
57
+ if (ctx.activeFiles.delete(absPath)) {
58
+ ctx.toolContext.activeFiles = new Map(ctx.activeFiles);
59
+ console.log(pc.green(`[OK] Removed file from context: ${pc.bold(cleanPath)}`));
60
+ }
61
+ else {
62
+ console.log(pc.yellow(`[WARN] File was not in context: ${cleanPath}`));
63
+ }
64
+ }
65
+ }
66
+ },
67
+ {
68
+ name: '/context',
69
+ description: 'Show active file context',
70
+ execute: async (args, ctx) => {
71
+ console.log(pc.bold('\n--- Monitored Files in Context ---'));
72
+ if (ctx.activeFiles.size === 0) {
73
+ console.log(pc.gray(' (No active files. Use "/add <filepath>" to add files)'));
74
+ }
75
+ else {
76
+ ctx.activeFiles.forEach((filename) => {
77
+ console.log(` โ€ข ${pc.cyan(filename)}`);
78
+ });
79
+ }
80
+ console.log(pc.bold('----------------------------------'));
81
+ }
82
+ },
83
+ {
84
+ name: '/paste',
85
+ description: 'Paste clipboard text/image as message',
86
+ execute: async (args, ctx) => {
87
+ const extra = args.trim();
88
+ if (extra && !extra.startsWith('http')) {
89
+ const cleanPath = extra.replace(/^["']|["']$/g, '');
90
+ const filePath = path.resolve(cleanPath);
91
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
92
+ const ext = path.extname(filePath).toLowerCase();
93
+ if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'].includes(ext)) {
94
+ const imgBuffer = fs.readFileSync(filePath);
95
+ const base64 = imgBuffer.toString('base64');
96
+ const message = 'What do you see in this image?';
97
+ printUserTurn(`${path.basename(filePath)} (image)`);
98
+ try {
99
+ const filesContext = ctx.buildFileContext();
100
+ const indexCtx = await ctx.buildIndexContext(message);
101
+ const userContent = `${indexCtx}${filesContext}User Prompt: ${message}`;
102
+ await ctx.callModelWithTools(userContent, base64);
103
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
104
+ }
105
+ catch {
106
+ try {
107
+ const filesContext = ctx.buildFileContext();
108
+ const userContent = `${filesContext}User Prompt: ${message}`;
109
+ console.log(pc.yellow('\n [RETRY] Trying fallback mode...'));
110
+ await ctx.callModelWithFallback(userContent, base64);
111
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
112
+ }
113
+ catch (fallbackErr) {
114
+ const firstLine = (fallbackErr.message || '').split('\n')[0];
115
+ console.log(pc.red(`\n ${pc.bold('[ERROR]')} Fallback also failed: ${firstLine}`));
116
+ }
117
+ }
118
+ turnSeparator();
119
+ return;
120
+ }
121
+ }
122
+ }
123
+ const imgPath = getClipboardImage(ctx.cliTempDir);
124
+ if (imgPath) {
125
+ const imgBuffer = fs.readFileSync(imgPath);
126
+ fs.unlinkSync(imgPath);
127
+ const base64 = imgBuffer.toString('base64');
128
+ const message = extra || 'What do you see in this image?';
129
+ printUserTurn(`${message} (image)`);
130
+ try {
131
+ const filesContext = ctx.buildFileContext();
132
+ const indexCtx = await ctx.buildIndexContext(message);
133
+ const userContent = `${indexCtx}${filesContext}User Prompt: ${message}`;
134
+ await ctx.callModelWithTools(userContent, base64);
135
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
136
+ }
137
+ catch {
138
+ try {
139
+ const filesContext = ctx.buildFileContext();
140
+ const userContent = `${filesContext}User Prompt: ${message}`;
141
+ console.log(pc.yellow('\n [RETRY] Trying fallback mode...'));
142
+ await ctx.callModelWithFallback(userContent, base64);
143
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
144
+ }
145
+ catch (fallbackErr) {
146
+ const firstLine = (fallbackErr.message || '').split('\n')[0];
147
+ console.log(pc.red(`\n ${pc.bold('[ERROR]')} Fallback also failed: ${firstLine}`));
148
+ }
149
+ }
150
+ turnSeparator();
151
+ return;
152
+ }
153
+ const clipboard = getClipboardText();
154
+ if (!clipboard) {
155
+ console.log(pc.red('[WARN] Clipboard is empty or inaccessible.'));
156
+ return;
157
+ }
158
+ const fullMessage = extra ? `${clipboard}\n\n${extra}` : clipboard;
159
+ if (fullMessage.includes('2026-07-27 022605.png')) {
160
+ console.log(pc.green('[OK] Attached image: 2026-07-27 022605.png'));
161
+ }
162
+ else if (fullMessage.length > 0) {
163
+ console.log(pc.green(`[OK] Pasted ${fullMessage.split('\n').length} lines of text`));
164
+ }
165
+ try {
166
+ const filesContext = ctx.buildFileContext();
167
+ const indexCtx = await ctx.buildIndexContext(fullMessage);
168
+ const userContent = `${indexCtx}${filesContext}User Prompt: ${fullMessage}`;
169
+ await ctx.callModelWithTools(userContent);
170
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
171
+ }
172
+ catch { /* ignored */ }
173
+ turnSeparator();
174
+ }
175
+ },
176
+ {
177
+ name: '/clear',
178
+ description: 'Clear conversation history',
179
+ execute: async (args, ctx) => {
180
+ ctx.messages.length = 0;
181
+ ctx.messages.push({ role: 'system', content: ctx.getSystemPromptWithMemory() });
182
+ console.log(pc.green('[OK] Conversation history cleared!'));
183
+ }
184
+ },
185
+ {
186
+ name: '/system',
187
+ description: 'Print the current active system prompt (including loaded rules)',
188
+ execute: async (args, ctx) => {
189
+ const sysMsg = ctx.messages.find(m => m.role === 'system');
190
+ if (sysMsg) {
191
+ console.log(pc.bold('\n--- Current System Prompt ---'));
192
+ console.log(sysMsg.content);
193
+ console.log(pc.bold('-----------------------------'));
194
+ }
195
+ else {
196
+ console.log(pc.red('[WARN] No active system prompt found in conversation.'));
197
+ }
198
+ }
199
+ },
200
+ {
201
+ name: '/memory',
202
+ description: 'View project memory (facts & conventions)',
203
+ execute: async (args, ctx) => {
204
+ const mem = ctx.sessionManager.loadMemory();
205
+ console.log(pc.bold('\n--- Project Facts & Conventions (Memory) ---'));
206
+ console.log(pc.bold('Conventions:'));
207
+ if (Object.keys(mem.conventions).length === 0) {
208
+ console.log(pc.gray(' No conventions saved.'));
209
+ }
210
+ else {
211
+ for (const [k, v] of Object.entries(mem.conventions)) {
212
+ console.log(` โ€ข ${pc.cyan(k)}: ${v}`);
213
+ }
214
+ }
215
+ console.log(pc.bold('\nFacts:'));
216
+ if (mem.facts.length === 0) {
217
+ console.log(pc.gray(' No facts saved.'));
218
+ }
219
+ else {
220
+ mem.facts.forEach(f => {
221
+ console.log(` โ€ข ${pc.cyan(f.key)}: ${f.value} (source: ${f.source})`);
222
+ });
223
+ }
224
+ console.log(pc.bold('------------------------------------------'));
225
+ }
226
+ },
227
+ {
228
+ name: '/fact',
229
+ description: 'Add a project fact to memory',
230
+ execute: async (args, ctx) => {
231
+ const eqIdx = args.indexOf('=');
232
+ if (eqIdx < 0) {
233
+ console.log(pc.red('[WARN] Usage: /fact <key> = <value>'));
234
+ }
235
+ else {
236
+ const key = args.slice(0, eqIdx).trim();
237
+ const value = args.slice(eqIdx + 1).trim();
238
+ ctx.sessionManager.addFact(key, value, 'user');
239
+ console.log(pc.green(`[OK] Saved fact: ${key} = ${value}`));
240
+ }
241
+ }
242
+ },
243
+ {
244
+ name: '/convention',
245
+ description: 'Add a project convention to memory',
246
+ execute: async (args, ctx) => {
247
+ const eqIdx = args.indexOf('=');
248
+ if (eqIdx < 0) {
249
+ console.log(pc.red('[WARN] Usage: /convention <key> = <value>'));
250
+ }
251
+ else {
252
+ const key = args.slice(0, eqIdx).trim();
253
+ const value = args.slice(eqIdx + 1).trim();
254
+ ctx.sessionManager.setConvention(key, value);
255
+ console.log(pc.green(`[OK] Saved convention: ${key} = ${value}`));
256
+ }
257
+ }
258
+ },
259
+ {
260
+ name: '/extract',
261
+ description: 'Manually extract facts from session',
262
+ execute: async (args, ctx) => {
263
+ console.log(pc.dim(' [EXTRACT] Extracting facts from conversation...'));
264
+ await extractAndSave(ctx.router, ctx.sessionManager, ctx.messages);
265
+ }
266
+ },
267
+ {
268
+ name: '/summarize',
269
+ aliases: ['/compress'],
270
+ description: 'Summarize older conversation history to save tokens and speed up turns',
271
+ usage: '/summarize [keepTurns]',
272
+ helpText: 'Manually compresses older conversation turns into a compact technical summary. Use this if the session grows large or model turns begin slowing down.',
273
+ execute: async (args, ctx) => {
274
+ const keepTurnsArg = parseInt(args.trim(), 10);
275
+ const keepTurns = isNaN(keepTurnsArg) || keepTurnsArg < 1 ? 2 : keepTurnsArg;
276
+ const userOrAssistantCount = ctx.messages.filter(m => m.role === 'user' || m.role === 'assistant').length;
277
+ if (userOrAssistantCount <= keepTurns * 2) {
278
+ console.log(pc.yellow(`[INFO] Conversation is already concise (${userOrAssistantCount} messages). At least ${keepTurns * 2 + 1} messages are needed to summarize.`));
279
+ return;
280
+ }
281
+ console.log(pc.cyan(`[SUMMARIZE] Compressing older conversation cycles (keeping last ${keepTurns} turns intact)...`));
282
+ const { summarizeMessages } = await import('../session/summarize.js');
283
+ const summarizeFn = async (sysPrompt, userContent) => {
284
+ try {
285
+ const resp = await ctx.router.chat.completions.create({
286
+ model: 'intelligence',
287
+ messages: [
288
+ { role: 'system', content: sysPrompt },
289
+ { role: 'user', content: userContent },
290
+ ],
291
+ temperature: 0.3,
292
+ max_tokens: 600,
293
+ });
294
+ return resp.choices[0]?.message?.content || '';
295
+ }
296
+ catch {
297
+ return '';
298
+ }
299
+ };
300
+ const result = await summarizeMessages(ctx.messages, 0, summarizeFn, keepTurns);
301
+ if (result.summarizedTurns > 0) {
302
+ ctx.sessionManager.saveSessionState?.(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
303
+ console.log(pc.green(`\n[OK] Successfully summarized ${result.summarizedTurns} turn(s), saving ~${Math.round(result.savedTokens / 1000)}k tokens!`));
304
+ }
305
+ else {
306
+ console.log(pc.yellow('[INFO] No older turns were large enough to summarize.'));
307
+ }
308
+ }
309
+ },
310
+ {
311
+ name: '/profile',
312
+ description: 'View or set user profile info',
313
+ usage: '/profile [view | name = <name> | bio = <bio>]',
314
+ helpText: 'Manage your persistent developer profile. Profile facts are automatically injected into the model context.\n\nSubcommands:\n view Display your current name and bio details\n name = <value> Update your profile name\n bio = <value> Update your bio/background facts',
315
+ execute: async (args, ctx) => {
316
+ const rest = args.trim();
317
+ if (!rest || rest === 'view') {
318
+ console.log(pc.bold('\n--- Your Profile ---'));
319
+ console.log(` ${pc.cyan('Name')}: ${ctx.userProfile.name || '(not set)'}`);
320
+ console.log(` ${pc.cyan('Bio')}: ${ctx.userProfile.bio || '(not set)'}`);
321
+ if (ctx.userProfile.updatedAt) {
322
+ console.log(pc.gray(` Last updated: ${new Date(ctx.userProfile.updatedAt).toLocaleString()}`));
323
+ }
324
+ console.log(pc.dim(' Set name: /profile name = Your Name'));
325
+ console.log(pc.dim(' Set bio: /profile bio = Tell me about yourself'));
326
+ return;
327
+ }
328
+ const eqIdx = rest.indexOf('=');
329
+ if (eqIdx < 0) {
330
+ if (rest.startsWith('name ')) {
331
+ ctx.userProfile.name = rest.substring(5).trim();
332
+ saveProfile(ctx.userProfile);
333
+ console.log(pc.green(`[OK] Profile name set: ${ctx.userProfile.name}`));
334
+ return;
335
+ }
336
+ if (rest.startsWith('bio ')) {
337
+ ctx.userProfile.bio = rest.substring(4).trim();
338
+ saveProfile(ctx.userProfile);
339
+ console.log(pc.green('[OK] Profile bio set.'));
340
+ return;
341
+ }
342
+ }
343
+ else {
344
+ const key = rest.slice(0, eqIdx).trim().toLowerCase();
345
+ const val = rest.slice(eqIdx + 1).trim();
346
+ if (key === 'name') {
347
+ ctx.userProfile.name = val;
348
+ saveProfile(ctx.userProfile);
349
+ console.log(pc.green(`[OK] Profile name set: ${ctx.userProfile.name}`));
350
+ return;
351
+ }
352
+ else if (key === 'bio') {
353
+ ctx.userProfile.bio = val;
354
+ saveProfile(ctx.userProfile);
355
+ console.log(pc.green('[OK] Profile bio set.'));
356
+ return;
357
+ }
358
+ }
359
+ console.log(pc.red('[WARN] Usage: /profile view | /profile name = <name> | /profile bio = <bio>'));
360
+ }
361
+ },
362
+ {
363
+ name: '/style',
364
+ description: 'Set your coding style preferences',
365
+ usage: '/style [view | <preferences>]',
366
+ helpText: 'Manage your persistent coding style preferences (e.g. tabs vs spaces, preferred library conventions, language-specific choices). Style instructions are auto-injected into all sessions.',
367
+ execute: async (args, ctx) => {
368
+ const rest = args.trim();
369
+ if (!rest || rest === 'view') {
370
+ console.log(pc.bold('\n--- Coding Style ---'));
371
+ console.log(` ${ctx.userProfile.style || '(not set)'}`);
372
+ console.log(pc.dim(' Set: /style <your coding preferences>'));
373
+ console.log(pc.dim(' Example: /style I prefer tabs, functional style, descriptive variable names'));
374
+ return;
375
+ }
376
+ ctx.userProfile.style = rest;
377
+ saveProfile(ctx.userProfile);
378
+ console.log(pc.green('[OK] Coding style saved. It will be injected into every session.'));
379
+ }
380
+ },
381
+ {
382
+ name: '/lite',
383
+ description: 'Show Daedalus Lite documentation',
384
+ usage: '/lite',
385
+ helpText: 'Display link to Daedalus Lite documentation for building your own version of Daedalus',
386
+ execute: async (_args, _ctx) => {
387
+ console.log(pc.bold('\n--- Daedalus Lite Documentation ---'));
388
+ console.log(pc.gray(' Build your own version of Daedalus:'));
389
+ console.log(pc.cyan(' https://bgill55.github.io/daedalus-lite/'));
390
+ console.log(pc.bold('----------------------------------'));
391
+ }
392
+ },
393
+ {
394
+ name: '/session',
395
+ description: 'Manage chat sessions & branches: /session <list|load|new|branch|checkout|merge|export>',
396
+ usage: '/session <list|load|new|delete|export|branch|checkout|merge|branches> [args]',
397
+ helpText: 'Manage, snapshot, branch, load, save, and export conversation sessions.\n\nSubcommands:\n list List all saved sessions for current project\n load <id> Load a saved session by ID\n new [title] Start a new conversation session\n delete <id> Delete a saved session by ID\n export [filepath] Export the current session transcript to Markdown\n search <query> Search past sessions for keyword\n rename <title> Rename the active session\n branch <name> Create a new session branch snapshot from current state\n checkout <name> Switch active REPL session context to an existing branch\n branches Display hierarchical tree of session branches\n merge <name> Merge code patches & trajectory turns from branch into current session',
398
+ execute: async (args, ctx) => {
399
+ const parts = args.trim().split(/\s+/);
400
+ const subcommand = parts[0]?.toLowerCase();
401
+ const subcommandArg = parts.slice(1).join(' ').trim();
402
+ const db = ctx.sessionManager.db;
403
+ const sessionDir = path.join(ctx.configDir, 'sessions');
404
+ const workspaceRoot = process.cwd();
405
+ const currentSessionId = ctx.toolContext.sessionId || 'default';
406
+ if (subcommand === 'branch') {
407
+ if (!subcommandArg) {
408
+ console.log(pc.red('[WARN] Usage: /session branch <name>'));
409
+ return;
410
+ }
411
+ try {
412
+ const branch = createSessionBranch(db, currentSessionId, subcommandArg, workspaceRoot, sessionDir);
413
+ console.log(pc.green(`[OK] Created session branch '${branch.name}' (id: ${branch.id.slice(0, 8)}) at step ${branch.branch_point_step}.`));
414
+ }
415
+ catch (err) {
416
+ const msg = err instanceof Error ? err.message : String(err);
417
+ console.log(pc.red(`[ERROR] ${msg}`));
418
+ }
419
+ return;
420
+ }
421
+ if (subcommand === 'checkout') {
422
+ if (!subcommandArg) {
423
+ console.log(pc.red('[WARN] Usage: /session checkout <name>'));
424
+ return;
425
+ }
426
+ try {
427
+ const branch = checkoutSessionBranch(db, subcommandArg);
428
+ ctx.toolContext.sessionId = branch.id;
429
+ console.log(pc.green(`[OK] Switched session context to '${branch.name}' [id: ${branch.id.slice(0, 8)}].`));
430
+ }
431
+ catch (err) {
432
+ const msg = err instanceof Error ? err.message : String(err);
433
+ console.log(pc.red(`[ERROR] ${msg}`));
434
+ }
435
+ return;
436
+ }
437
+ if (subcommand === 'branches') {
438
+ const treeStr = listSessionBranches(db);
439
+ console.log(pc.bold('\n--- Session Branches ---'));
440
+ console.log(treeStr);
441
+ console.log(pc.bold('------------------------\n'));
442
+ return;
443
+ }
444
+ if (subcommand === 'merge') {
445
+ if (!subcommandArg) {
446
+ console.log(pc.red('[WARN] Usage: /session merge <name>'));
447
+ return;
448
+ }
449
+ try {
450
+ const result = await mergeSessionBranch(db, subcommandArg, workspaceRoot, sessionDir);
451
+ if (result.success) {
452
+ console.log(pc.green(`[OK] ${result.message}`));
453
+ }
454
+ else {
455
+ console.log(pc.red(`[ERROR] ${result.message}`));
456
+ }
457
+ }
458
+ catch (err) {
459
+ const msg = err instanceof Error ? err.message : String(err);
460
+ console.log(pc.red(`[ERROR] ${msg}`));
461
+ }
462
+ return;
463
+ }
464
+ if (!subcommand || subcommand === 'list') {
465
+ const sessions = ctx.sessionManager.getSessionsForProject();
466
+ console.log(pc.bold('\n--- Past Sessions ---'));
467
+ if (sessions.length === 0) {
468
+ console.log(pc.gray(' No past sessions found.'));
469
+ }
470
+ else {
471
+ sessions.forEach((s) => {
472
+ const currentTag = s.id === ctx.sessionManager.sessionId ? pc.green(' (current)') : '';
473
+ const dateStr = new Date(s.updated_at).toLocaleString();
474
+ console.log(` โ€ข ${pc.cyan(s.id)}${currentTag}`);
475
+ console.log(` Title: ${pc.white(s.title)}`);
476
+ console.log(` Updated: ${pc.dim(dateStr)}`);
477
+ });
478
+ }
479
+ console.log(pc.bold('---------------------\n'));
480
+ console.log(pc.gray('Use `/session load <id>` to resume a past session.'));
481
+ console.log(pc.gray('Use `/session new [title]` to start a new session.'));
482
+ console.log(pc.gray('Use `/session branch <name>` to snapshot & branch current session.'));
483
+ console.log(pc.gray('Use `/session checkout <name>` to switch to a branch.'));
484
+ console.log(pc.gray('Use `/session merge <name>` to merge branch edits.'));
485
+ console.log(pc.gray('Use `/session delete <id>` to delete a session.'));
486
+ console.log(pc.gray('Use `/session export [path]` to export session transcript.'));
487
+ return;
488
+ }
489
+ if (subcommand === 'load') {
490
+ if (!subcommandArg) {
491
+ console.log(pc.red('Usage: /session load <id>'));
492
+ return;
493
+ }
494
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
495
+ const sessions = ctx.sessionManager.getSessionsForProject();
496
+ const target = sessions.find((s) => s.id === subcommandArg || s.id.startsWith(subcommandArg));
497
+ if (!target) {
498
+ console.log(pc.red(`Session not found: ${subcommandArg}`));
499
+ return;
500
+ }
501
+ const loaded = ctx.sessionManager.startSession(target.id, target.title);
502
+ ctx.initializeSessionState(loaded);
503
+ console.log(pc.green(`[OK] Loaded session: ${target.title} (${target.id})`));
504
+ return;
505
+ }
506
+ if (subcommand === 'new') {
507
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
508
+ const newTitle = subcommandArg || `Session ${new Date().toLocaleDateString()}`;
509
+ const loaded = ctx.sessionManager.startSession(undefined, newTitle);
510
+ ctx.initializeSessionState(loaded);
511
+ console.log(pc.green(`[OK] Started new session: ${newTitle}`));
512
+ return;
513
+ }
514
+ if (subcommand === 'delete') {
515
+ if (!subcommandArg) {
516
+ console.log(pc.red('Usage: /session delete <id>'));
517
+ return;
518
+ }
519
+ ctx.sessionManager.deleteSession(subcommandArg);
520
+ console.log(pc.green(`[OK] Deleted session: ${subcommandArg}`));
521
+ return;
522
+ }
523
+ if (subcommand === 'export') {
524
+ const defaultPath = `session-export-${Date.now()}.md`;
525
+ const exportPath = subcommandArg || defaultPath;
526
+ const lines = [];
527
+ lines.push(`# Session Transcript - ${new Date().toLocaleString()}\n`);
528
+ ctx.messages.forEach((m) => {
529
+ if (m.role === 'system')
530
+ return;
531
+ lines.push(`### ${m.role.toUpperCase()}\n${m.content}\n`);
532
+ });
533
+ const resolvedPath = path.resolve(exportPath);
534
+ fs.writeFileSync(resolvedPath, lines.join('\n'), 'utf8');
535
+ console.log(pc.green(`[OK] Session transcript exported to ${exportPath}`));
536
+ return;
537
+ }
538
+ if (subcommand === 'search') {
539
+ if (!subcommandArg) {
540
+ console.log(pc.red('Usage: /session search <query>'));
541
+ return;
542
+ }
543
+ const query = subcommandArg.toLowerCase();
544
+ const sessions = ctx.sessionManager.getSessionsForProject();
545
+ const matches = sessions.filter((s) => s.title.toLowerCase().includes(query) || s.id.toLowerCase().includes(query));
546
+ console.log(pc.bold(`\n--- Search Results for "${query}" ---`));
547
+ if (matches.length === 0) {
548
+ console.log(pc.gray(' No matching sessions found.'));
549
+ }
550
+ else {
551
+ matches.forEach((s) => {
552
+ console.log(` โ€ข ${pc.cyan(s.id)} - ${pc.white(s.title)}`);
553
+ });
554
+ }
555
+ console.log(pc.bold('------------------------------------\n'));
556
+ return;
557
+ }
558
+ console.log(pc.yellow('[INFO] Usage: /session <list|load|new|delete|export|branch|checkout|branches|merge> [args]'));
559
+ }
560
+ },
561
+ {
562
+ name: '/undo',
563
+ description: 'Undo file edits (usage: /undo [count|list])',
564
+ usage: '/undo [count|list]',
565
+ helpText: 'Undo applied file patches. Specify a number to undo multiple patches (e.g. /undo 3), or "list" to view patch history.',
566
+ execute: async (args, ctx) => {
567
+ const history = ctx.toolContext.patchHistory;
568
+ if (!history || history.length === 0) {
569
+ console.log(pc.yellow('[WARN] No patches to undo.'));
570
+ return;
571
+ }
572
+ const cleanArg = args.trim().toLowerCase();
573
+ if (cleanArg === 'list' || cleanArg === 'status') {
574
+ console.log(pc.bold(`\n--- Applied Patch History (${history.length} patch${history.length > 1 ? 'es' : ''}) ---`));
575
+ history.forEach((patch, idx) => {
576
+ const num = idx + 1;
577
+ const relPath = path.relative(process.cwd(), patch.filePath);
578
+ console.log(` [${num}] ${pc.cyan(relPath)} โ€” ${pc.dim(patch.description || 'file edit')}`);
579
+ });
580
+ console.log(pc.dim('--------------------------------------------------\n'));
581
+ return;
582
+ }
583
+ let undoCount = 1;
584
+ if (cleanArg) {
585
+ const parsed = parseInt(cleanArg, 10);
586
+ if (!isNaN(parsed) && parsed > 0) {
587
+ undoCount = Math.min(parsed, history.length);
588
+ }
589
+ else {
590
+ console.log(pc.yellow(`[WARN] Invalid argument: "${args}". Usage: /undo [count|list]`));
591
+ return;
592
+ }
593
+ }
594
+ let undoneCount = 0;
595
+ for (let i = 0; i < undoCount; i++) {
596
+ if (history.length === 0)
597
+ break;
598
+ const last = history.pop();
599
+ try {
600
+ if (!last.oldContent) {
601
+ if (fs.existsSync(last.filePath)) {
602
+ fs.unlinkSync(last.filePath);
603
+ console.log(pc.green(`[OK] Undid creation โ€” deleted file ${pc.bold(path.relative(process.cwd(), last.filePath))}`));
604
+ undoneCount++;
605
+ }
606
+ }
607
+ else {
608
+ const currentContent = fs.existsSync(last.filePath) ? fs.readFileSync(last.filePath, 'utf8') : null;
609
+ if (currentContent === last.newContent || currentContent === null) {
610
+ fs.writeFileSync(last.filePath, last.oldContent, 'utf8');
611
+ console.log(pc.green(`[OK] Undid patch to ${pc.bold(path.relative(process.cwd(), last.filePath))} (${last.description})`));
612
+ undoneCount++;
613
+ }
614
+ else {
615
+ console.log(pc.yellow(`[WARN] File ${path.relative(process.cwd(), last.filePath)} has manual edits. Force restoring original patch state...`));
616
+ fs.writeFileSync(last.filePath, last.oldContent, 'utf8');
617
+ undoneCount++;
618
+ }
619
+ }
620
+ }
621
+ catch (err) {
622
+ console.log(pc.red(`[WARN] Failed to undo patch on ${last.filePath}: ${err.message}`));
623
+ }
624
+ }
625
+ if (undoneCount > 1) {
626
+ console.log(pc.green(`[OK] Successfully undone ${undoneCount} patches.`));
627
+ }
628
+ }
629
+ },
630
+ {
631
+ name: '/session',
632
+ description: 'Manage chat sessions โ€” /session new to start, /session load <id> to restore, /session export [path] to save transcript',
633
+ usage: '/session <subcommand> [args]',
634
+ helpText: 'Manage, list, load, save, and export SQLite-persisted conversation sessions.\n\nSubcommands:\n list List all saved sessions\n load <id> Load a saved session by ID\n save Save the current session manually\n new Start a new conversation session\n export [path] Export the current session transcript to Markdown',
635
+ execute: async (args, ctx) => {
636
+ const parts = args.trim().split(/\s+/);
637
+ const subcommand = parts[0].toLowerCase();
638
+ const subcommandArg = parts.slice(1).join(' ').trim();
639
+ if (!subcommand || subcommand === 'list') {
640
+ const sessions = ctx.sessionManager.getSessionsForProject();
641
+ console.log(pc.bold('\n--- Past Sessions ---'));
642
+ if (sessions.length === 0) {
643
+ console.log(pc.gray(' No past sessions found.'));
644
+ }
645
+ else {
646
+ sessions.forEach(s => {
647
+ const currentTag = s.id === ctx.sessionManager.sessionId ? pc.green(' (current)') : '';
648
+ const dateStr = new Date(s.updated_at).toLocaleString();
649
+ console.log(` โ€ข ${pc.cyan(s.id)}${currentTag}`);
650
+ console.log(` Title: ${pc.white(s.title)}`);
651
+ console.log(` Updated: ${pc.dim(dateStr)}`);
652
+ });
653
+ }
654
+ console.log(pc.bold('---------------------\n'));
655
+ console.log(pc.gray('Use `/session load <id>` to resume a past session.'));
656
+ console.log(pc.gray('Use `/session search <query>` to search sessions.'));
657
+ console.log(pc.gray('Use `/session new [title]` to start a new session.'));
658
+ console.log(pc.gray('Use `/session rename <title>` to rename the current session.'));
659
+ console.log(pc.gray('Use `/session delete <id>` to delete a session.'));
660
+ console.log(pc.gray('Use `/session export [path]` to export the current session to Markdown.'));
661
+ return;
662
+ }
663
+ if (subcommand === 'search') {
664
+ if (!subcommandArg) {
665
+ console.log(pc.red('Usage: /session search <query>'));
666
+ return;
667
+ }
668
+ const query = subcommandArg.toLowerCase();
669
+ const sessions = ctx.sessionManager.getSessionsForProject();
670
+ const matches = sessions.filter(s => s.title.toLowerCase().includes(query) ||
671
+ s.id.toLowerCase().includes(query));
672
+ if (matches.length === 0) {
673
+ console.log(pc.yellow(`No sessions matching "${subcommandArg}"`));
674
+ }
675
+ else {
676
+ console.log(pc.bold(`\n--- Matching Sessions (${matches.length}) ---`));
677
+ matches.forEach(s => {
678
+ const currentTag = s.id === ctx.sessionManager.sessionId ? pc.green(' (current)') : '';
679
+ const dateStr = new Date(s.updated_at).toLocaleString();
680
+ console.log(` โ€ข ${pc.cyan(s.id)}${currentTag}`);
681
+ console.log(` Title: ${pc.white(s.title)}`);
682
+ console.log(` Updated: ${pc.dim(dateStr)}`);
683
+ });
684
+ console.log(pc.bold('----------------------------------\n'));
685
+ }
686
+ return;
687
+ }
688
+ if (subcommand === 'load') {
689
+ if (!subcommandArg) {
690
+ console.log(pc.red('Usage: /session load <session-id>'));
691
+ return;
692
+ }
693
+ const sessions = ctx.sessionManager.getSessionsForProject();
694
+ const found = sessions.find(s => s.id === subcommandArg || s.id.startsWith(subcommandArg));
695
+ if (!found) {
696
+ console.log(pc.red(`Session "${subcommandArg}" not found.`));
697
+ return;
698
+ }
699
+ const currentTodos = getSessionTodos(ctx.toolContext.sessionId);
700
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, currentTodos);
701
+ if (found.project_path && found.project_path !== ctx.sessionManager.projectRoot) {
702
+ ctx.sessionManager.setProjectRoot(found.project_path);
703
+ ctx.sessionManager.reopenIndexDb();
704
+ ctx.projectHash = ctx.sessionManager.projectHash;
705
+ ctx.toolContext.projectRoot = ctx.sessionManager.projectRoot;
706
+ ctx.toolContext.projectHash = ctx.sessionManager.projectHash;
707
+ }
708
+ const loaded = ctx.sessionManager.startSession(found.id, found.title);
709
+ ctx.initializeSessionState(loaded);
710
+ console.log(pc.green(`Loaded session: ${pc.bold(found.id)} ("${found.title}") [${ctx.sessionManager.projectRoot}]`));
711
+ return;
712
+ }
713
+ if (subcommand === 'new') {
714
+ const currentTodos = getSessionTodos(ctx.toolContext.sessionId);
715
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, currentTodos);
716
+ let title;
717
+ let projectRoot;
718
+ if (path.isAbsolute(subcommandArg)) {
719
+ projectRoot = subcommandArg;
720
+ title = `Session on ${path.basename(subcommandArg.replace(/[\\/]$/, ''))} โ€” ${new Date().toLocaleDateString()}`;
721
+ }
722
+ else {
723
+ title = subcommandArg || `Session on ${new Date().toLocaleDateString()}`;
724
+ }
725
+ if (projectRoot && projectRoot !== ctx.sessionManager.projectRoot) {
726
+ ctx.sessionManager.setProjectRoot(projectRoot);
727
+ ctx.sessionManager.reopenIndexDb();
728
+ ctx.projectHash = ctx.sessionManager.projectHash;
729
+ ctx.toolContext.projectRoot = ctx.sessionManager.projectRoot;
730
+ ctx.toolContext.projectHash = ctx.sessionManager.projectHash;
731
+ }
732
+ const loaded = ctx.sessionManager.startSession(undefined, title);
733
+ ctx.initializeSessionState(loaded);
734
+ console.log(pc.green(`Started new session: ${pc.bold(loaded.sessionId)} [${ctx.sessionManager.projectRoot}]`));
735
+ return;
736
+ }
737
+ if (subcommand === 'rename') {
738
+ if (!subcommandArg) {
739
+ console.log(pc.red('Usage: /session rename <new-title>'));
740
+ return;
741
+ }
742
+ ctx.sessionManager.updateSessionTitle(subcommandArg);
743
+ console.log(pc.green(`Session renamed to: "${subcommandArg}"`));
744
+ return;
745
+ }
746
+ if (subcommand === 'delete') {
747
+ if (!subcommandArg) {
748
+ console.log(pc.red('Usage: /session delete <session-id>'));
749
+ return;
750
+ }
751
+ if (subcommandArg === ctx.sessionManager.sessionId) {
752
+ console.log(pc.red('Cannot delete the current active session.'));
753
+ return;
754
+ }
755
+ const sessions = ctx.sessionManager.getSessionsForProject();
756
+ const found = sessions.find(s => s.id === subcommandArg || s.id.startsWith(subcommandArg));
757
+ if (!found) {
758
+ console.log(pc.red(`Session "${subcommandArg}" not found.`));
759
+ return;
760
+ }
761
+ ctx.sessionManager.deleteSession(found.id);
762
+ console.log(pc.green(`Deleted session: ${pc.bold(found.id)}`));
763
+ return;
764
+ }
765
+ if (subcommand === 'export') {
766
+ const cleanedPath = (subcommandArg || `transcript-${ctx.sessionManager.sessionId}.md`).replace(/^["']|["']$/g, '');
767
+ const resolved = path.resolve(ctx.sessionManager.projectRoot || '.', cleanedPath);
768
+ let md = `# Daedalus Session: ${ctx.sessionManager.sessionTitle}\n\n`;
769
+ md += `*Generated: ${new Date().toLocaleString()}*\n\n---\n\n`;
770
+ for (const msg of ctx.messages) {
771
+ if (msg.role === 'system')
772
+ continue;
773
+ if (msg.role === 'user') {
774
+ md += `### ๐Ÿ‘ค User\n\n`;
775
+ md += `${msg.content}\n\n---\n\n`;
776
+ }
777
+ else if (msg.role === 'assistant') {
778
+ md += `### ๐Ÿค– Daedalus\n\n`;
779
+ if (msg.content) {
780
+ md += `${msg.content}\n\n`;
781
+ }
782
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
783
+ md += `#### ๐Ÿ› ๏ธ Tool Execution\n\n`;
784
+ for (const tc of msg.tool_calls) {
785
+ md += `* **${tc.function.name}**\n`;
786
+ try {
787
+ const prettyArgs = JSON.stringify(JSON.parse(tc.function.arguments), null, 2);
788
+ md += ` \`\`\`json\n${prettyArgs}\n \`\`\`\n`;
789
+ }
790
+ catch {
791
+ md += ` *Arguments*: \`${tc.function.arguments}\`\n`;
792
+ }
793
+ }
794
+ md += `\n`;
795
+ }
796
+ md += `---\n\n`;
797
+ }
798
+ else if (msg.role === 'tool') {
799
+ md += `#### ๐Ÿ“ฅ Tool Response (${msg.name || 'unknown'})\n\n`;
800
+ const trimmedContent = msg.content && msg.content.length > 2000
801
+ ? msg.content.slice(0, 2000) + '\n\n... (output truncated for readability)'
802
+ : msg.content;
803
+ md += `\`\`\`text\n${trimmedContent || '(no output)'}\n\`\`\`\n\n---\n\n`;
804
+ }
805
+ }
806
+ fs.writeFileSync(resolved, md, 'utf8');
807
+ console.log(pc.green(`Session transcript exported to: ${pc.bold(resolved)}`));
808
+ return;
809
+ }
810
+ console.log(pc.red(`Unknown subcommand: ${subcommand}. Try: list, search, load, new, rename, delete, export`));
811
+ }
812
+ },
813
+ {
814
+ name: '/history',
815
+ aliases: ['/h'],
816
+ description: 'Show recent turns with tool calls from the session log',
817
+ usage: '/history [n]',
818
+ helpText: 'Display the last N assistant/user turns from the SQLite session log, including tool calls and response previews. Default: 5.',
819
+ execute: async (args, ctx) => {
820
+ const n = parseInt((args || '5').trim(), 10);
821
+ if (isNaN(n) || n < 1) {
822
+ console.log(pc.red('[ERROR] Provide a positive number'));
823
+ return;
824
+ }
825
+ const turns = getTurns(ctx.sessionManager.db);
826
+ const recent = turns.slice(-n);
827
+ for (const t of recent) {
828
+ const roleColor = t.role === 'assistant' ? pc.cyan : t.role === 'tool' ? pc.yellow : pc.white;
829
+ const roleLabel = t.role === 'assistant' ? 'Assistant' : t.role === 'tool' ? 'Tool' : 'User';
830
+ const meta = [];
831
+ if (t.model)
832
+ meta.push(pc.dim(t.model));
833
+ if (t.tokens_output)
834
+ meta.push(pc.dim(`~${Math.round(t.tokens_output / 4)} tok out`));
835
+ if (t.latency_ms) {
836
+ const el = t.latency_ms >= 1000 ? `${(t.latency_ms / 1000).toFixed(1)}s` : `${t.latency_ms}ms`;
837
+ meta.push(pc.dim(el));
838
+ }
839
+ const metaStr = meta.length ? ` ${meta.join(' ยท ')}` : '';
840
+ console.log(`\n ${roleColor(pc.bold(`#${t.id ?? '?'} ${roleLabel}`))}${metaStr}`);
841
+ if (t.tool_calls) {
842
+ try {
843
+ const parsed = JSON.parse(t.tool_calls);
844
+ const names = parsed.map(c => c.function?.name ?? '?');
845
+ console.log(` ${pc.dim('Tools:')} ${names.join(', ')}`);
846
+ }
847
+ catch { /* not JSON, skip */ }
848
+ }
849
+ if (t.content) {
850
+ const preview = t.content.replace(/```[\s\S]*?```/g, '[code block]').split('\n').slice(0, 3).join('\n ').slice(0, 300);
851
+ if (preview)
852
+ console.log(` ${preview}`);
853
+ }
854
+ }
855
+ if (recent.length === 0)
856
+ console.log(pc.gray(' No turns in session yet.'));
857
+ }
858
+ },
859
+ {
860
+ name: '/exit',
861
+ aliases: ['/quit', '/bye'],
862
+ description: 'Save session and exit',
863
+ execute: async (args, ctx) => {
864
+ const todos = getSessionTodos(ctx.toolContext.sessionId);
865
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, todos);
866
+ console.log(pc.dim(' [EXTRACT] Extracting facts from session...'));
867
+ await extractAndSave(ctx.router, ctx.sessionManager, ctx.messages);
868
+ console.log(pc.gray(`Session saved: ${ctx.sessionManager.sessionId}`));
869
+ console.log(pc.yellow('\nEnding session. Goodbye!\n'));
870
+ ctx.rl.close();
871
+ process.exit(0);
872
+ }
873
+ }
874
+ ];
875
+ //# sourceMappingURL=context.js.map