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