lsd-pi 1.1.2 → 1.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 (183) hide show
  1. package/README.md +2 -1
  2. package/dist/bedrock-auth.d.ts +25 -0
  3. package/dist/bedrock-auth.js +59 -0
  4. package/dist/headless.js +8 -3
  5. package/dist/loader.js +1 -0
  6. package/dist/onboarding-llm.d.ts +37 -0
  7. package/dist/onboarding-llm.js +64 -0
  8. package/dist/onboarding.d.ts +2 -14
  9. package/dist/onboarding.js +146 -71
  10. package/dist/pi-migration.js +1 -0
  11. package/dist/resources/extensions/memory/auto-extract.js +21 -3
  12. package/dist/resources/extensions/memory/dream.js +703 -0
  13. package/dist/resources/extensions/memory/extension-manifest.json +2 -2
  14. package/dist/resources/extensions/memory/index.js +115 -8
  15. package/dist/resources/extensions/slash-commands/extension-manifest.json +10 -10
  16. package/dist/resources/extensions/slash-commands/index.js +0 -4
  17. package/dist/resources/extensions/slash-commands/plan.js +181 -45
  18. package/dist/resources/extensions/subagent/agents.js +14 -1
  19. package/dist/resources/extensions/subagent/configured-model.js +3 -2
  20. package/dist/resources/extensions/subagent/index.js +34 -28
  21. package/dist/resources/extensions/subagent/launch-helpers.js +24 -0
  22. package/dist/resources/extensions/subagent/model-resolution.js +41 -3
  23. package/dist/resources/extensions/usage/extension-manifest.json +11 -0
  24. package/dist/resources/extensions/usage/index.js +346 -0
  25. package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/SKILL.md +6 -6
  26. package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/references/custom-tools.md +1 -1
  27. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extension-lifecycle.md +2 -2
  28. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensioncontext-reference.md +1 -1
  29. package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/references/key-rules-gotchas.md +4 -4
  30. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/packaging-distribution.md +6 -6
  31. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/create-extension.md +3 -3
  32. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/debug-extension.md +5 -5
  33. package/dist/resources/skills/teams-debug/SKILL.md +5 -6
  34. package/dist/resources/skills/teams-document/SKILL.md +1 -2
  35. package/dist/resources/skills/teams-plan/SKILL.md +3 -4
  36. package/dist/resources/skills/teams-run/SKILL.md +3 -4
  37. package/dist/resources/skills/teams-verify/SKILL.md +4 -5
  38. package/dist/startup-model-validation.js +1 -0
  39. package/dist/welcome-screen.js +13 -11
  40. package/dist/wizard.js +12 -0
  41. package/package.json +1 -1
  42. package/packages/pi-ai/dist/models.generated.d.ts +688 -409
  43. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  44. package/packages/pi-ai/dist/models.generated.js +761 -488
  45. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  46. package/packages/pi-ai/scripts/generate-models.ts +40 -18
  47. package/packages/pi-ai/src/models.generated.ts +759 -486
  48. package/packages/pi-coding-agent/dist/cli/config-selector.js +1 -1
  49. package/packages/pi-coding-agent/dist/cli/config-selector.js.map +1 -1
  50. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +1 -2
  51. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/agent-session.js +6 -30
  53. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  55. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/settings-manager.js +44 -1
  57. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +9 -5
  59. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  60. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/skills.js +3 -2
  62. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  64. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  65. package/packages/pi-coding-agent/dist/index.js +1 -1
  66. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  67. package/packages/pi-coding-agent/dist/main.js +1 -1
  68. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -2
  70. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  71. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +15 -12
  73. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +6 -0
  75. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +25 -1
  77. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  78. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -1
  79. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  81. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +10 -0
  82. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  83. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
  84. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  86. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +31 -22
  87. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  88. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +18 -5
  89. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  90. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +139 -20
  91. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  93. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -0
  94. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  95. package/packages/pi-coding-agent/package.json +1 -1
  96. package/packages/pi-coding-agent/src/cli/config-selector.ts +1 -1
  97. package/packages/pi-coding-agent/src/core/agent-session.ts +5 -28
  98. package/packages/pi-coding-agent/src/core/settings-manager.ts +52 -1
  99. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +18 -5
  100. package/packages/pi-coding-agent/src/core/skills.ts +3 -2
  101. package/packages/pi-coding-agent/src/index.ts +1 -1
  102. package/packages/pi-coding-agent/src/main.ts +1 -1
  103. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +2 -2
  104. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +12 -13
  105. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +39 -1
  106. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -1
  107. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +10 -0
  108. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
  109. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +46 -20
  110. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +171 -20
  111. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -0
  112. package/packages/pi-tui/dist/components/editor.d.ts +1 -0
  113. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  114. package/packages/pi-tui/dist/components/editor.js +23 -0
  115. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  116. package/packages/pi-tui/src/components/editor.ts +23 -0
  117. package/pkg/dist/modes/interactive/theme/theme.d.ts +18 -5
  118. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  119. package/pkg/dist/modes/interactive/theme/theme.js +139 -20
  120. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  121. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  122. package/pkg/dist/modes/interactive/theme/themes.js +4 -0
  123. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  124. package/pkg/package.json +1 -1
  125. package/src/resources/extensions/memory/auto-extract.ts +23 -3
  126. package/src/resources/extensions/memory/dream.ts +814 -0
  127. package/src/resources/extensions/memory/extension-manifest.json +2 -2
  128. package/src/resources/extensions/memory/index.ts +134 -13
  129. package/src/resources/extensions/memory/tests/auto-extract.test.ts +10 -2
  130. package/src/resources/extensions/memory/tests/dream.test.ts +142 -0
  131. package/src/resources/extensions/slash-commands/extension-manifest.json +10 -10
  132. package/src/resources/extensions/slash-commands/index.ts +3 -7
  133. package/src/resources/extensions/slash-commands/plan.ts +192 -46
  134. package/src/resources/extensions/subagent/agents.ts +11 -1
  135. package/src/resources/extensions/subagent/configured-model.ts +3 -2
  136. package/src/resources/extensions/subagent/index.ts +38 -30
  137. package/src/resources/extensions/subagent/launch-helpers.ts +30 -0
  138. package/src/resources/extensions/subagent/model-resolution.ts +40 -3
  139. package/src/resources/extensions/usage/extension-manifest.json +11 -0
  140. package/src/resources/extensions/usage/index.ts +441 -0
  141. package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/SKILL.md +6 -6
  142. package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/references/custom-tools.md +1 -1
  143. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extension-lifecycle.md +2 -2
  144. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensioncontext-reference.md +1 -1
  145. package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/references/key-rules-gotchas.md +4 -4
  146. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/packaging-distribution.md +6 -6
  147. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/create-extension.md +3 -3
  148. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/debug-extension.md +5 -5
  149. package/src/resources/skills/teams-debug/SKILL.md +5 -6
  150. package/src/resources/skills/teams-document/SKILL.md +1 -2
  151. package/src/resources/skills/teams-plan/SKILL.md +3 -4
  152. package/src/resources/skills/teams-run/SKILL.md +3 -4
  153. package/src/resources/skills/teams-verify/SKILL.md +4 -5
  154. package/dist/resources/extensions/slash-commands/create-extension.js +0 -264
  155. package/dist/resources/extensions/slash-commands/create-slash-command.js +0 -208
  156. package/src/resources/extensions/slash-commands/create-extension.ts +0 -297
  157. package/src/resources/extensions/slash-commands/create-slash-command.ts +0 -234
  158. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/compaction-session-control.md +0 -0
  159. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-commands.md +0 -0
  160. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-rendering.md +0 -0
  161. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-ui.md +0 -0
  162. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/events-reference.md +0 -0
  163. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensionapi-reference.md +0 -0
  164. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/mode-behavior.md +0 -0
  165. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/model-provider-management.md +0 -0
  166. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/remote-execution-overrides.md +0 -0
  167. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/state-management.md +0 -0
  168. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/system-prompt-modification.md +0 -0
  169. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/add-capability.md +0 -0
  170. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/compaction-session-control.md +0 -0
  171. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-commands.md +0 -0
  172. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-rendering.md +0 -0
  173. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-ui.md +0 -0
  174. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/events-reference.md +0 -0
  175. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensionapi-reference.md +0 -0
  176. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/mode-behavior.md +0 -0
  177. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/model-provider-management.md +0 -0
  178. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/remote-execution-overrides.md +0 -0
  179. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/state-management.md +0 -0
  180. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/system-prompt-modification.md +0 -0
  181. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/templates/extension-skeleton.ts +0 -0
  182. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/templates/stateful-tool-skeleton.ts +0 -0
  183. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/add-capability.md +0 -0
@@ -0,0 +1,814 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { spawn } from 'node:child_process';
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ statSync,
9
+ unlinkSync,
10
+ writeFileSync,
11
+ } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
14
+ import { CONFIG_DIR_NAME, getAgentDir, type ExtensionContext } from '@gsd/pi-coding-agent';
15
+ import { getMemoryDir } from './memory-paths.js';
16
+ import { readBudgetMemoryModel, resolveCliPath } from './auto-extract.js';
17
+
18
+ export interface AutoDreamSettings {
19
+ enabled: boolean;
20
+ minHours: number;
21
+ minSessions: number;
22
+ }
23
+
24
+ export interface DreamStartResult {
25
+ started: boolean;
26
+ status: 'started' | 'skipped' | 'busy';
27
+ message: string;
28
+ }
29
+
30
+ const DEFAULT_AUTO_DREAM_SETTINGS: AutoDreamSettings = {
31
+ enabled: false,
32
+ minHours: 24,
33
+ minSessions: 5,
34
+ };
35
+
36
+ const LOCK_FILE = '.consolidate-lock';
37
+ const AUDIT_FILE = '.last-dream.txt';
38
+ const LOG_FILE = '.last-dream.log';
39
+ const HOLDER_STALE_MS = 60 * 60 * 1000;
40
+ const SESSION_SCAN_INTERVAL_MS = 10 * 60 * 1000;
41
+ const READ_ONLY_BASH_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut)\b/;
42
+
43
+ let lastAutoDreamScanAt = 0;
44
+
45
+ function getProjectSettingsPath(cwd: string): string {
46
+ return join(cwd, CONFIG_DIR_NAME, 'settings.json');
47
+ }
48
+
49
+ function readJsonFile(path: string): Record<string, unknown> {
50
+ try {
51
+ if (!existsSync(path)) return {};
52
+ const raw = readFileSync(path, 'utf-8');
53
+ const parsed = JSON.parse(raw);
54
+ return parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : {};
55
+ } catch {
56
+ return {};
57
+ }
58
+ }
59
+
60
+ function parseAutoDreamSettings(source: Record<string, unknown>): Partial<AutoDreamSettings> {
61
+ const memory = source.memory;
62
+ if (!memory || typeof memory !== 'object') return {};
63
+
64
+ const settings = memory as {
65
+ autoDream?: unknown;
66
+ autoDreamMinHours?: unknown;
67
+ autoDreamMinSessions?: unknown;
68
+ };
69
+
70
+ return {
71
+ enabled:
72
+ typeof settings.autoDream === 'boolean'
73
+ ? settings.autoDream
74
+ : DEFAULT_AUTO_DREAM_SETTINGS.enabled,
75
+ minHours:
76
+ typeof settings.autoDreamMinHours === 'number' && Number.isFinite(settings.autoDreamMinHours)
77
+ ? Math.max(1, settings.autoDreamMinHours)
78
+ : DEFAULT_AUTO_DREAM_SETTINGS.minHours,
79
+ minSessions:
80
+ typeof settings.autoDreamMinSessions === 'number' && Number.isFinite(settings.autoDreamMinSessions)
81
+ ? Math.max(1, Math.floor(settings.autoDreamMinSessions))
82
+ : DEFAULT_AUTO_DREAM_SETTINGS.minSessions,
83
+ };
84
+ }
85
+
86
+ export function readAutoDreamSettings(cwd: string): AutoDreamSettings {
87
+ const global = parseAutoDreamSettings(readJsonFile(join(getAgentDir(), 'settings.json')));
88
+ const project = parseAutoDreamSettings(readJsonFile(getProjectSettingsPath(cwd)));
89
+
90
+ return {
91
+ ...DEFAULT_AUTO_DREAM_SETTINGS,
92
+ ...global,
93
+ ...project,
94
+ };
95
+ }
96
+
97
+ export function setProjectAutoDreamEnabled(cwd: string, enabled: boolean): AutoDreamSettings {
98
+ const settingsPath = getProjectSettingsPath(cwd);
99
+ const next = readJsonFile(settingsPath);
100
+ const memory = next.memory && typeof next.memory === 'object'
101
+ ? { ...(next.memory as Record<string, unknown>) }
102
+ : {};
103
+
104
+ memory.autoDream = enabled;
105
+ next.memory = memory;
106
+
107
+ mkdirSync(dirname(settingsPath), { recursive: true });
108
+ writeFileSync(settingsPath, JSON.stringify(next, null, 2) + '\n', 'utf-8');
109
+
110
+ return readAutoDreamSettings(cwd);
111
+ }
112
+
113
+ function getLockPath(memoryDir: string): string {
114
+ return join(memoryDir, LOCK_FILE);
115
+ }
116
+
117
+ function getAuditPath(memoryDir: string): string {
118
+ return join(memoryDir, AUDIT_FILE);
119
+ }
120
+
121
+ function getLogPath(memoryDir: string): string {
122
+ return join(memoryDir, LOG_FILE);
123
+ }
124
+
125
+ export function readLastConsolidatedAt(memoryDir: string): number {
126
+ try {
127
+ return statSync(getLockPath(memoryDir)).mtimeMs;
128
+ } catch {
129
+ return 0;
130
+ }
131
+ }
132
+
133
+ function isConsolidationInProgress(memoryDir: string): boolean {
134
+ const lockPath = getLockPath(memoryDir);
135
+ try {
136
+ const stat = statSync(lockPath);
137
+ if (Date.now() - stat.mtimeMs > HOLDER_STALE_MS) return false;
138
+ const pid = Number.parseInt(readFileSync(lockPath, 'utf-8').trim(), 10);
139
+ if (!Number.isFinite(pid) || pid <= 0) return false;
140
+ process.kill(pid, 0);
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ export function tryAcquireConsolidationLock(memoryDir: string): number | null {
148
+ mkdirSync(memoryDir, { recursive: true });
149
+ const lockPath = getLockPath(memoryDir);
150
+ let priorMtime = 0;
151
+
152
+ try {
153
+ priorMtime = statSync(lockPath).mtimeMs;
154
+ } catch {
155
+ priorMtime = 0;
156
+ }
157
+
158
+ if (isConsolidationInProgress(memoryDir)) {
159
+ return null;
160
+ }
161
+
162
+ writeFileSync(lockPath, String(process.pid), 'utf-8');
163
+ return priorMtime;
164
+ }
165
+
166
+ function parseAuditFile(path: string): Record<string, string> {
167
+ try {
168
+ const raw = readFileSync(path, 'utf-8');
169
+ const result: Record<string, string> = {};
170
+ for (const line of raw.split(/\r?\n/)) {
171
+ const idx = line.indexOf(':');
172
+ if (idx === -1) continue;
173
+ const key = line.slice(0, idx).trim();
174
+ const value = line.slice(idx + 1).trim();
175
+ if (key) result[key] = value;
176
+ }
177
+ return result;
178
+ } catch {
179
+ return {};
180
+ }
181
+ }
182
+
183
+ function listSessionsTouchedSince(
184
+ sessionDir: string,
185
+ sinceMs: number,
186
+ currentSessionFile?: string,
187
+ ): string[] {
188
+ try {
189
+ const files = readdirSync(sessionDir)
190
+ .filter((name) => name.endsWith('.jsonl'))
191
+ .map((name) => join(sessionDir, name));
192
+
193
+ return files.filter((file) => {
194
+ if (currentSessionFile && resolve(file) === resolve(currentSessionFile)) return false;
195
+ try {
196
+ return statSync(file).mtimeMs > sinceMs;
197
+ } catch {
198
+ return false;
199
+ }
200
+ });
201
+ } catch {
202
+ return [];
203
+ }
204
+ }
205
+
206
+ function isPathInsideDir(targetPath: string, dir: string): boolean {
207
+ const resolvedDir = resolve(dir);
208
+ const resolvedTarget = resolve(targetPath);
209
+ return resolvedTarget === resolvedDir || resolvedTarget.startsWith(resolvedDir + '/');
210
+ }
211
+
212
+ export function listBrokenMemoryIndexEntries(memoryDir: string): string[] {
213
+ const entrypoint = join(memoryDir, 'MEMORY.md');
214
+ try {
215
+ const raw = readFileSync(entrypoint, 'utf-8');
216
+ const broken = new Set<string>();
217
+ const linkRe = /\[[^\]]+\]\(([^)]+)\)/g;
218
+
219
+ for (const match of raw.matchAll(linkRe)) {
220
+ const href = match[1]?.trim();
221
+ if (!href || href.startsWith('#') || /^[a-z][a-z0-9+.-]*:/i.test(href)) continue;
222
+
223
+ const target = isAbsolute(href) ? resolve(href) : resolve(memoryDir, href);
224
+ if (!isPathInsideDir(target, memoryDir)) {
225
+ broken.add(href);
226
+ continue;
227
+ }
228
+ if (!existsSync(target)) {
229
+ broken.add(href);
230
+ }
231
+ }
232
+
233
+ return [...broken].sort();
234
+ } catch {
235
+ return [];
236
+ }
237
+ }
238
+
239
+ export function pruneBrokenMemoryIndexEntries(memoryDir: string): string[] {
240
+ const entrypoint = join(memoryDir, 'MEMORY.md');
241
+ try {
242
+ const raw = readFileSync(entrypoint, 'utf-8');
243
+ const broken = new Set(listBrokenMemoryIndexEntries(memoryDir));
244
+ if (broken.size === 0) return [];
245
+
246
+ const keptLines = raw
247
+ .split(/\r?\n/)
248
+ .filter((line) => {
249
+ const match = line.match(/\[[^\]]+\]\(([^)]+)\)/);
250
+ if (!match) return true;
251
+ const href = match[1]?.trim();
252
+ return href ? !broken.has(href) : true;
253
+ });
254
+
255
+ const next = keptLines.join('\n').replace(/\n*$/, '\n');
256
+ writeFileSync(entrypoint, next, 'utf-8');
257
+ return [...broken].sort();
258
+ } catch {
259
+ return [];
260
+ }
261
+ }
262
+
263
+ export function buildConsolidationPrompt(memoryDir: string, sessionDir: string): string {
264
+ return `# Dream: Memory Consolidation
265
+
266
+ You are performing a dream — a reflective pass over existing memory files. Improve the memory store so future sessions can orient quickly and trust what they read.
267
+
268
+ Memory directory: ${memoryDir}
269
+ Session transcripts: ${sessionDir}
270
+
271
+ The memory system prompt already defines the memory file format, allowed memory types, and what not to save. Follow that as the source of truth.
272
+
273
+ ## Phase 1 — Orient
274
+ - List the memory directory and inspect MEMORY.md first
275
+ - Skim existing topic files before creating anything new
276
+ - Prefer improving existing files over creating near-duplicates
277
+ - Validate every MEMORY.md link and repair or remove broken pointers
278
+
279
+ ## Phase 2 — Gather recent signal
280
+ - Review recent session transcripts only as needed
281
+ - Search transcripts narrowly for concrete terms instead of reading them exhaustively
282
+ - Look for drift, contradictions, stale wording, duplicate memories, and stale index entries
283
+
284
+ ## Phase 3 — Consolidate
285
+ - Merge duplicate or overlapping memories
286
+ - Update existing memories with clearer and more durable wording
287
+ - Convert relative dates like “yesterday” into absolute dates when they matter
288
+ - Remove contradicted, stale, or superseded facts
289
+ - Keep only durable information that will help in future conversations
290
+
291
+ ## Phase 4 — Prune and index
292
+ - Keep MEMORY.md as a concise index, not a content dump
293
+ - Each MEMORY.md entry should stay to one short line
294
+ - Remove pointers to stale or deleted memories
295
+ - Resolve contradictions between files instead of leaving both versions in place
296
+
297
+ ## Tooling constraints
298
+ - When using write or edit, always target absolute paths inside ${memoryDir}
299
+ - Do not use relative write/edit paths like MEMORY.md or user_identity.md because they resolve against the repo cwd, not the memory directory
300
+
301
+ ## Guardrails
302
+ - Only modify files inside the memory directory
303
+ - Do not edit project source files
304
+ - Use bash only for read-only inspection
305
+ - If the memory store is already in good shape, make no changes and say so
306
+
307
+ Return a short summary of what you consolidated, updated, pruned, or left unchanged.`;
308
+ }
309
+
310
+ function writeAudit(
311
+ memoryDir: string,
312
+ fields: Record<string, string | number | boolean | undefined>,
313
+ ): void {
314
+ const lines = Object.entries(fields)
315
+ .filter(([, value]) => value !== undefined)
316
+ .map(([key, value]) => `${key}: ${String(value)}`);
317
+ writeFileSync(getAuditPath(memoryDir), lines.join('\n') + '\n', 'utf-8');
318
+ }
319
+
320
+ function startDetachedDreamProcess(
321
+ cwd: string,
322
+ memoryDir: string,
323
+ sessionDir: string,
324
+ trigger: 'manual' | 'auto',
325
+ priorMtime: number,
326
+ sessionCount: number,
327
+ ): DreamStartResult {
328
+ const cliPath = resolveCliPath();
329
+ const budgetModel = readBudgetMemoryModel();
330
+ const auditPath = getAuditPath(memoryDir);
331
+ const logPath = getLogPath(memoryDir);
332
+
333
+ if (!cliPath) {
334
+ writeAudit(memoryDir, {
335
+ timestamp: new Date().toISOString(),
336
+ status: 'skipped',
337
+ trigger,
338
+ reason: 'cli_path_not_found',
339
+ cwd,
340
+ memoryDir,
341
+ sessionDir,
342
+ sessionsSinceLastConsolidation: sessionCount,
343
+ });
344
+ return {
345
+ started: false,
346
+ status: 'skipped',
347
+ message: 'Dream skipped because the CLI path could not be resolved.',
348
+ };
349
+ }
350
+
351
+ const prompt = buildConsolidationPrompt(memoryDir, sessionDir);
352
+ const tmpPromptPath = join(tmpdir(), `lsd-memory-dream-${randomUUID()}.md`);
353
+ writeFileSync(tmpPromptPath, prompt, 'utf-8');
354
+ writeAudit(memoryDir, {
355
+ timestamp: new Date().toISOString(),
356
+ status: 'spawning',
357
+ trigger,
358
+ cwd,
359
+ memoryDir,
360
+ sessionDir,
361
+ sessionsSinceLastConsolidation: sessionCount,
362
+ cliPath,
363
+ model: budgetModel ?? 'default',
364
+ logPath,
365
+ });
366
+
367
+ const instruction = 'Perform a dream consolidation pass over the memory directory. Only update memory files and MEMORY.md if they genuinely need consolidation.';
368
+
369
+ const helperScript = String.raw`
370
+ const { spawn } = require('node:child_process');
371
+ const {
372
+ appendFileSync,
373
+ existsSync,
374
+ readdirSync,
375
+ readFileSync,
376
+ statSync,
377
+ unlinkSync,
378
+ utimesSync,
379
+ writeFileSync,
380
+ } = require('node:fs');
381
+ const { join, delimiter } = require('node:path');
382
+
383
+ const [cliPath, cwd, tmpPromptPath, auditPath, logPath, memoryDir, sessionDir, instruction, model, trigger, priorMtime, sessionCount] = process.argv.slice(1);
384
+ let finalized = false;
385
+ let pendingLogText = '';
386
+
387
+ function newestMemoryMtime(dir) {
388
+ try {
389
+ const names = readdirSync(dir, { recursive: true });
390
+ let newest = 0;
391
+ for (const name of names) {
392
+ const parts = String(name).split(/[\\/]/).filter(Boolean);
393
+ if (parts.some((part) => part.startsWith('.'))) continue;
394
+ const full = join(dir, String(name));
395
+ try {
396
+ const stat = statSync(full);
397
+ if (stat.isFile() && stat.mtimeMs > newest) newest = stat.mtimeMs;
398
+ } catch {}
399
+ }
400
+ return newest;
401
+ } catch {
402
+ return 0;
403
+ }
404
+ }
405
+
406
+ function isPathInsideDir(targetPath, dir) {
407
+ try {
408
+ const resolvedDir = require('node:path').resolve(dir);
409
+ const resolvedTarget = require('node:path').resolve(targetPath);
410
+ return resolvedTarget === resolvedDir || resolvedTarget.startsWith(resolvedDir + '/');
411
+ } catch {
412
+ return false;
413
+ }
414
+ }
415
+
416
+ function listBrokenMemoryRefs(dir) {
417
+ try {
418
+ const entrypoint = join(dir, 'MEMORY.md');
419
+ const raw = readFileSync(entrypoint, 'utf-8');
420
+ const broken = new Set();
421
+ const linkRe = /\[[^\]]+\]\(([^)]+)\)/g;
422
+ for (const match of raw.matchAll(linkRe)) {
423
+ const href = String(match[1] || '').trim();
424
+ if (!href || href.startsWith('#') || /^[a-z][a-z0-9+.-]*:/i.test(href)) continue;
425
+ const target = require('node:path').isAbsolute(href)
426
+ ? require('node:path').resolve(href)
427
+ : require('node:path').resolve(dir, href);
428
+ if (!isPathInsideDir(target, dir) || !existsSync(target)) broken.add(href);
429
+ }
430
+ return Array.from(broken).sort();
431
+ } catch {
432
+ return [];
433
+ }
434
+ }
435
+
436
+ function pruneBrokenMemoryRefs(dir) {
437
+ try {
438
+ const entrypoint = join(dir, 'MEMORY.md');
439
+ const raw = readFileSync(entrypoint, 'utf-8');
440
+ const broken = new Set(listBrokenMemoryRefs(dir));
441
+ if (broken.size === 0) return [];
442
+ const kept = raw
443
+ .split(/\r?\n/)
444
+ .filter((line) => {
445
+ const match = line.match(/\[[^\]]+\]\(([^)]+)\)/);
446
+ if (!match) return true;
447
+ const href = String(match[1] || '').trim();
448
+ return href ? !broken.has(href) : true;
449
+ });
450
+ writeFileSync(entrypoint, kept.join('\n').replace(/\n*$/, '\n'), 'utf-8');
451
+ return Array.from(broken).sort();
452
+ } catch {
453
+ return [];
454
+ }
455
+ }
456
+
457
+ function writeAudit(status, extra = []) {
458
+ try {
459
+ writeFileSync(auditPath, [
460
+ 'timestamp: ' + new Date().toISOString(),
461
+ 'status: ' + status,
462
+ 'trigger: ' + trigger,
463
+ 'cwd: ' + cwd,
464
+ 'memoryDir: ' + memoryDir,
465
+ 'sessionDir: ' + sessionDir,
466
+ 'sessionsSinceLastConsolidation: ' + sessionCount,
467
+ 'cliPath: ' + cliPath,
468
+ 'model: ' + (model || 'default'),
469
+ 'logPath: ' + logPath,
470
+ ...extra,
471
+ ].join('\n') + '\n', 'utf-8');
472
+ } catch {}
473
+ }
474
+
475
+ function rollbackLock() {
476
+ const lockPath = join(memoryDir, '.consolidate-lock');
477
+ try {
478
+ const prior = Number(priorMtime);
479
+ if (!Number.isFinite(prior) || prior <= 0) {
480
+ if (existsSync(lockPath)) unlinkSync(lockPath);
481
+ return;
482
+ }
483
+ writeFileSync(lockPath, '', 'utf-8');
484
+ const t = prior / 1000;
485
+ utimesSync(lockPath, t, t);
486
+ } catch {}
487
+ }
488
+
489
+ function flushLogText(text, force = false) {
490
+ pendingLogText += text;
491
+ const parts = pendingLogText.split(/\r?\n/);
492
+ pendingLogText = force ? '' : (parts.pop() ?? '');
493
+ const kept = parts.filter((line) => line.trim());
494
+ if (kept.length > 0) appendFileSync(logPath, kept.join('\n') + '\n');
495
+ }
496
+
497
+ function appendLog(chunk) {
498
+ try {
499
+ const text = Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk);
500
+ flushLogText(text, false);
501
+ } catch {}
502
+ }
503
+
504
+ function finalize(status, code, signal, completionReason) {
505
+ if (finalized) return;
506
+ finalized = true;
507
+ flushLogText('', true);
508
+ const beforeBrokenRefs = listBrokenMemoryRefs(memoryDir);
509
+ const prunedRefs = beforeBrokenRefs.length > 0 ? pruneBrokenMemoryRefs(memoryDir) : [];
510
+ const afterMtime = newestMemoryMtime(memoryDir);
511
+ const brokenRefs = listBrokenMemoryRefs(memoryDir);
512
+ const result = brokenRefs.length > 0
513
+ ? 'broken_memory_index'
514
+ : afterMtime > beforeMtime
515
+ ? 'updated_memory'
516
+ : 'no_memory_changes';
517
+ if (status !== 'finished') rollbackLock();
518
+ writeAudit(status, [
519
+ 'exitCode: ' + String(code),
520
+ 'signal: ' + String(signal),
521
+ 'result: ' + result,
522
+ 'brokenRefsPrunedCount: ' + String(prunedRefs.length),
523
+ ...(prunedRefs.length > 0 ? ['brokenRefsPruned: ' + prunedRefs.join(', ')] : []),
524
+ 'brokenRefsCount: ' + String(brokenRefs.length),
525
+ ...(brokenRefs.length > 0 ? ['brokenRefs: ' + brokenRefs.join(', ')] : []),
526
+ 'completionReason: ' + completionReason,
527
+ ]);
528
+ try { unlinkSync(tmpPromptPath); } catch {}
529
+ }
530
+
531
+ const beforeMtime = newestMemoryMtime(memoryDir);
532
+ const lockPath = join(memoryDir, '.consolidate-lock');
533
+ try { writeFileSync(lockPath, String(process.pid), 'utf-8'); } catch {}
534
+ writeFileSync(logPath, '', 'utf-8');
535
+ writeAudit('running');
536
+
537
+ const childArgs = [cliPath, 'headless'];
538
+ const bundledPaths = Array.from(
539
+ new Set(
540
+ [process.env.GSD_BUNDLED_EXTENSION_PATHS, process.env.LSD_BUNDLED_EXTENSION_PATHS]
541
+ .filter(Boolean)
542
+ .flatMap((value) => String(value).split(delimiter).map((entry) => entry.trim()).filter(Boolean)),
543
+ ),
544
+ );
545
+ for (const extensionPath of bundledPaths) childArgs.push('--extension', extensionPath);
546
+ if (model) childArgs.push('--model', model);
547
+ childArgs.push('--bare', '--context', tmpPromptPath, '--context-text', instruction);
548
+
549
+ const child = spawn(process.execPath, childArgs, {
550
+ cwd,
551
+ env: { ...process.env, LSD_MEMORY_DREAM: '1' },
552
+ stdio: ['ignore', 'pipe', 'pipe'],
553
+ });
554
+
555
+ const hardTimeout = setTimeout(() => {
556
+ finalize('failed', null, 'timeout', 'timeout');
557
+ try { child.kill('SIGTERM'); } catch {}
558
+ setTimeout(() => { try { child.kill('SIGKILL'); } catch {} }, 2000).unref();
559
+ }, 180000);
560
+ hardTimeout.unref();
561
+
562
+ child.stdout.on('data', appendLog);
563
+ child.stderr.on('data', appendLog);
564
+ child.on('error', (err) => {
565
+ appendLog(String(err && err.stack ? err.stack : err) + '\n');
566
+ clearTimeout(hardTimeout);
567
+ finalize('failed', null, 'spawn_error', String(err && err.message ? err.message : err));
568
+ });
569
+ child.on('exit', (code, signal) => {
570
+ clearTimeout(hardTimeout);
571
+ finalize(code === 0 ? 'finished' : 'failed', code, signal, 'child_exit');
572
+ });
573
+ `;
574
+
575
+ const proc = spawn(
576
+ process.execPath,
577
+ [
578
+ '-e',
579
+ helperScript,
580
+ cliPath,
581
+ cwd,
582
+ tmpPromptPath,
583
+ auditPath,
584
+ logPath,
585
+ memoryDir,
586
+ sessionDir,
587
+ instruction,
588
+ budgetModel ?? '',
589
+ trigger,
590
+ String(priorMtime),
591
+ String(sessionCount),
592
+ ],
593
+ {
594
+ cwd,
595
+ detached: true,
596
+ stdio: 'ignore',
597
+ env: process.env,
598
+ },
599
+ );
600
+ proc.unref();
601
+
602
+ writeAudit(memoryDir, {
603
+ timestamp: new Date().toISOString(),
604
+ status: 'spawned',
605
+ trigger,
606
+ pid: proc.pid ?? 'unknown',
607
+ cwd,
608
+ memoryDir,
609
+ sessionDir,
610
+ sessionsSinceLastConsolidation: sessionCount,
611
+ cliPath,
612
+ model: budgetModel ?? 'default',
613
+ logPath,
614
+ });
615
+
616
+ setTimeout(() => {
617
+ try {
618
+ unlinkSync(tmpPromptPath);
619
+ } catch {
620
+ // Best-effort cleanup only.
621
+ }
622
+ }, 180_000).unref();
623
+
624
+ return {
625
+ started: true,
626
+ status: 'started',
627
+ message:
628
+ trigger === 'auto'
629
+ ? `Auto-dream started in the background (${sessionCount} sessions since the last consolidation).`
630
+ : 'Dream started in the background.',
631
+ };
632
+ }
633
+
634
+ export function startDream(
635
+ ctx: Pick<ExtensionContext, 'cwd' | 'sessionManager'>,
636
+ options?: {
637
+ trigger?: 'manual' | 'auto';
638
+ force?: boolean;
639
+ sessionCountHint?: number;
640
+ },
641
+ ): DreamStartResult {
642
+ if (process.env.LSD_MEMORY_EXTRACT === '1' || process.env.LSD_MEMORY_DREAM === '1') {
643
+ return {
644
+ started: false,
645
+ status: 'skipped',
646
+ message: 'Dream is unavailable from inside a maintenance worker.',
647
+ };
648
+ }
649
+
650
+ const trigger = options?.trigger ?? 'manual';
651
+ const memoryDir = getMemoryDir(ctx.cwd);
652
+ const sessionDir = ctx.sessionManager.getSessionDir();
653
+ const currentSessionFile = ctx.sessionManager.getSessionFile();
654
+ const sessionCount =
655
+ options?.sessionCountHint ??
656
+ listSessionsTouchedSince(sessionDir, readLastConsolidatedAt(memoryDir), currentSessionFile).length;
657
+
658
+ const priorMtime = tryAcquireConsolidationLock(memoryDir);
659
+ if (priorMtime === null) {
660
+ return {
661
+ started: false,
662
+ status: 'busy',
663
+ message: 'A dream consolidation is already running.',
664
+ };
665
+ }
666
+
667
+ return startDetachedDreamProcess(ctx.cwd, memoryDir, sessionDir, trigger, priorMtime, sessionCount);
668
+ }
669
+
670
+ export function maybeStartAutoDream(
671
+ ctx: Pick<ExtensionContext, 'cwd' | 'sessionManager'>,
672
+ ): DreamStartResult {
673
+ if (process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY) {
674
+ return {
675
+ started: false,
676
+ status: 'skipped',
677
+ message: 'Auto-memory is disabled.',
678
+ };
679
+ }
680
+
681
+ if (process.env.LSD_MEMORY_EXTRACT === '1' || process.env.LSD_MEMORY_DREAM === '1') {
682
+ return {
683
+ started: false,
684
+ status: 'skipped',
685
+ message: 'Maintenance workers do not schedule auto-dream.',
686
+ };
687
+ }
688
+
689
+ const settings = readAutoDreamSettings(ctx.cwd);
690
+ if (!settings.enabled) {
691
+ return {
692
+ started: false,
693
+ status: 'skipped',
694
+ message: 'Auto-dream is disabled.',
695
+ };
696
+ }
697
+
698
+ const memoryDir = getMemoryDir(ctx.cwd);
699
+ const lastAt = readLastConsolidatedAt(memoryDir);
700
+ const hoursSince = lastAt > 0 ? (Date.now() - lastAt) / 3_600_000 : Number.POSITIVE_INFINITY;
701
+ if (hoursSince < settings.minHours) {
702
+ return {
703
+ started: false,
704
+ status: 'skipped',
705
+ message: `Only ${hoursSince.toFixed(1)}h since the last dream; need ${settings.minHours}h.`,
706
+ };
707
+ }
708
+
709
+ const sinceLastScan = Date.now() - lastAutoDreamScanAt;
710
+ if (sinceLastScan < SESSION_SCAN_INTERVAL_MS) {
711
+ return {
712
+ started: false,
713
+ status: 'skipped',
714
+ message: 'Auto-dream scan throttled.',
715
+ };
716
+ }
717
+ lastAutoDreamScanAt = Date.now();
718
+
719
+ const sessionFiles = listSessionsTouchedSince(
720
+ ctx.sessionManager.getSessionDir(),
721
+ lastAt,
722
+ ctx.sessionManager.getSessionFile(),
723
+ );
724
+ if (sessionFiles.length < settings.minSessions) {
725
+ return {
726
+ started: false,
727
+ status: 'skipped',
728
+ message: `Only ${sessionFiles.length} sessions since the last dream; need ${settings.minSessions}.`,
729
+ };
730
+ }
731
+
732
+ return startDream(ctx, {
733
+ trigger: 'auto',
734
+ sessionCountHint: sessionFiles.length,
735
+ });
736
+ }
737
+
738
+ export function formatDreamStatus(
739
+ ctx: Pick<ExtensionContext, 'cwd' | 'sessionManager'>,
740
+ ): string {
741
+ const memoryDir = getMemoryDir(ctx.cwd);
742
+ const settings = readAutoDreamSettings(ctx.cwd);
743
+ const audit = parseAuditFile(getAuditPath(memoryDir));
744
+ const lastAt = readLastConsolidatedAt(memoryDir);
745
+ const sessionsSince = listSessionsTouchedSince(
746
+ ctx.sessionManager.getSessionDir(),
747
+ lastAt,
748
+ ctx.sessionManager.getSessionFile(),
749
+ ).length;
750
+
751
+ const lines = [
752
+ 'Dream Status',
753
+ '',
754
+ `- Memory directory: ${memoryDir}`,
755
+ `- Auto-dream enabled: ${settings.enabled ? 'yes' : 'no'}`,
756
+ `- Thresholds: ${settings.minHours}h / ${settings.minSessions} sessions`,
757
+ `- Last consolidated at: ${lastAt > 0 ? new Date(lastAt).toISOString() : 'never'}`,
758
+ `- Sessions since last consolidation: ${sessionsSince}`,
759
+ `- Dream currently running: ${isConsolidationInProgress(memoryDir) ? 'yes' : 'no'}`,
760
+ ];
761
+
762
+ if (Object.keys(audit).length > 0) {
763
+ lines.push('', 'Last recorded dream run:');
764
+ for (const key of [
765
+ 'timestamp',
766
+ 'status',
767
+ 'trigger',
768
+ 'result',
769
+ 'brokenRefsCount',
770
+ 'brokenRefs',
771
+ 'completionReason',
772
+ 'pid',
773
+ 'model',
774
+ 'logPath',
775
+ ]) {
776
+ if (audit[key]) {
777
+ lines.push(`- ${key}: ${audit[key]}`);
778
+ }
779
+ }
780
+ } else {
781
+ lines.push('', '- No dream audit file found yet.');
782
+ }
783
+
784
+ return lines.join('\n');
785
+ }
786
+
787
+ export function isMaintenanceModeToolAllowed(toolName: string, input?: { path?: string; command?: string }, cwd?: string): boolean {
788
+ if (!(process.env.LSD_MEMORY_EXTRACT === '1' || process.env.LSD_MEMORY_DREAM === '1')) {
789
+ return true;
790
+ }
791
+
792
+ if (toolName === 'read' || toolName === 'grep' || toolName === 'find' || toolName === 'ls') {
793
+ return true;
794
+ }
795
+
796
+ if ((toolName === 'write' || toolName === 'edit') && input?.path && cwd) {
797
+ const targetPath = isAbsolute(input.path) ? resolve(input.path) : resolve(cwd, input.path);
798
+ return isPathInsideDir(targetPath, getMemoryDir(cwd));
799
+ }
800
+
801
+ if (toolName === 'bash' && input?.command) {
802
+ return READ_ONLY_BASH_RE.test(input.command);
803
+ }
804
+
805
+ return false;
806
+ }
807
+
808
+ export const __testing = {
809
+ listBrokenMemoryIndexEntries,
810
+ pruneBrokenMemoryIndexEntries,
811
+ parseAutoDreamSettings,
812
+ listSessionsTouchedSince,
813
+ readJsonFile,
814
+ };