codemini-cli 0.6.3 → 0.6.4

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 (48) hide show
  1. package/codemini-web/dist/assets/{AboutDialog-jgqGjQgl.js → AboutDialog-MRopwNIL.js} +2 -2
  2. package/codemini-web/dist/assets/CodeWikiPanel-UpK5xGE3.js +1 -0
  3. package/codemini-web/dist/assets/ConfigDialog-CNl28wsj.js +1 -0
  4. package/codemini-web/dist/assets/GitDiffDialog-gSysUg2J.js +3 -0
  5. package/codemini-web/dist/assets/{MemoryDialog-BhxQgG0I.js → MemoryDialog-DFUmo3Kl.js} +3 -3
  6. package/codemini-web/dist/assets/MessageBubble-CGnnViv0.js +12 -0
  7. package/codemini-web/dist/assets/PatchDiff-B8rwvEg5.js +230 -0
  8. package/codemini-web/dist/assets/ProjectSelector-BF59M1zb.js +1 -0
  9. package/codemini-web/dist/assets/{SkillDialog-DxS43NpR.js → SkillDialog-CQTjbSiw.js} +4 -4
  10. package/codemini-web/dist/assets/SoulDialog-BLjUGqqB.js +1 -0
  11. package/codemini-web/dist/assets/chevron-right--85xg7qk.js +1 -0
  12. package/codemini-web/dist/assets/{chunk-BO2N2NFS-Budy_hfO.js → chunk-BO2N2NFS-6uELoidu.js} +6 -6
  13. package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-CQS1PAvD.js → highlighted-body-OFNGDK62-gb1UMBZ5.js} +1 -1
  14. package/codemini-web/dist/assets/index-1xqD0R5t.css +2 -0
  15. package/codemini-web/dist/assets/index-CDXQGwPs.js +65 -0
  16. package/codemini-web/dist/assets/{input-CNQgbKe6.js → input-Ca8O_061.js} +1 -1
  17. package/codemini-web/dist/assets/lib-BXWizt13.js +1 -0
  18. package/codemini-web/dist/assets/mermaid-GHXKKRXX-ROliF8Yd.js +1 -0
  19. package/codemini-web/dist/assets/{pencil-Ce_LFiEh.js → pencil-BhT11Ztp.js} +1 -1
  20. package/codemini-web/dist/assets/{refresh-cw-BKL-AZu5.js → refresh-cw-D7R5Lth6.js} +1 -1
  21. package/codemini-web/dist/assets/select-DBvcHBzs.js +1 -0
  22. package/codemini-web/dist/assets/{trash-2-KmAlCwXd.js → trash-2-BfNZcWfX.js} +1 -1
  23. package/codemini-web/dist/index.html +2 -2
  24. package/codemini-web/lib/runtime-bridge.js +325 -296
  25. package/codemini-web/server.js +310 -243
  26. package/package.json +1 -1
  27. package/src/core/agent-loop.js +188 -97
  28. package/src/core/chat-runtime.js +674 -571
  29. package/src/core/config-store.js +11 -3
  30. package/src/core/git-oplog-change-tracker.js +387 -0
  31. package/src/core/non-git-backup.js +116 -0
  32. package/src/core/paths.js +123 -123
  33. package/src/core/session-store.js +148 -99
  34. package/src/core/tools.js +499 -456
  35. package/src/tui/chat-app.js +196 -56
  36. package/codemini-web/dist/assets/CodeWikiPanel-EPuoerNv.js +0 -1
  37. package/codemini-web/dist/assets/ConfigDialog-B5IGZCc9.js +0 -1
  38. package/codemini-web/dist/assets/GitDiffDialog-Bb_Tw5ZK.js +0 -222
  39. package/codemini-web/dist/assets/MessageBubble-wUff4GP4.js +0 -6
  40. package/codemini-web/dist/assets/ProjectSelector-C0leTf6f.js +0 -1
  41. package/codemini-web/dist/assets/SoulDialog-XDTEGWvH.js +0 -1
  42. package/codemini-web/dist/assets/chevron-right-Dbzw7YzA.js +0 -1
  43. package/codemini-web/dist/assets/index-D0EGtNPr.js +0 -65
  44. package/codemini-web/dist/assets/index-wOUf3WkN.css +0 -2
  45. package/codemini-web/dist/assets/lib-BOngVP_M.js +0 -11
  46. package/codemini-web/dist/assets/lib-DrOTTm_N.js +0 -1
  47. package/codemini-web/dist/assets/mermaid-GHXKKRXX-DrBu5KyC.js +0 -1
  48. package/codemini-web/dist/assets/select-BZXfigic.js +0 -1
package/src/core/paths.js CHANGED
@@ -1,123 +1,123 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
-
4
- const GLOBAL_APP_DIR = 'codemini-global';
5
- const PROJECT_APP_DIR = '.codemini';
6
- const PROJECT_INDEX_DIR = '.codemini';
7
-
8
- export function getBaseConfigDir() {
9
- if (process.env.CODEMINI_GLOBAL_DIR) {
10
- return process.env.CODEMINI_GLOBAL_DIR;
11
- }
12
-
13
- if (process.platform === 'win32') {
14
- const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
15
- return path.join(appData, GLOBAL_APP_DIR);
16
- }
17
-
18
- if (process.platform === 'darwin') {
19
- return path.join(os.homedir(), 'Library', 'Preferences', GLOBAL_APP_DIR);
20
- }
21
-
22
- if (process.env.XDG_CONFIG_HOME) {
23
- return path.join(process.env.XDG_CONFIG_HOME, GLOBAL_APP_DIR);
24
- }
25
-
26
- return path.join(process.cwd(), '.codemini-global');
27
- }
28
-
29
- export function getLegacyConfigDir() {
30
- return getBaseConfigDir();
31
- }
32
-
33
- export function getConfigFilePath() {
34
- return path.join(getBaseConfigDir(), 'config.json');
35
- }
36
-
37
- export function getSessionsDir() {
38
- return path.join(getBaseConfigDir(), 'sessions');
39
- }
40
-
41
- export function getSkillsDir() {
42
- return path.join(getBaseConfigDir(), 'skills');
43
- }
44
-
45
- export function getSkillRegistryPath() {
46
- return path.join(getBaseConfigDir(), 'skill-registry.json');
47
- }
48
-
49
- export function getCommandsDir() {
50
- return path.join(getBaseConfigDir(), 'commands');
51
- }
52
-
53
- export function getMemoryDir() {
54
- return path.join(getBaseConfigDir(), 'memory');
55
- }
56
-
57
- export function getInputHistoryFilePath() {
58
- return path.join(getBaseConfigDir(), 'input-history.json');
59
- }
60
-
61
- export function getProjectWorkspaceDir(cwd = process.cwd()) {
62
- return path.join(cwd, PROJECT_APP_DIR);
63
- }
64
-
65
- export function getProjectCommandsDir(cwd = process.cwd()) {
66
- return path.join(getProjectWorkspaceDir(cwd), 'commands');
67
- }
68
-
69
- export function getProjectSkillsDir(cwd = process.cwd()) {
70
- return path.join(getProjectWorkspaceDir(cwd), 'skills');
71
- }
72
-
73
- export function getProjectSpecsDir(cwd = process.cwd(), sessionId = '') {
74
- return sessionId
75
- ? path.join(getProjectWorkspaceDir(cwd), 'specs', String(sessionId))
76
- : path.join(getProjectWorkspaceDir(cwd), 'specs');
77
- }
78
-
79
- export function getProjectPlansDir(cwd = process.cwd(), sessionId = '') {
80
- return sessionId
81
- ? path.join(getProjectWorkspaceDir(cwd), 'plans', String(sessionId))
82
- : path.join(getProjectWorkspaceDir(cwd), 'plans');
83
- }
84
-
85
- export function getProjectCheckpointsDir(cwd = process.cwd()) {
86
- return path.join(getProjectWorkspaceDir(cwd), 'checkpoints');
87
- }
88
-
89
- export function getProjectTasksDir(cwd = process.cwd()) {
90
- return path.join(getProjectWorkspaceDir(cwd), 'tasks');
91
- }
92
-
93
- export function getProjectLegacyTasksFilePath(cwd = process.cwd()) {
94
- return path.join(getProjectWorkspaceDir(cwd), 'tasks.json');
95
- }
96
-
97
- export function getProjectMapPath(cwd = process.cwd()) {
98
- return path.join(cwd, PROJECT_INDEX_DIR, 'project-map.json');
99
- }
100
-
101
- export function getFileIndexPath(cwd = process.cwd()) {
102
- return path.join(cwd, PROJECT_INDEX_DIR, 'file-index.json');
103
- }
104
-
105
- export function getProjectIndexDir(cwd = process.cwd()) {
106
- return path.join(cwd, PROJECT_INDEX_DIR);
107
- }
108
-
109
- export function getProjectMemoryDir(cwd = process.cwd()) {
110
- return path.join(getProjectIndexDir(cwd), 'memory');
111
- }
112
-
113
- export function getInboxDir() {
114
- return path.join(getMemoryDir(), 'inbox');
115
- }
116
-
117
- export function getArchiveDir() {
118
- return path.join(getMemoryDir(), 'archive');
119
- }
120
-
121
- export function getDreamAuditDir() {
122
- return path.join(getMemoryDir(), 'audit');
123
- }
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ const GLOBAL_APP_DIR = 'codemini-global';
5
+ const PROJECT_APP_DIR = '.codemini';
6
+ const PROJECT_INDEX_DIR = '.codemini';
7
+
8
+ export function getBaseConfigDir() {
9
+ if (process.env.CODEMINI_GLOBAL_DIR) {
10
+ return process.env.CODEMINI_GLOBAL_DIR;
11
+ }
12
+
13
+ if (process.platform === 'win32') {
14
+ const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
15
+ return path.join(appData, GLOBAL_APP_DIR);
16
+ }
17
+
18
+ if (process.platform === 'darwin') {
19
+ return path.join(os.homedir(), 'Library', 'Preferences', GLOBAL_APP_DIR);
20
+ }
21
+
22
+ if (process.env.XDG_CONFIG_HOME) {
23
+ return path.join(process.env.XDG_CONFIG_HOME, GLOBAL_APP_DIR);
24
+ }
25
+
26
+ return path.join(process.cwd(), '.codemini-global');
27
+ }
28
+
29
+ export function getLegacyConfigDir() {
30
+ return getBaseConfigDir();
31
+ }
32
+
33
+ export function getConfigFilePath() {
34
+ return path.join(getBaseConfigDir(), 'config.json');
35
+ }
36
+
37
+ export function getSessionsDir() {
38
+ return path.join(getBaseConfigDir(), 'sessions');
39
+ }
40
+
41
+ export function getSkillsDir() {
42
+ return path.join(getBaseConfigDir(), 'skills');
43
+ }
44
+
45
+ export function getSkillRegistryPath() {
46
+ return path.join(getBaseConfigDir(), 'skill-registry.json');
47
+ }
48
+
49
+ export function getCommandsDir() {
50
+ return path.join(getBaseConfigDir(), 'commands');
51
+ }
52
+
53
+ export function getMemoryDir() {
54
+ return path.join(getBaseConfigDir(), 'memory');
55
+ }
56
+
57
+ export function getInputHistoryFilePath() {
58
+ return path.join(getBaseConfigDir(), 'input-history.json');
59
+ }
60
+
61
+ export function getProjectWorkspaceDir(cwd = process.cwd()) {
62
+ return path.join(cwd, PROJECT_APP_DIR);
63
+ }
64
+
65
+ export function getProjectCommandsDir(cwd = process.cwd()) {
66
+ return path.join(getProjectWorkspaceDir(cwd), 'commands');
67
+ }
68
+
69
+ export function getProjectSkillsDir(cwd = process.cwd()) {
70
+ return path.join(getProjectWorkspaceDir(cwd), 'skills');
71
+ }
72
+
73
+ export function getProjectSpecsDir(cwd = process.cwd(), sessionId = '') {
74
+ return sessionId
75
+ ? path.join(getProjectWorkspaceDir(cwd), 'specs', String(sessionId))
76
+ : path.join(getProjectWorkspaceDir(cwd), 'specs');
77
+ }
78
+
79
+ export function getProjectPlansDir(cwd = process.cwd(), sessionId = '') {
80
+ return sessionId
81
+ ? path.join(getProjectWorkspaceDir(cwd), 'plans', String(sessionId))
82
+ : path.join(getProjectWorkspaceDir(cwd), 'plans');
83
+ }
84
+
85
+ export function getProjectCheckpointsDir(cwd = process.cwd()) {
86
+ return path.join(getProjectWorkspaceDir(cwd), 'checkpoints');
87
+ }
88
+
89
+ export function getProjectTasksDir(cwd = process.cwd()) {
90
+ return path.join(getProjectWorkspaceDir(cwd), 'tasks');
91
+ }
92
+
93
+ export function getProjectLegacyTasksFilePath(cwd = process.cwd()) {
94
+ return path.join(getProjectWorkspaceDir(cwd), 'tasks.json');
95
+ }
96
+
97
+ export function getProjectMapPath(cwd = process.cwd()) {
98
+ return path.join(cwd, PROJECT_INDEX_DIR, 'project-map.json');
99
+ }
100
+
101
+ export function getFileIndexPath(cwd = process.cwd()) {
102
+ return path.join(cwd, PROJECT_INDEX_DIR, 'file-index.json');
103
+ }
104
+
105
+ export function getProjectIndexDir(cwd = process.cwd()) {
106
+ return path.join(cwd, PROJECT_INDEX_DIR);
107
+ }
108
+
109
+ export function getProjectMemoryDir(cwd = process.cwd()) {
110
+ return path.join(getProjectIndexDir(cwd), 'memory');
111
+ }
112
+
113
+ export function getInboxDir() {
114
+ return path.join(getMemoryDir(), 'inbox');
115
+ }
116
+
117
+ export function getArchiveDir() {
118
+ return path.join(getMemoryDir(), 'archive');
119
+ }
120
+
121
+ export function getDreamAuditDir() {
122
+ return path.join(getMemoryDir(), 'audit');
123
+ }
@@ -17,7 +17,7 @@ function createSessionId() {
17
17
  return `${ts}-${rand}`;
18
18
  }
19
19
 
20
- function sanitizeToolCall(tc, index) {
20
+ function sanitizeToolCall(tc, index) {
21
21
  const id = String(tc?.id || `tc-${index + 1}`);
22
22
  const fnName = String(tc?.function?.name || tc?.name || '').trim();
23
23
  const fnArgsRaw = tc?.function?.arguments ?? tc?.arguments ?? '{}';
@@ -31,58 +31,67 @@ function sanitizeToolCall(tc, index) {
31
31
  arguments: fnArgs
32
32
  }
33
33
  };
34
- if (Number.isFinite(Number(tc?.durationMs))) out.durationMs = Number(tc.durationMs);
34
+ if (Number.isFinite(Number(tc?.durationMs))) out.durationMs = Number(tc.durationMs);
35
35
  if (typeof tc?.summary === 'string' && tc.summary.trim()) out.summary = tc.summary.trim();
36
36
  if (typeof tc?.status === 'string' && tc.status.trim()) out.status = tc.status.trim();
37
+ if (tc?.resultMeta && typeof tc.resultMeta === 'object' && !Array.isArray(tc.resultMeta)) {
38
+ out.resultMeta = { ...tc.resultMeta };
39
+ }
37
40
  const fileChange = sanitizeFileChange(tc?.fileChange);
38
41
  if (fileChange) out.fileChange = fileChange;
42
+ if (Array.isArray(tc?.fileChanges)) {
43
+ const fileChanges = tc.fileChanges.map(sanitizeFileChange).filter(Boolean);
44
+ if (fileChanges.length > 0) out.fileChanges = fileChanges;
45
+ }
39
46
  return out;
40
47
  }
41
-
42
- function sanitizeFileChange(change) {
43
- if (!change || typeof change !== 'object') return null;
44
- const filePath = String(change.path || '').trim();
45
- if (!filePath) return null;
46
- const action = String(change.action || '').trim();
47
- return {
48
- path: filePath,
49
- action: action === 'create' || action === 'delete' ? action : 'edit',
48
+
49
+ function sanitizeFileChange(change) {
50
+ if (!change || typeof change !== 'object') return null;
51
+ const filePath = String(change.path || '').trim();
52
+ if (!filePath) return null;
53
+ const action = String(change.action || '').trim();
54
+ return {
55
+ path: filePath,
56
+ action: action === 'create' || action === 'delete' ? action : 'edit',
50
57
  linesAdded: Math.max(0, Math.round(Number(change.linesAdded || 0))),
51
58
  linesRemoved: Math.max(0, Math.round(Number(change.linesRemoved || 0))),
52
59
  changedLine: Math.max(0, Math.round(Number(change.changedLine || 0))),
53
- diffPreview: String(change.diffPreview || '')
60
+ diffPreview: String(change.diffPreview || ''),
61
+ changeSetId: String(change.changeSetId || ''),
62
+ patchRef: String(change.patchRef || '')
54
63
  };
55
64
  }
56
65
 
57
- function normalizeWhitespace(value) {
58
- return String(value || '').replace(/\s+/g, ' ').trim();
59
- }
60
-
61
- function sanitizeUsage(usage) {
62
- if (!usage || typeof usage !== 'object') return null;
63
- const out = {};
64
- const copyNumber = (from, to = from) => {
65
- const value = Number(usage?.[from]);
66
- if (Number.isFinite(value)) out[to] = Math.max(0, Math.round(value));
67
- };
68
- copyNumber('inputTokens');
69
- copyNumber('outputTokens');
70
- copyNumber('totalTokens');
71
- copyNumber('cachedInputTokens');
72
- copyNumber('cacheMissInputTokens');
73
- copyNumber('cacheWriteInputTokens');
74
- copyNumber('reasoningOutputTokens');
75
- copyNumber('requests');
76
- if (Array.isArray(usage.raw) && usage.raw.length > 0) {
77
- out.raw = usage.raw.filter((item) => item && typeof item === 'object').map((item) => ({ ...item }));
78
- } else if (usage.raw && typeof usage.raw === 'object') {
79
- out.raw = [{ ...usage.raw }];
80
- }
81
- return Object.keys(out).length ? out : null;
82
- }
83
-
84
- function stripMarkdown(value) {
85
- return normalizeWhitespace(value)
66
+ function normalizeWhitespace(value) {
67
+ return String(value || '').replace(/\s+/g, ' ').trim();
68
+ }
69
+
70
+ function sanitizeUsage(usage) {
71
+ if (!usage || typeof usage !== 'object') return null;
72
+ const out = {};
73
+ const copyNumber = (from, to = from) => {
74
+ const value = Number(usage?.[from]);
75
+ if (Number.isFinite(value)) out[to] = Math.max(0, Math.round(value));
76
+ };
77
+ copyNumber('inputTokens');
78
+ copyNumber('outputTokens');
79
+ copyNumber('totalTokens');
80
+ copyNumber('cachedInputTokens');
81
+ copyNumber('cacheMissInputTokens');
82
+ copyNumber('cacheWriteInputTokens');
83
+ copyNumber('reasoningOutputTokens');
84
+ copyNumber('requests');
85
+ if (Array.isArray(usage.raw) && usage.raw.length > 0) {
86
+ out.raw = usage.raw.filter((item) => item && typeof item === 'object').map((item) => ({ ...item }));
87
+ } else if (usage.raw && typeof usage.raw === 'object') {
88
+ out.raw = [{ ...usage.raw }];
89
+ }
90
+ return Object.keys(out).length ? out : null;
91
+ }
92
+
93
+ function stripMarkdown(value) {
94
+ return normalizeWhitespace(value)
86
95
  .replace(/```[\s\S]*?```/g, ' ')
87
96
  .replace(/`([^`]*)`/g, '$1')
88
97
  .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
@@ -111,56 +120,63 @@ function sanitizeMessage(msg) {
111
120
  };
112
121
 
113
122
  if (typeof msg?.model_content === 'string' && msg.model_content) out.model_content = msg.model_content;
114
- if (msg?.tool_call_id) out.tool_call_id = String(msg.tool_call_id);
115
- if (Number.isFinite(Number(msg?.tool_duration_ms))) out.tool_duration_ms = Number(msg.tool_duration_ms);
123
+ if (msg?.tool_call_id) out.tool_call_id = String(msg.tool_call_id);
124
+ if (Number.isFinite(Number(msg?.tool_duration_ms))) out.tool_duration_ms = Number(msg.tool_duration_ms);
116
125
  if (typeof msg?.tool_summary === 'string' && msg.tool_summary.trim()) out.tool_summary = msg.tool_summary.trim();
117
126
  if (typeof msg?.tool_status === 'string' && msg.tool_status.trim()) out.tool_status = msg.tool_status.trim();
127
+ if (msg?.tool_result_meta && typeof msg.tool_result_meta === 'object' && !Array.isArray(msg.tool_result_meta)) {
128
+ out.tool_result_meta = { ...msg.tool_result_meta };
129
+ }
118
130
  const toolFileChange = sanitizeFileChange(msg?.tool_file_change);
119
131
  if (toolFileChange) out.tool_file_change = toolFileChange;
120
- if (typeof msg?.name === 'string' && msg.name.trim()) out.name = msg.name.trim();
121
- if (typeof msg?.at === 'string' && msg.at.trim()) out.at = msg.at;
122
- if (typeof msg?.reasoning_content === 'string' && msg.reasoning_content) {
123
- out.reasoning_content = msg.reasoning_content;
124
- }
125
- if (typeof msg?.reasoning_started_at === 'string' && msg.reasoning_started_at.trim()) {
126
- out.reasoning_started_at = msg.reasoning_started_at;
127
- }
128
- if (typeof msg?.reasoning_ended_at === 'string' && msg.reasoning_ended_at.trim()) {
129
- out.reasoning_ended_at = msg.reasoning_ended_at;
130
- }
131
- if (Number.isFinite(Number(msg?.reasoning_duration_ms))) {
132
- out.reasoning_duration_ms = Math.max(0, Math.round(Number(msg.reasoning_duration_ms)));
132
+ if (Array.isArray(msg?.tool_file_changes)) {
133
+ const toolFileChanges = msg.tool_file_changes.map(sanitizeFileChange).filter(Boolean);
134
+ if (toolFileChanges.length > 0) out.tool_file_changes = toolFileChanges;
133
135
  }
134
- if (Array.isArray(msg?.reasoning_details) && msg.reasoning_details.length > 0) {
136
+ if (typeof msg?.name === 'string' && msg.name.trim()) out.name = msg.name.trim();
137
+ if (typeof msg?.at === 'string' && msg.at.trim()) out.at = msg.at;
138
+ if (typeof msg?.reasoning_content === 'string' && msg.reasoning_content) {
139
+ out.reasoning_content = msg.reasoning_content;
140
+ }
141
+ if (typeof msg?.reasoning_started_at === 'string' && msg.reasoning_started_at.trim()) {
142
+ out.reasoning_started_at = msg.reasoning_started_at;
143
+ }
144
+ if (typeof msg?.reasoning_ended_at === 'string' && msg.reasoning_ended_at.trim()) {
145
+ out.reasoning_ended_at = msg.reasoning_ended_at;
146
+ }
147
+ if (Number.isFinite(Number(msg?.reasoning_duration_ms))) {
148
+ out.reasoning_duration_ms = Math.max(0, Math.round(Number(msg.reasoning_duration_ms)));
149
+ }
150
+ if (Array.isArray(msg?.reasoning_details) && msg.reasoning_details.length > 0) {
135
151
  out.reasoning_details = msg.reasoning_details
136
152
  .filter((detail) => detail && typeof detail === 'object')
137
153
  .map((detail) => ({ ...detail }));
138
154
  }
139
155
 
140
- if (Array.isArray(msg?.tool_calls)) {
141
- const toolCalls = msg.tool_calls.map(sanitizeToolCall).filter(Boolean);
142
- if (toolCalls.length > 0) out.tool_calls = toolCalls;
143
- }
144
- if (Array.isArray(msg?.file_changes)) {
145
- const fileChanges = msg.file_changes.map(sanitizeFileChange).filter(Boolean);
146
- if (fileChanges.length > 0) out.file_changes = fileChanges;
147
- }
148
- const usage = sanitizeUsage(msg?.usage);
149
- if (usage) out.usage = usage;
150
-
151
- if (Array.isArray(msg?.plan_transcript)) {
152
- out.plan_transcript = msg.plan_transcript
153
- .filter((entry) => entry && typeof entry === 'object')
154
- .map((entry) => {
155
- const { usage, ...rest } = entry;
156
- const cleanUsage = sanitizeUsage(usage);
157
- return {
158
- ...rest,
159
- ...(cleanUsage ? { usage: cleanUsage } : {}),
160
- segments: Array.isArray(entry.segments) ? entry.segments : []
161
- };
162
- });
163
- }
156
+ if (Array.isArray(msg?.tool_calls)) {
157
+ const toolCalls = msg.tool_calls.map(sanitizeToolCall).filter(Boolean);
158
+ if (toolCalls.length > 0) out.tool_calls = toolCalls;
159
+ }
160
+ if (Array.isArray(msg?.file_changes)) {
161
+ const fileChanges = msg.file_changes.map(sanitizeFileChange).filter(Boolean);
162
+ if (fileChanges.length > 0) out.file_changes = fileChanges;
163
+ }
164
+ const usage = sanitizeUsage(msg?.usage);
165
+ if (usage) out.usage = usage;
166
+
167
+ if (Array.isArray(msg?.plan_transcript)) {
168
+ out.plan_transcript = msg.plan_transcript
169
+ .filter((entry) => entry && typeof entry === 'object')
170
+ .map((entry) => {
171
+ const { usage, ...rest } = entry;
172
+ const cleanUsage = sanitizeUsage(usage);
173
+ return {
174
+ ...rest,
175
+ ...(cleanUsage ? { usage: cleanUsage } : {}),
176
+ segments: Array.isArray(entry.segments) ? entry.segments : []
177
+ };
178
+ });
179
+ }
164
180
 
165
181
  return out;
166
182
  }
@@ -319,6 +335,25 @@ async function writeSessionIndex(index) {
319
335
  await fs.rename(tempPath, filePath);
320
336
  }
321
337
 
338
+ async function removeSessionIndexEntry(sessionId, removedFileNames = []) {
339
+ try {
340
+ const id = String(sessionId || '').trim();
341
+ const removed = new Set(removedFileNames.map((name) => String(name || '').trim()).filter(Boolean));
342
+ const index = await readSessionIndex();
343
+ if (!index) return false;
344
+ const files = Array.isArray(index.files)
345
+ ? index.files.filter((entry) => !removed.has(String(entry?.name || '')))
346
+ : [];
347
+ const sessions = Array.isArray(index.sessions)
348
+ ? index.sessions.filter((entry) => String(entry?.id || '').trim() !== id)
349
+ : [];
350
+ await writeSessionIndex({ files, sessions });
351
+ return true;
352
+ } catch {
353
+ return false;
354
+ }
355
+ }
356
+
322
357
  async function rebuildSessionIndex(fileMeta = null) {
323
358
  const files = await listSessionFiles();
324
359
  const sessionsById = new Map();
@@ -460,37 +495,51 @@ export async function deleteSession(sessionId) {
460
495
  throw new Error('Invalid session id');
461
496
  }
462
497
 
463
- const files = await listSessionFiles();
464
498
  const targets = new Set();
465
- for (const file of files) {
466
- const fileId = sessionIdFromFileName(path.basename(file));
467
- if (fileId === id) {
468
- targets.add(file);
469
- continue;
470
- }
471
- try {
472
- const parsed = file.endsWith(SESSION_JSONL_EXT) ? await loadLatestJsonlObject(file) : await tryReadJson(file);
473
- if (String(parsed?.id || '').trim() === id) targets.add(file);
474
- } catch {}
475
- }
476
-
477
- let removed = 0;
478
499
  const fallbackTargets = [
479
500
  sessionPathById(id, SESSION_JSONL_EXT),
480
501
  sessionPathById(id, SESSION_LEGACY_EXT)
481
502
  ];
482
- for (const file of [...targets, ...fallbackTargets]) {
503
+ for (const file of fallbackTargets) targets.add(file);
504
+
505
+ let removed = 0;
506
+ const removedFileNames = [];
507
+ for (const file of targets) {
483
508
  try {
484
509
  await fs.unlink(file);
485
510
  removed += 1;
511
+ removedFileNames.push(path.basename(file));
486
512
  } catch (error) {
487
513
  if (error?.code !== 'ENOENT') throw error;
488
514
  }
489
515
  }
516
+
517
+ if (removed === 0) {
518
+ const files = await listSessionFiles();
519
+ for (const file of files) {
520
+ try {
521
+ const parsed = file.endsWith(SESSION_JSONL_EXT) ? await loadLatestJsonlObject(file) : await tryReadJson(file);
522
+ if (String(parsed?.id || '').trim() === id) targets.add(file);
523
+ } catch {}
524
+ }
525
+ for (const file of targets) {
526
+ try {
527
+ await fs.unlink(file);
528
+ removed += 1;
529
+ removedFileNames.push(path.basename(file));
530
+ } catch (error) {
531
+ if (error?.code !== 'ENOENT') throw error;
532
+ }
533
+ }
534
+ }
535
+
490
536
  if (removed > 0) {
491
- try {
492
- await rebuildSessionIndex();
493
- } catch {}
537
+ const updated = await removeSessionIndexEntry(id, removedFileNames);
538
+ if (!updated) {
539
+ try {
540
+ await rebuildSessionIndex();
541
+ } catch {}
542
+ }
494
543
  }
495
544
  return { removed };
496
545
  }