lsd-pi 1.1.4 → 1.1.6
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/headless-ui.js +2 -0
- package/dist/onboarding.js +11 -8
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/await-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/cancel-job-tool.js +7 -0
- package/dist/resources/extensions/cache-timer/index.js +5 -0
- package/dist/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/dist/resources/extensions/codex-rotate/README.md +9 -3
- package/dist/resources/extensions/codex-rotate/commands.js +15 -8
- package/dist/resources/extensions/codex-rotate/index.js +17 -8
- package/dist/resources/extensions/memory/auto-extract.js +196 -80
- package/dist/resources/extensions/memory/dream.js +86 -19
- package/dist/resources/extensions/shared/rtk.js +89 -87
- package/dist/resources/extensions/subagent/index.js +33 -7
- package/dist/startup-model-validation.js +12 -2
- package/dist/update-check.js +2 -2
- package/dist/update-cmd.js +3 -3
- package/dist/welcome-screen.js +43 -14
- package/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +43 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts +48 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js +173 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +16 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -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 +18 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.js +2 -2
- package/packages/pi-coding-agent/dist/core/tool-approval.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +23 -2
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +50 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js +289 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +36 -22
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +3 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +23 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts +39 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +182 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -0
- 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 +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -77
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +2 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +4 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +11 -0
- 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 +49 -13
- 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.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +27 -0
- 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 +251 -39
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts +10 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts +7 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js.map +1 -0
- package/packages/pi-coding-agent/package.json +9 -4
- package/packages/pi-coding-agent/src/core/agent-session.clear-queue.test.ts +50 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +50 -4
- package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/pty-executor.ts +229 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +16 -3
- package/packages/pi-coding-agent/src/core/settings-manager.ts +27 -0
- package/packages/pi-coding-agent/src/core/tool-approval.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/index.ts +35 -2
- package/packages/pi-coding-agent/src/core/tools/pty.ts +354 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +37 -24
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +22 -70
- package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +224 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +45 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +104 -81
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +5 -19
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +55 -13
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +296 -48
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +2 -2
- package/packages/pi-coding-agent/src/utils/terminal-screen.ts +77 -0
- package/packages/pi-coding-agent/src/utils/terminal-serializer.ts +72 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js +105 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +4 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +57 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/loader.d.ts +26 -6
- package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/loader.js +178 -18
- package/packages/pi-tui/dist/components/loader.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +65 -3
- package/packages/pi-tui/src/components/loader.ts +196 -19
- package/pkg/dist/modes/interactive/theme/themes.js +2 -2
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/cancel-job-tool.ts +8 -0
- package/src/resources/extensions/cache-timer/index.ts +102 -96
- package/src/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/src/resources/extensions/codex-rotate/README.md +9 -3
- package/src/resources/extensions/codex-rotate/commands.ts +335 -329
- package/src/resources/extensions/codex-rotate/index.ts +85 -75
- package/src/resources/extensions/memory/auto-extract.ts +330 -204
- package/src/resources/extensions/memory/dream.ts +88 -21
- package/src/resources/extensions/memory/tests/auto-extract.test.ts +200 -144
- package/src/resources/extensions/shared/rtk.js +112 -0
- package/src/resources/extensions/subagent/index.ts +35 -6
|
@@ -17,9 +17,74 @@ import { scanMemoryFiles, formatMemoryManifest } from './memory-scan.js';
|
|
|
17
17
|
import { normalizeSubagentModel } from '../subagent/model-resolution.js';
|
|
18
18
|
|
|
19
19
|
const AUTO_EXTRACT_ANSI_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/g;
|
|
20
|
+
const AUTO_EXTRACT_CACHE_TIMER_RE = /^\[phase\]\s+cache-timer(?:\s*:\s*.*)?\s*$/i;
|
|
21
|
+
const AUTO_EXTRACT_SESSION_ENDED_RE = /^\[agent\]\s+Session ended/;
|
|
22
|
+
const AUTO_EXTRACT_HEADLESS_STATUS_RE = /^\[headless\]\s+Status:\s+(\w+)\s*$/i;
|
|
20
23
|
|
|
21
24
|
export function stripAnsiForAutoExtractLog(text: string): string {
|
|
22
|
-
|
|
25
|
+
return text.replace(AUTO_EXTRACT_ANSI_PATTERN, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function classifyAutoExtractLogLine(rawLine: string): {
|
|
29
|
+
stripped: string;
|
|
30
|
+
keep: boolean;
|
|
31
|
+
completion: 'none' | 'success' | 'failure';
|
|
32
|
+
completionReason: string | null;
|
|
33
|
+
} {
|
|
34
|
+
const stripped = stripAnsiForAutoExtractLog(rawLine).trim();
|
|
35
|
+
if (!stripped) {
|
|
36
|
+
return {
|
|
37
|
+
stripped,
|
|
38
|
+
keep: false,
|
|
39
|
+
completion: 'none',
|
|
40
|
+
completionReason: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (AUTO_EXTRACT_CACHE_TIMER_RE.test(stripped)) {
|
|
45
|
+
return {
|
|
46
|
+
stripped,
|
|
47
|
+
keep: false,
|
|
48
|
+
completion: 'none',
|
|
49
|
+
completionReason: null,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (AUTO_EXTRACT_SESSION_ENDED_RE.test(stripped)) {
|
|
54
|
+
return {
|
|
55
|
+
stripped,
|
|
56
|
+
keep: true,
|
|
57
|
+
completion: 'success',
|
|
58
|
+
completionReason: 'session_end_detected',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const headlessStatusMatch = stripped.match(AUTO_EXTRACT_HEADLESS_STATUS_RE);
|
|
63
|
+
if (headlessStatusMatch) {
|
|
64
|
+
const status = headlessStatusMatch[1].toLowerCase();
|
|
65
|
+
if (status === 'complete') {
|
|
66
|
+
return {
|
|
67
|
+
stripped,
|
|
68
|
+
keep: true,
|
|
69
|
+
completion: 'success',
|
|
70
|
+
completionReason: 'headless_status_complete',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
stripped,
|
|
76
|
+
keep: true,
|
|
77
|
+
completion: 'failure',
|
|
78
|
+
completionReason: `headless_status_${status}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
stripped,
|
|
84
|
+
keep: true,
|
|
85
|
+
completion: 'none',
|
|
86
|
+
completionReason: null,
|
|
87
|
+
};
|
|
23
88
|
}
|
|
24
89
|
|
|
25
90
|
/**
|
|
@@ -29,40 +94,40 @@ export function stripAnsiForAutoExtractLog(text: string): string {
|
|
|
29
94
|
* Returns an empty string only when there is no user-authored content.
|
|
30
95
|
*/
|
|
31
96
|
export function buildTranscriptSummary(entries: any[]): string {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
for (const entry of entries) {
|
|
36
|
-
if (entry.type !== 'message') continue;
|
|
37
|
-
|
|
38
|
-
const role = entry.message?.role;
|
|
39
|
-
if (role !== 'user' && role !== 'assistant') continue;
|
|
40
|
-
|
|
41
|
-
const raw = entry.message.content;
|
|
42
|
-
let text = '';
|
|
43
|
-
|
|
44
|
-
if (typeof raw === 'string') {
|
|
45
|
-
text = raw;
|
|
46
|
-
} else if (Array.isArray(raw)) {
|
|
47
|
-
// Multi-part messages — extract text blocks only, skip tool_use / tool_result
|
|
48
|
-
text = raw
|
|
49
|
-
.filter((part: any) => part.type === 'text' && typeof part.text === 'string')
|
|
50
|
-
.map((part: any) => part.text)
|
|
51
|
-
.join('\n');
|
|
52
|
-
}
|
|
97
|
+
const lines: string[] = [];
|
|
98
|
+
let sawUserMessage = false;
|
|
53
99
|
|
|
54
|
-
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (entry.type !== 'message') continue;
|
|
55
102
|
|
|
56
|
-
|
|
103
|
+
const role = entry.message?.role;
|
|
104
|
+
if (role !== 'user' && role !== 'assistant') continue;
|
|
57
105
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
106
|
+
const raw = entry.message.content;
|
|
107
|
+
let text = '';
|
|
108
|
+
|
|
109
|
+
if (typeof raw === 'string') {
|
|
110
|
+
text = raw;
|
|
111
|
+
} else if (Array.isArray(raw)) {
|
|
112
|
+
// Multi-part messages — extract text blocks only, skip tool_use / tool_result
|
|
113
|
+
text = raw
|
|
114
|
+
.filter((part: any) => part.type === 'text' && typeof part.text === 'string')
|
|
115
|
+
.map((part: any) => part.text)
|
|
116
|
+
.join('\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!text.trim()) continue;
|
|
120
|
+
|
|
121
|
+
if (role === 'user') sawUserMessage = true;
|
|
63
122
|
|
|
64
|
-
|
|
65
|
-
|
|
123
|
+
// Truncate individual messages to keep the transcript manageable
|
|
124
|
+
const truncated = text.length > 2000 ? text.slice(0, 2000) + '…' : text;
|
|
125
|
+
const label = role === 'user' ? 'User' : 'Assistant';
|
|
126
|
+
lines.push(`${label}: ${truncated}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!sawUserMessage || lines.length === 0) return '';
|
|
130
|
+
return lines.join('\n\n');
|
|
66
131
|
}
|
|
67
132
|
|
|
68
133
|
/**
|
|
@@ -70,17 +135,17 @@ export function buildTranscriptSummary(entries: any[]): string {
|
|
|
70
135
|
* on what to save (and what to skip).
|
|
71
136
|
*/
|
|
72
137
|
export function buildExtractionPrompt(memoryDir: string, transcript: string): string {
|
|
73
|
-
|
|
74
|
-
|
|
138
|
+
const existing = scanMemoryFiles(memoryDir);
|
|
139
|
+
const manifest = existing.length > 0 ? formatMemoryManifest(existing) : 'None yet';
|
|
75
140
|
|
|
76
|
-
|
|
141
|
+
return `You are a memory extraction agent for a coding assistant. Read the conversation transcript and save any durable facts worth remembering.
|
|
77
142
|
|
|
78
143
|
Memory directory: ${memoryDir}
|
|
79
144
|
This directory already exists — write files directly.
|
|
80
145
|
|
|
81
146
|
Rules:
|
|
82
147
|
- Save ONLY: user preferences/role, feedback/corrections, project context (deadlines, decisions), external references
|
|
83
|
-
- Do NOT save: code
|
|
148
|
+
- Do NOT save: raw code snippets, low-level implementation details, file paths, git history, one-off debugging steps, ephemeral task details
|
|
84
149
|
- Check existing memories below — update existing files rather than creating duplicates
|
|
85
150
|
- Use frontmatter: ---\\nname: ...\\ndescription: ...\\ntype: user|feedback|project|reference\\n---
|
|
86
151
|
- After writing topic files, update MEMORY.md with one-line index entries
|
|
@@ -99,113 +164,42 @@ ${transcript}`;
|
|
|
99
164
|
* Returns null if no valid CLI binary can be found.
|
|
100
165
|
*/
|
|
101
166
|
export function resolveCliPath(): string | null {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
167
|
+
// Prefer env vars set by loader.ts — reliable across all invocation styles
|
|
168
|
+
const envPath = process.env.LSD_BIN_PATH || process.env.GSD_BIN_PATH;
|
|
169
|
+
if (envPath && existsSync(envPath)) return envPath;
|
|
170
|
+
|
|
171
|
+
// Fallback: the entry point used to launch the current process
|
|
172
|
+
const argv1 = process.argv[1];
|
|
173
|
+
if (argv1 && existsSync(argv1)) return argv1;
|
|
174
|
+
|
|
175
|
+
// Last resort: walk up from argv1 to find a bin/ sibling
|
|
176
|
+
if (argv1) {
|
|
177
|
+
const binDir = join(dirname(argv1), '..', 'bin');
|
|
178
|
+
for (const name of ['lsd', 'gsd']) {
|
|
179
|
+
const candidate = join(binDir, name);
|
|
180
|
+
if (existsSync(candidate)) return candidate;
|
|
181
|
+
}
|
|
116
182
|
}
|
|
117
|
-
}
|
|
118
183
|
|
|
119
|
-
|
|
184
|
+
return null;
|
|
120
185
|
}
|
|
121
186
|
|
|
122
187
|
export function readBudgetMemoryModel(): string | undefined {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
188
|
+
try {
|
|
189
|
+
const settingsPath = join(getAgentDir(), 'settings.json');
|
|
190
|
+
if (!existsSync(settingsPath)) return undefined;
|
|
191
|
+
const raw = readFileSync(settingsPath, 'utf-8');
|
|
192
|
+
const parsed = JSON.parse(raw) as { budgetSubagentModel?: unknown };
|
|
193
|
+
return typeof parsed.budgetSubagentModel === 'string'
|
|
194
|
+
? normalizeSubagentModel(parsed.budgetSubagentModel)
|
|
195
|
+
: undefined;
|
|
196
|
+
} catch {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
134
199
|
}
|
|
135
200
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
*
|
|
139
|
-
* Reads the conversation transcript, builds an extraction prompt,
|
|
140
|
-
* and spawns a detached headless agent to process it.
|
|
141
|
-
* Fire-and-forget: the parent can exit without killing the child.
|
|
142
|
-
*/
|
|
143
|
-
export function extractMemories(ctx: any, cwd: string): void {
|
|
144
|
-
// Guard: prevent recursive extraction
|
|
145
|
-
if (process.env.LSD_MEMORY_EXTRACT === '1') return;
|
|
146
|
-
|
|
147
|
-
// Guard: user opt-out
|
|
148
|
-
if (process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY) return;
|
|
149
|
-
|
|
150
|
-
const entries = ctx.sessionManager.getEntries();
|
|
151
|
-
|
|
152
|
-
// Guard: need at least one user message to extract from
|
|
153
|
-
const userMessageCount = entries.filter(
|
|
154
|
-
(e: any) => e.type === 'message' && e.message?.role === 'user',
|
|
155
|
-
).length;
|
|
156
|
-
if (userMessageCount < 1) return;
|
|
157
|
-
|
|
158
|
-
const transcript = buildTranscriptSummary(entries);
|
|
159
|
-
if (!transcript) return;
|
|
160
|
-
|
|
161
|
-
const memoryDir = getMemoryDir(cwd);
|
|
162
|
-
mkdirSync(memoryDir, { recursive: true });
|
|
163
|
-
|
|
164
|
-
const prompt = buildExtractionPrompt(memoryDir, transcript);
|
|
165
|
-
const auditPath = join(memoryDir, '.last-auto-extract.txt');
|
|
166
|
-
const logPath = join(memoryDir, '.last-auto-extract.log');
|
|
167
|
-
|
|
168
|
-
// Write prompt to a temp file so the spawned agent can read it
|
|
169
|
-
const tmpPromptPath = join(tmpdir(), `lsd-memory-extract-${randomUUID()}.md`);
|
|
170
|
-
writeFileSync(tmpPromptPath, prompt, 'utf-8');
|
|
171
|
-
|
|
172
|
-
const cliPath = resolveCliPath();
|
|
173
|
-
const budgetModel = readBudgetMemoryModel();
|
|
174
|
-
if (!cliPath) {
|
|
175
|
-
writeFileSync(
|
|
176
|
-
auditPath,
|
|
177
|
-
[
|
|
178
|
-
`timestamp: ${new Date().toISOString()}`,
|
|
179
|
-
'status: skipped',
|
|
180
|
-
'reason: cli_path_not_found',
|
|
181
|
-
`cwd: ${cwd}`,
|
|
182
|
-
`userMessages: ${userMessageCount}`,
|
|
183
|
-
`transcriptLength: ${transcript.length}`,
|
|
184
|
-
`budgetModel: ${budgetModel ?? 'default'}`,
|
|
185
|
-
].join('\n') + '\n',
|
|
186
|
-
'utf-8',
|
|
187
|
-
);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
writeFileSync(
|
|
192
|
-
auditPath,
|
|
193
|
-
[
|
|
194
|
-
`timestamp: ${new Date().toISOString()}`,
|
|
195
|
-
'status: spawning',
|
|
196
|
-
`cwd: ${cwd}`,
|
|
197
|
-
`userMessages: ${userMessageCount}`,
|
|
198
|
-
`transcriptLength: ${transcript.length}`,
|
|
199
|
-
`cliPath: ${cliPath}`,
|
|
200
|
-
`budgetModel: ${budgetModel ?? 'default'}`,
|
|
201
|
-
`logPath: ${logPath}`,
|
|
202
|
-
].join('\n') + '\n',
|
|
203
|
-
'utf-8',
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
const instruction = 'Extract memories from the transcript above. Write any worth-saving memories to the memory directory, then update MEMORY.md.';
|
|
207
|
-
|
|
208
|
-
const helperScript = `
|
|
201
|
+
export function buildAutoExtractHelperScript(): string {
|
|
202
|
+
return String.raw`
|
|
209
203
|
const { spawn } = require('node:child_process');
|
|
210
204
|
const { appendFileSync, writeFileSync, readFileSync, readdirSync, statSync, existsSync } = require('node:fs');
|
|
211
205
|
const { join, delimiter } = require('node:path');
|
|
@@ -213,41 +207,67 @@ const { join, delimiter } = require('node:path');
|
|
|
213
207
|
const [cliPath, cwd, tmpPromptPath, auditPath, logPath, memoryDir, instruction, model, userMessageCount, transcriptLength] = process.argv.slice(1);
|
|
214
208
|
const startedAt = new Date().toISOString();
|
|
215
209
|
let finalized = false;
|
|
216
|
-
let
|
|
217
|
-
let
|
|
210
|
+
let completionState = null;
|
|
211
|
+
let completionTimer = null;
|
|
218
212
|
let hardTimeout = null;
|
|
219
213
|
let pendingLogText = '';
|
|
220
214
|
const ANSI_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/g;
|
|
215
|
+
const CACHE_TIMER_RE = /^\[phase\]\s+cache-timer(?:\s*:\s*.*)?\s*$/i;
|
|
216
|
+
const SESSION_ENDED_RE = /^\[agent\]\s+Session ended/;
|
|
217
|
+
const HEADLESS_STATUS_RE = /^\[headless\]\s+Status:\s+(\w+)\s*$/i;
|
|
221
218
|
|
|
222
219
|
function stripAnsi(text) {
|
|
223
220
|
return String(text).replace(ANSI_PATTERN, '');
|
|
224
221
|
}
|
|
225
222
|
|
|
223
|
+
function classifyLogLine(rawLine) {
|
|
224
|
+
const stripped = stripAnsi(rawLine).trim();
|
|
225
|
+
if (!stripped) {
|
|
226
|
+
return { stripped, keep: false, completion: null, completionReason: null };
|
|
227
|
+
}
|
|
228
|
+
if (CACHE_TIMER_RE.test(stripped)) {
|
|
229
|
+
return { stripped, keep: false, completion: null, completionReason: null };
|
|
230
|
+
}
|
|
231
|
+
if (SESSION_ENDED_RE.test(stripped)) {
|
|
232
|
+
return { stripped, keep: true, completion: 'finished', completionReason: 'session_end_detected' };
|
|
233
|
+
}
|
|
234
|
+
const headlessStatusMatch = stripped.match(HEADLESS_STATUS_RE);
|
|
235
|
+
if (headlessStatusMatch) {
|
|
236
|
+
const status = String(headlessStatusMatch[1] || '').toLowerCase();
|
|
237
|
+
if (status === 'complete') {
|
|
238
|
+
return { stripped, keep: true, completion: 'finished', completionReason: 'headless_status_complete' };
|
|
239
|
+
}
|
|
240
|
+
return { stripped, keep: true, completion: 'failed', completionReason: 'headless_status_' + status };
|
|
241
|
+
}
|
|
242
|
+
return { stripped, keep: true, completion: null, completionReason: null };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function scheduleCompletion(completion, completionReason) {
|
|
246
|
+
if (!completion || completionState || completionTimer) return;
|
|
247
|
+
completionState = { completion, completionReason };
|
|
248
|
+
completionTimer = setTimeout(() => {
|
|
249
|
+
finalize(completion, completion === 'finished' ? 0 : 1, null, completionReason);
|
|
250
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
251
|
+
setTimeout(() => { try { child.kill('SIGKILL'); } catch {} }, 2000).unref();
|
|
252
|
+
}, 1500);
|
|
253
|
+
completionTimer.unref();
|
|
254
|
+
}
|
|
255
|
+
|
|
226
256
|
function flushLogText(text, force = false) {
|
|
227
257
|
pendingLogText += text;
|
|
228
|
-
const parts = pendingLogText.split(
|
|
258
|
+
const parts = pendingLogText.split(/(?:\r?\n|\r)/);
|
|
229
259
|
pendingLogText = force ? '' : (parts.pop() ?? '');
|
|
230
260
|
|
|
231
261
|
const kept = [];
|
|
232
262
|
for (const rawLine of parts) {
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
if (/^\[agent\]\s+Session ended/.test(line)) {
|
|
238
|
-
sawSessionEnded = true;
|
|
263
|
+
const classified = classifyLogLine(rawLine);
|
|
264
|
+
if (classified.keep) kept.push(rawLine);
|
|
265
|
+
if (classified.completion) {
|
|
266
|
+
scheduleCompletion(classified.completion, classified.completionReason);
|
|
239
267
|
}
|
|
240
268
|
}
|
|
241
269
|
|
|
242
270
|
if (kept.length > 0) appendFileSync(logPath, kept.join('\n') + '\n');
|
|
243
|
-
if (sawSessionEnded && !sessionEndTimer) {
|
|
244
|
-
sessionEndTimer = setTimeout(() => {
|
|
245
|
-
finalize('finished', 0, null, 'session_end_detected');
|
|
246
|
-
try { child.kill('SIGTERM'); } catch {}
|
|
247
|
-
setTimeout(() => { try { child.kill('SIGKILL'); } catch {} }, 2000).unref();
|
|
248
|
-
}, 1500);
|
|
249
|
-
sessionEndTimer.unref();
|
|
250
|
-
}
|
|
251
271
|
}
|
|
252
272
|
|
|
253
273
|
function appendLog(chunk) {
|
|
@@ -269,7 +289,7 @@ function writeAudit(status, extra = []) {
|
|
|
269
289
|
'model: ' + (model || 'default'),
|
|
270
290
|
'logPath: ' + logPath,
|
|
271
291
|
...extra,
|
|
272
|
-
].join('
|
|
292
|
+
].join('\n') + '\n', 'utf-8');
|
|
273
293
|
} catch {}
|
|
274
294
|
}
|
|
275
295
|
|
|
@@ -291,7 +311,7 @@ function newestMemoryMtime(dir) {
|
|
|
291
311
|
function finalize(status, code, signal, completionReason) {
|
|
292
312
|
if (finalized) return;
|
|
293
313
|
finalized = true;
|
|
294
|
-
if (
|
|
314
|
+
if (completionTimer) clearTimeout(completionTimer);
|
|
295
315
|
if (hardTimeout) clearTimeout(hardTimeout);
|
|
296
316
|
const afterMtime = newestMemoryMtime(memoryDir);
|
|
297
317
|
const logText = existsSync(logPath) ? readFileSync(logPath, 'utf-8') : '';
|
|
@@ -318,7 +338,10 @@ const bundledPaths = Array.from(
|
|
|
318
338
|
new Set(
|
|
319
339
|
[process.env.GSD_BUNDLED_EXTENSION_PATHS, process.env.LSD_BUNDLED_EXTENSION_PATHS]
|
|
320
340
|
.filter(Boolean)
|
|
321
|
-
.flatMap((value) => String(value).split(delimiter).map((entry) => entry.trim()).filter(Boolean))
|
|
341
|
+
.flatMap((value) => String(value).split(delimiter).map((entry) => entry.trim()).filter(Boolean))
|
|
342
|
+
// Explicitly disable cache-timer extension for auto-memory workers.
|
|
343
|
+
// It is noisy in headless logs and provides no value for maintenance runs.
|
|
344
|
+
.filter((entry) => !/[\\/]cache-timer[\\/]/i.test(entry)),
|
|
322
345
|
),
|
|
323
346
|
);
|
|
324
347
|
for (const extensionPath of bundledPaths) childArgs.push('--extension', extensionPath);
|
|
@@ -328,11 +351,25 @@ childArgs.push('--bare', '--context', tmpPromptPath, '--context-text', instructi
|
|
|
328
351
|
const child = spawn(
|
|
329
352
|
process.execPath,
|
|
330
353
|
childArgs,
|
|
331
|
-
{
|
|
354
|
+
{
|
|
355
|
+
cwd,
|
|
356
|
+
env: {
|
|
357
|
+
...process.env,
|
|
358
|
+
LSD_MEMORY_EXTRACT: '1',
|
|
359
|
+
// Hard-disable cache timer in maintenance workers.
|
|
360
|
+
LSD_DISABLE_CACHE_TIMER: '1',
|
|
361
|
+
// Memory maintenance workers must not run in auto permission mode.
|
|
362
|
+
// In auto mode, write/edit calls require classifier approval, but headless
|
|
363
|
+
// maintenance workers have no interactive classifier responder.
|
|
364
|
+
// Keep writes safe via extension-level path restrictions instead.
|
|
365
|
+
LUCENT_CODE_PERMISSION_MODE: 'danger-full-access',
|
|
366
|
+
},
|
|
367
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
368
|
+
},
|
|
332
369
|
);
|
|
333
370
|
|
|
334
371
|
hardTimeout = setTimeout(() => {
|
|
335
|
-
finalize('failed', null, 'timeout',
|
|
372
|
+
finalize('failed', null, 'timeout', completionState?.completionReason ?? 'timeout');
|
|
336
373
|
try { child.kill('SIGTERM'); } catch {}
|
|
337
374
|
setTimeout(() => { try { child.kill('SIGKILL'); } catch {} }, 2000).unref();
|
|
338
375
|
}, 120000);
|
|
@@ -341,7 +378,7 @@ hardTimeout.unref();
|
|
|
341
378
|
child.stdout.on('data', appendLog);
|
|
342
379
|
child.stderr.on('data', appendLog);
|
|
343
380
|
child.on('error', (err) => {
|
|
344
|
-
appendLog(String(err && err.stack ? err.stack : err) + '
|
|
381
|
+
appendLog(String(err && err.stack ? err.stack : err) + '\n');
|
|
345
382
|
flushLogText('', true);
|
|
346
383
|
finalize('failed', null, 'spawn_error', String(err && err.message ? err.message : err));
|
|
347
384
|
});
|
|
@@ -350,54 +387,143 @@ child.on('exit', (code, signal) => {
|
|
|
350
387
|
finalize(code === 0 ? 'finished' : 'failed', code, signal, 'child_exit');
|
|
351
388
|
});
|
|
352
389
|
`;
|
|
390
|
+
}
|
|
353
391
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
auditPath,
|
|
363
|
-
logPath,
|
|
364
|
-
memoryDir,
|
|
365
|
-
instruction,
|
|
366
|
-
budgetModel ?? '',
|
|
367
|
-
String(userMessageCount),
|
|
368
|
-
String(transcript.length),
|
|
369
|
-
],
|
|
370
|
-
{
|
|
371
|
-
cwd,
|
|
372
|
-
detached: true,
|
|
373
|
-
stdio: 'ignore',
|
|
374
|
-
env: process.env,
|
|
375
|
-
},
|
|
376
|
-
);
|
|
377
|
-
proc.unref();
|
|
378
|
-
|
|
379
|
-
writeFileSync(
|
|
380
|
-
auditPath,
|
|
381
|
-
[
|
|
382
|
-
`timestamp: ${new Date().toISOString()}`,
|
|
383
|
-
'status: spawned',
|
|
384
|
-
`pid: ${proc.pid ?? 'unknown'}`,
|
|
385
|
-
`cwd: ${cwd}`,
|
|
386
|
-
`userMessages: ${userMessageCount}`,
|
|
387
|
-
`transcriptLength: ${transcript.length}`,
|
|
388
|
-
`cliPath: ${cliPath}`,
|
|
389
|
-
`model: ${budgetModel ?? 'default'}`,
|
|
390
|
-
`logPath: ${logPath}`,
|
|
391
|
-
].join('\n') + '\n',
|
|
392
|
-
'utf-8',
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
// Clean up the temp file after the child has had time to read it
|
|
396
|
-
setTimeout(() => {
|
|
392
|
+
/**
|
|
393
|
+
* Main entry point — called from the session_shutdown hook.
|
|
394
|
+
*
|
|
395
|
+
* Reads the conversation transcript, builds an extraction prompt,
|
|
396
|
+
* and spawns a detached headless agent to process it.
|
|
397
|
+
* Fire-and-forget: the parent can exit without killing the child.
|
|
398
|
+
*/
|
|
399
|
+
function readAutoMemoryEnabled(): boolean {
|
|
397
400
|
try {
|
|
398
|
-
|
|
401
|
+
const settingsPath = join(getAgentDir(), 'settings.json');
|
|
402
|
+
if (!existsSync(settingsPath)) return false;
|
|
403
|
+
const raw = readFileSync(settingsPath, 'utf-8');
|
|
404
|
+
const parsed = JSON.parse(raw) as { autoMemory?: unknown };
|
|
405
|
+
return parsed.autoMemory === true;
|
|
399
406
|
} catch {
|
|
400
|
-
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function extractMemories(ctx: any, cwd: string): void {
|
|
412
|
+
// Guard: prevent recursive extraction
|
|
413
|
+
if (process.env.LSD_MEMORY_EXTRACT === '1') return;
|
|
414
|
+
|
|
415
|
+
// Guard: user opt-out via env var
|
|
416
|
+
if (process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY) return;
|
|
417
|
+
|
|
418
|
+
// Guard: auto memory must be enabled in settings (default: disabled)
|
|
419
|
+
if (!readAutoMemoryEnabled()) return;
|
|
420
|
+
|
|
421
|
+
const entries = ctx.sessionManager.getEntries();
|
|
422
|
+
|
|
423
|
+
// Guard: need at least one user message to extract from
|
|
424
|
+
const userMessageCount = entries.filter(
|
|
425
|
+
(e: any) => e.type === 'message' && e.message?.role === 'user',
|
|
426
|
+
).length;
|
|
427
|
+
if (userMessageCount < 1) return;
|
|
428
|
+
|
|
429
|
+
const transcript = buildTranscriptSummary(entries);
|
|
430
|
+
if (!transcript) return;
|
|
431
|
+
|
|
432
|
+
const memoryDir = getMemoryDir(cwd);
|
|
433
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
434
|
+
|
|
435
|
+
const prompt = buildExtractionPrompt(memoryDir, transcript);
|
|
436
|
+
const auditPath = join(memoryDir, '.last-auto-extract.txt');
|
|
437
|
+
const logPath = join(memoryDir, '.last-auto-extract.log');
|
|
438
|
+
|
|
439
|
+
// Write prompt to a temp file so the spawned agent can read it
|
|
440
|
+
const tmpPromptPath = join(tmpdir(), `lsd-memory-extract-${randomUUID()}.md`);
|
|
441
|
+
writeFileSync(tmpPromptPath, prompt, 'utf-8');
|
|
442
|
+
|
|
443
|
+
const cliPath = resolveCliPath();
|
|
444
|
+
const budgetModel = readBudgetMemoryModel();
|
|
445
|
+
if (!cliPath) {
|
|
446
|
+
writeFileSync(
|
|
447
|
+
auditPath,
|
|
448
|
+
[
|
|
449
|
+
`timestamp: ${new Date().toISOString()}`,
|
|
450
|
+
'status: skipped',
|
|
451
|
+
'reason: cli_path_not_found',
|
|
452
|
+
`cwd: ${cwd}`,
|
|
453
|
+
`userMessages: ${userMessageCount}`,
|
|
454
|
+
`transcriptLength: ${transcript.length}`,
|
|
455
|
+
`budgetModel: ${budgetModel ?? 'default'}`,
|
|
456
|
+
].join('\n') + '\n',
|
|
457
|
+
'utf-8',
|
|
458
|
+
);
|
|
459
|
+
return;
|
|
401
460
|
}
|
|
402
|
-
|
|
461
|
+
|
|
462
|
+
writeFileSync(
|
|
463
|
+
auditPath,
|
|
464
|
+
[
|
|
465
|
+
`timestamp: ${new Date().toISOString()}`,
|
|
466
|
+
'status: spawning',
|
|
467
|
+
`cwd: ${cwd}`,
|
|
468
|
+
`userMessages: ${userMessageCount}`,
|
|
469
|
+
`transcriptLength: ${transcript.length}`,
|
|
470
|
+
`cliPath: ${cliPath}`,
|
|
471
|
+
`budgetModel: ${budgetModel ?? 'default'}`,
|
|
472
|
+
`logPath: ${logPath}`,
|
|
473
|
+
].join('\n') + '\n',
|
|
474
|
+
'utf-8',
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
const instruction = 'Extract memories from the transcript above. Write any worth-saving memories to the memory directory, then update MEMORY.md.';
|
|
478
|
+
const helperScript = buildAutoExtractHelperScript();
|
|
479
|
+
|
|
480
|
+
const proc = spawn(
|
|
481
|
+
process.execPath,
|
|
482
|
+
[
|
|
483
|
+
'-e',
|
|
484
|
+
helperScript,
|
|
485
|
+
cliPath,
|
|
486
|
+
cwd,
|
|
487
|
+
tmpPromptPath,
|
|
488
|
+
auditPath,
|
|
489
|
+
logPath,
|
|
490
|
+
memoryDir,
|
|
491
|
+
instruction,
|
|
492
|
+
budgetModel ?? '',
|
|
493
|
+
String(userMessageCount),
|
|
494
|
+
String(transcript.length),
|
|
495
|
+
],
|
|
496
|
+
{
|
|
497
|
+
cwd,
|
|
498
|
+
detached: true,
|
|
499
|
+
stdio: 'ignore',
|
|
500
|
+
env: process.env,
|
|
501
|
+
},
|
|
502
|
+
);
|
|
503
|
+
proc.unref();
|
|
504
|
+
|
|
505
|
+
writeFileSync(
|
|
506
|
+
auditPath,
|
|
507
|
+
[
|
|
508
|
+
`timestamp: ${new Date().toISOString()}`,
|
|
509
|
+
'status: spawned',
|
|
510
|
+
`pid: ${proc.pid ?? 'unknown'}`,
|
|
511
|
+
`cwd: ${cwd}`,
|
|
512
|
+
`userMessages: ${userMessageCount}`,
|
|
513
|
+
`transcriptLength: ${transcript.length}`,
|
|
514
|
+
`cliPath: ${cliPath}`,
|
|
515
|
+
`model: ${budgetModel ?? 'default'}`,
|
|
516
|
+
`logPath: ${logPath}`,
|
|
517
|
+
].join('\n') + '\n',
|
|
518
|
+
'utf-8',
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
// Clean up the temp file after the child has had time to read it
|
|
522
|
+
setTimeout(() => {
|
|
523
|
+
try {
|
|
524
|
+
unlinkSync(tmpPromptPath);
|
|
525
|
+
} catch {
|
|
526
|
+
// Already cleaned up or inaccessible — safe to ignore
|
|
527
|
+
}
|
|
528
|
+
}, 120_000).unref();
|
|
403
529
|
}
|