@ekkos/cli 1.3.7 → 1.3.9
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/dist/commands/dashboard.js +67 -11
- package/dist/commands/gemini.js +45 -18
- package/dist/commands/init.js +92 -21
- package/dist/commands/living-docs.d.ts +8 -0
- package/dist/commands/living-docs.js +66 -0
- package/dist/commands/run.d.ts +5 -0
- package/dist/commands/run.js +473 -15
- package/dist/commands/scan.d.ts +68 -0
- package/dist/commands/scan.js +318 -22
- package/dist/commands/setup.js +2 -6
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/deploy/instructions.d.ts +10 -3
- package/dist/deploy/instructions.js +34 -7
- package/dist/index.js +170 -87
- package/dist/lib/usage-parser.js +18 -2
- package/dist/local/index.d.ts +4 -0
- package/dist/local/index.js +14 -1
- package/dist/local/language-config.d.ts +55 -0
- package/dist/local/language-config.js +729 -0
- package/dist/local/living-docs-manager.d.ts +59 -0
- package/dist/local/living-docs-manager.js +1084 -0
- package/dist/local/stack-detection.d.ts +21 -0
- package/dist/local/stack-detection.js +406 -0
- package/dist/utils/proxy-url.d.ts +6 -1
- package/dist/utils/proxy-url.js +16 -2
- package/package.json +31 -17
- package/templates/CLAUDE.md +89 -99
- package/templates/agents/prune.md +83 -0
- package/templates/agents/rewind.md +84 -0
- package/templates/agents/scout.md +102 -0
- package/templates/agents/trace.md +99 -0
- package/templates/commands/continue.md +47 -0
- package/LICENSE +0 -21
package/dist/commands/run.js
CHANGED
|
@@ -49,6 +49,7 @@ const doctor_1 = require("./doctor");
|
|
|
49
49
|
const stream_tailer_1 = require("../capture/stream-tailer");
|
|
50
50
|
const jsonl_rewriter_1 = require("../capture/jsonl-rewriter");
|
|
51
51
|
const transcript_repair_1 = require("../capture/transcript-repair");
|
|
52
|
+
const living_docs_manager_js_1 = require("../local/living-docs-manager.js");
|
|
52
53
|
// Try to load node-pty (may fail on Node 24+)
|
|
53
54
|
// IMPORTANT: This must be awaited in run() to avoid racey false fallbacks.
|
|
54
55
|
let pty = null;
|
|
@@ -85,6 +86,244 @@ function getConfig(options) {
|
|
|
85
86
|
};
|
|
86
87
|
/* eslint-enable no-restricted-syntax */
|
|
87
88
|
}
|
|
89
|
+
const MAX_OUTPUT_1M_MODELS = '128000';
|
|
90
|
+
const MAX_OUTPUT_200K_OPUS_SONNET = '32768';
|
|
91
|
+
let runtimeClaudeCodeVersion = null;
|
|
92
|
+
let runtimeClaudeContextWindow = 'auto';
|
|
93
|
+
let runtimeClaudeLaunchModel;
|
|
94
|
+
function normalizeContextWindowOption(value) {
|
|
95
|
+
const normalized = (value || '').trim().toLowerCase();
|
|
96
|
+
if (normalized === '200k' || normalized === '200000')
|
|
97
|
+
return '200k';
|
|
98
|
+
if (normalized === '1m' || normalized === '1000000')
|
|
99
|
+
return '1m';
|
|
100
|
+
return 'auto';
|
|
101
|
+
}
|
|
102
|
+
function normalizeRequestedLaunchModel(value) {
|
|
103
|
+
if (typeof value !== 'string')
|
|
104
|
+
return {};
|
|
105
|
+
const raw = (value || '').trim();
|
|
106
|
+
if (!raw)
|
|
107
|
+
return {};
|
|
108
|
+
if (raw.toLowerCase() === 'default') {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
const oneMillionMatch = raw.match(/^(.*)\[1m\]$/i);
|
|
112
|
+
if (oneMillionMatch) {
|
|
113
|
+
return {
|
|
114
|
+
model: oneMillionMatch[1].trim(),
|
|
115
|
+
syntheticContextWindow: '1m',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const lower = raw.toLowerCase();
|
|
119
|
+
if (lower.endsWith('-200k')) {
|
|
120
|
+
return {
|
|
121
|
+
model: raw.slice(0, -5).trim(),
|
|
122
|
+
syntheticContextWindow: '200k',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (lower.endsWith('-1m')) {
|
|
126
|
+
return {
|
|
127
|
+
model: raw.slice(0, -3).trim(),
|
|
128
|
+
syntheticContextWindow: '1m',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { model: raw };
|
|
132
|
+
}
|
|
133
|
+
function modelSupportsOneMillionContext(model) {
|
|
134
|
+
const normalized = (model || 'default').trim().toLowerCase();
|
|
135
|
+
if (!normalized || normalized === 'default')
|
|
136
|
+
return true;
|
|
137
|
+
if (normalized === 'opus' || normalized === 'sonnet')
|
|
138
|
+
return true;
|
|
139
|
+
return normalized.includes('claude-opus-4-6') || normalized.includes('claude-sonnet-4-6');
|
|
140
|
+
}
|
|
141
|
+
function buildClaudeLaunchModelArg(model, contextWindow) {
|
|
142
|
+
if (!model)
|
|
143
|
+
return undefined;
|
|
144
|
+
const normalized = normalizeRequestedLaunchModel(model).model;
|
|
145
|
+
if (!normalized)
|
|
146
|
+
return undefined;
|
|
147
|
+
if (contextWindow === '1m' && modelSupportsOneMillionContext(normalized)) {
|
|
148
|
+
return `${normalized}[1m]`;
|
|
149
|
+
}
|
|
150
|
+
return normalized;
|
|
151
|
+
}
|
|
152
|
+
function isTwoHundredKFixedOutputModel(model) {
|
|
153
|
+
const normalized = (model || '').trim().toLowerCase();
|
|
154
|
+
if (!normalized)
|
|
155
|
+
return false;
|
|
156
|
+
if (normalized === 'opus' || normalized === 'sonnet')
|
|
157
|
+
return true;
|
|
158
|
+
return /^claude-(?:opus|sonnet)-4-(?:5|6)(?:$|-)/.test(normalized);
|
|
159
|
+
}
|
|
160
|
+
function resolveClaudeMaxOutputTokens(model, contextWindow) {
|
|
161
|
+
if (process.env.EKKOS_MAX_OUTPUT_TOKENS) {
|
|
162
|
+
return process.env.EKKOS_MAX_OUTPUT_TOKENS;
|
|
163
|
+
}
|
|
164
|
+
if (contextWindow === '200k' && isTwoHundredKFixedOutputModel(model)) {
|
|
165
|
+
return MAX_OUTPUT_200K_OPUS_SONNET;
|
|
166
|
+
}
|
|
167
|
+
return MAX_OUTPUT_1M_MODELS;
|
|
168
|
+
}
|
|
169
|
+
function shouldOpenLaunchSelector(options) {
|
|
170
|
+
return options.model === true;
|
|
171
|
+
}
|
|
172
|
+
function renderClaudeLaunchSelectorIntro() {
|
|
173
|
+
const neonCyan = chalk_1.default.hex('#00f5ff');
|
|
174
|
+
const acidGreen = chalk_1.default.hex('#9dff00');
|
|
175
|
+
const signalAmber = chalk_1.default.hex('#ffb300');
|
|
176
|
+
const steel = chalk_1.default.hex('#8a92a6');
|
|
177
|
+
const width = 68;
|
|
178
|
+
const line = (text) => {
|
|
179
|
+
const pad = Math.max(0, width - stripAnsi(text).length);
|
|
180
|
+
return `${neonCyan('║')} ${text}${' '.repeat(pad)}${neonCyan('║')}`;
|
|
181
|
+
};
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log(neonCyan('╔══════════════════════════════════════════════════════════════════════╗'));
|
|
184
|
+
console.log(line(chalk_1.default.white.bold('ekkOS.dev // PULSE')));
|
|
185
|
+
console.log(neonCyan('╠══════════════════════════════════════════════════════════════════════╣'));
|
|
186
|
+
console.log(line(`${acidGreen('200K')} ${steel('compat lane')} ${chalk_1.default.white('// Opus/Sonnet 4.5/4.6 => 32,768 output')}`));
|
|
187
|
+
console.log(line(`${signalAmber(' 1M')} ${steel('wide lane')} ${chalk_1.default.white('// Opus/Sonnet 4.6 => 128,000 output')}`));
|
|
188
|
+
console.log(line(steel('Pick a launch vector, then a fixed runtime profile. No hidden context prompt.')));
|
|
189
|
+
console.log(neonCyan('╚══════════════════════════════════════════════════════════════════════╝'));
|
|
190
|
+
console.log('');
|
|
191
|
+
}
|
|
192
|
+
function buildLaunchModeChoices() {
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
name: `${chalk_1.default.hex('#00f5ff').bold('Fresh boot')} ${chalk_1.default.gray('// new shell')}`,
|
|
196
|
+
value: 'fresh',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: `${chalk_1.default.hex('#9dff00').bold('Resume recent')} ${chalk_1.default.gray('// latest thread')}`,
|
|
200
|
+
value: 'continue',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: `${chalk_1.default.hex('#ffb300').bold('Resume by ID')} ${chalk_1.default.gray('// targeted reconnect')}`,
|
|
204
|
+
value: 'resume',
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
function buildLaunchModelChoices() {
|
|
209
|
+
const cyan = chalk_1.default.hex('#00f5ff');
|
|
210
|
+
const amber = chalk_1.default.hex('#ffb300');
|
|
211
|
+
const green = chalk_1.default.hex('#00ff88');
|
|
212
|
+
return [
|
|
213
|
+
{
|
|
214
|
+
name: `${cyan.bold('Auto route')} ${chalk_1.default.gray('// Claude decides profile')}`,
|
|
215
|
+
value: 'default',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: `${cyan.bold('Claude Opus 4.5')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
|
|
219
|
+
value: 'claude-opus-4-5',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: `${cyan.bold('Claude Opus 4.6')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
|
|
223
|
+
value: 'claude-opus-4-6-200k',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: `${cyan.bold('Claude Opus 4.6')} ${amber('[1M]')} ${chalk_1.default.gray('// out 128,000')}`,
|
|
227
|
+
value: 'claude-opus-4-6-1m',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: `${cyan.bold('Claude Sonnet 4.5')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
|
|
231
|
+
value: 'claude-sonnet-4-5',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: `${cyan.bold('Claude Sonnet 4.6')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
|
|
235
|
+
value: 'claude-sonnet-4-6-200k',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: `${cyan.bold('Claude Sonnet 4.6')} ${amber('[1M]')} ${chalk_1.default.gray('// out 128,000')}`,
|
|
239
|
+
value: 'claude-sonnet-4-6-1m',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: `${cyan.bold('Claude Haiku 4.5')} ${green('[200K]')} ${chalk_1.default.gray('// fast lane')}`,
|
|
243
|
+
value: 'claude-haiku-4-5',
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
async function resolveClaudeLaunchSelection(options) {
|
|
248
|
+
const requestedModel = normalizeRequestedLaunchModel(typeof options.model === 'string' ? options.model : process.env.EKKOS_CLAUDE_MODEL);
|
|
249
|
+
let model = requestedModel.model;
|
|
250
|
+
let contextWindow = requestedModel.syntheticContextWindow
|
|
251
|
+
|| normalizeContextWindowOption(options.contextWindow || process.env.EKKOS_CLAUDE_CONTEXT_WINDOW);
|
|
252
|
+
let continueLast = options.continueLast || false;
|
|
253
|
+
let resumeSession = (options.resumeSession || '').trim();
|
|
254
|
+
const shouldShowLaunchWindow = shouldOpenLaunchSelector(options)
|
|
255
|
+
&& process.stdin.isTTY === true
|
|
256
|
+
&& process.stdout.isTTY === true
|
|
257
|
+
&& process.env.EKKOS_DISABLE_LAUNCH_WINDOW !== '1';
|
|
258
|
+
if (!shouldShowLaunchWindow) {
|
|
259
|
+
if (!modelSupportsOneMillionContext(model)) {
|
|
260
|
+
contextWindow = '200k';
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
model,
|
|
264
|
+
contextWindow,
|
|
265
|
+
continueLast: !!continueLast && !resumeSession,
|
|
266
|
+
resumeSession: resumeSession || undefined,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const { default: inquirer } = await Promise.resolve().then(() => __importStar(require('inquirer')));
|
|
270
|
+
renderClaudeLaunchSelectorIntro();
|
|
271
|
+
const defaultLaunchMode = resumeSession ? 'resume' : continueLast ? 'continue' : 'fresh';
|
|
272
|
+
const firstPrompt = await inquirer.prompt([
|
|
273
|
+
{
|
|
274
|
+
type: 'list',
|
|
275
|
+
name: 'launchMode',
|
|
276
|
+
message: chalk_1.default.hex('#00f5ff').bold('>> Boot vector'),
|
|
277
|
+
default: defaultLaunchMode,
|
|
278
|
+
pageSize: 3,
|
|
279
|
+
loop: false,
|
|
280
|
+
choices: buildLaunchModeChoices(),
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
type: 'list',
|
|
284
|
+
name: 'model',
|
|
285
|
+
message: chalk_1.default.hex('#00f5ff').bold('>> Model lane'),
|
|
286
|
+
default: model || 'default',
|
|
287
|
+
pageSize: 8,
|
|
288
|
+
loop: false,
|
|
289
|
+
choices: buildLaunchModelChoices(),
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
const selectedProfile = normalizeRequestedLaunchModel(firstPrompt.model);
|
|
293
|
+
model = firstPrompt.model === 'default' ? undefined : selectedProfile.model;
|
|
294
|
+
continueLast = firstPrompt.launchMode === 'continue';
|
|
295
|
+
resumeSession = firstPrompt.launchMode === 'resume' ? resumeSession : '';
|
|
296
|
+
if (firstPrompt.launchMode === 'resume') {
|
|
297
|
+
const resumePrompt = await inquirer.prompt([
|
|
298
|
+
{
|
|
299
|
+
type: 'input',
|
|
300
|
+
name: 'resumeSession',
|
|
301
|
+
message: 'Claude session ID',
|
|
302
|
+
default: resumeSession,
|
|
303
|
+
validate: (value) => value.trim().length > 0 || 'Session ID is required',
|
|
304
|
+
},
|
|
305
|
+
]);
|
|
306
|
+
resumeSession = resumePrompt.resumeSession.trim();
|
|
307
|
+
}
|
|
308
|
+
if (firstPrompt.model === 'default') {
|
|
309
|
+
contextWindow = 'auto';
|
|
310
|
+
}
|
|
311
|
+
else if (selectedProfile.syntheticContextWindow) {
|
|
312
|
+
contextWindow = selectedProfile.syntheticContextWindow;
|
|
313
|
+
}
|
|
314
|
+
else if (!modelSupportsOneMillionContext(model)) {
|
|
315
|
+
contextWindow = '200k';
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
contextWindow = 'auto';
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
model,
|
|
322
|
+
contextWindow,
|
|
323
|
+
continueLast,
|
|
324
|
+
resumeSession: resumeSession || undefined,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
88
327
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
89
328
|
// PATTERN MATCHING
|
|
90
329
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -290,11 +529,6 @@ const isWindows = os.platform() === 'win32';
|
|
|
290
529
|
// Core ekkOS patches (eviction, context management) work with all recent versions
|
|
291
530
|
// Cosmetic patches may fail on newer versions but don't affect functionality
|
|
292
531
|
const PINNED_CLAUDE_VERSION = 'latest';
|
|
293
|
-
// Max output tokens for Claude responses
|
|
294
|
-
// Default: 16384 (safe for Sonnet 4.5)
|
|
295
|
-
// Opus 4.5 supports up to 64k - set EKKOS_MAX_OUTPUT_TOKENS=32768 or =65536 to use higher limits
|
|
296
|
-
// Configurable via environment variable
|
|
297
|
-
const EKKOS_MAX_OUTPUT_TOKENS = process.env.EKKOS_MAX_OUTPUT_TOKENS || '16384';
|
|
298
532
|
const proxy_url_1 = require("../utils/proxy-url");
|
|
299
533
|
// Track proxy mode for getEkkosEnv (set by run() based on options)
|
|
300
534
|
let proxyModeEnabled = true;
|
|
@@ -315,12 +549,13 @@ let cliSessionName = null;
|
|
|
315
549
|
let cliSessionId = null;
|
|
316
550
|
/**
|
|
317
551
|
* Get environment with ekkOS enhancements
|
|
318
|
-
* - Sets CLAUDE_CODE_MAX_OUTPUT_TOKENS
|
|
552
|
+
* - Sets CLAUDE_CODE_MAX_OUTPUT_TOKENS dynamically for the selected Claude profile
|
|
319
553
|
* - Routes API through ekkOS proxy for seamless context eviction (when enabled)
|
|
320
554
|
* - Sets EKKOS_PROXY_MODE to signal JSONL rewriter to disable eviction
|
|
321
555
|
* - Passes session headers for eviction/retrieval context tracking
|
|
322
556
|
*/
|
|
323
557
|
function getEkkosEnv() {
|
|
558
|
+
const maxOutputTokens = resolveClaudeMaxOutputTokens(runtimeClaudeLaunchModel, runtimeClaudeContextWindow);
|
|
324
559
|
/* eslint-disable no-restricted-syntax -- System env spreading, not API key access */
|
|
325
560
|
const env = {
|
|
326
561
|
...process.env,
|
|
@@ -332,8 +567,18 @@ function getEkkosEnv() {
|
|
|
332
567
|
// Native autocompact would compact without ekkOS saving state first, causing knowledge loss.
|
|
333
568
|
// Only ekkOS-wrapped sessions get this; vanilla `claude` keeps autocompact on.
|
|
334
569
|
DISABLE_AUTO_COMPACT: 'true',
|
|
570
|
+
// Align Claude's advertised output ceiling with the chosen model/context profile.
|
|
571
|
+
// 1M Opus/Sonnet gets 128K; 200K Opus/Sonnet 4.5/4.6 is forced to 32,768.
|
|
572
|
+
// The proxy also enforces this server-side in case Claude Code ignores the env var.
|
|
573
|
+
CLAUDE_CODE_MAX_OUTPUT_TOKENS: maxOutputTokens,
|
|
335
574
|
};
|
|
336
575
|
/* eslint-enable no-restricted-syntax */
|
|
576
|
+
if (runtimeClaudeContextWindow === '200k') {
|
|
577
|
+
env.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
delete env.CLAUDE_CODE_DISABLE_1M_CONTEXT;
|
|
581
|
+
}
|
|
337
582
|
// Check if proxy is disabled via env var or options
|
|
338
583
|
// eslint-disable-next-line no-restricted-syntax -- Feature flag, not API key
|
|
339
584
|
const proxyDisabled = process.env.EKKOS_DISABLE_PROXY === '1' || !proxyModeEnabled;
|
|
@@ -346,7 +591,9 @@ function getEkkosEnv() {
|
|
|
346
591
|
if (!cliSessionName) {
|
|
347
592
|
cliSessionId = crypto.randomUUID();
|
|
348
593
|
cliSessionName = (0, state_1.uuidToWords)(cliSessionId);
|
|
349
|
-
|
|
594
|
+
if (process.env.EKKOS_NO_SPLASH !== '1') {
|
|
595
|
+
console.log(chalk_1.default.gray(` 📂 Session: ${cliSessionName}`));
|
|
596
|
+
}
|
|
350
597
|
}
|
|
351
598
|
// Get full userId from config (NOT the truncated version from auth token)
|
|
352
599
|
// Config has full UUID like "d4532ba0-0a86-42ce-bab4-22aa62b55ce6"
|
|
@@ -366,7 +613,10 @@ function getEkkosEnv() {
|
|
|
366
613
|
// CRITICAL: Embed user/session in URL path since ANTHROPIC_HEADERS doesn't work
|
|
367
614
|
// Claude Code SDK doesn't forward custom headers, but it DOES use ANTHROPIC_BASE_URL
|
|
368
615
|
// Gateway extracts from URL: /proxy/{userId}/{sessionName}/v1/messages
|
|
369
|
-
const proxyUrl = (0, proxy_url_1.buildProxyUrl)(userId, cliSessionName, process.cwd(), cliSessionId
|
|
616
|
+
const proxyUrl = (0, proxy_url_1.buildProxyUrl)(userId, cliSessionName, process.cwd(), cliSessionId, {
|
|
617
|
+
claudeCodeVersion: runtimeClaudeCodeVersion || undefined,
|
|
618
|
+
claudeContextWindow: runtimeClaudeContextWindow,
|
|
619
|
+
});
|
|
370
620
|
env.ANTHROPIC_BASE_URL = proxyUrl;
|
|
371
621
|
// Proxy URL contains userId + project path — don't leak to terminal
|
|
372
622
|
}
|
|
@@ -504,7 +754,9 @@ function resolveClaudePath() {
|
|
|
504
754
|
// No system Claude found — fall through to ekkOS-managed install
|
|
505
755
|
}
|
|
506
756
|
// ekkOS-managed installation (for pinned versions or no system Claude)
|
|
507
|
-
|
|
757
|
+
// When 'latest', skip the slow `claude --version` subprocess — any version is fine.
|
|
758
|
+
const ekkosClaudeExists = fs.existsSync(EKKOS_CLAUDE_BIN);
|
|
759
|
+
if (ekkosClaudeExists && (PINNED_CLAUDE_VERSION === 'latest' || checkClaudeVersion(EKKOS_CLAUDE_BIN))) {
|
|
508
760
|
return EKKOS_CLAUDE_BIN;
|
|
509
761
|
}
|
|
510
762
|
// Auto-install to ekkOS-managed directory
|
|
@@ -575,6 +827,105 @@ function resolveGlobalClaudePath() {
|
|
|
575
827
|
function sleep(ms) {
|
|
576
828
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
577
829
|
}
|
|
830
|
+
function formatPulseLaunchMode(options) {
|
|
831
|
+
if (options.resumeSession)
|
|
832
|
+
return `RESUME // ${options.resumeSession}`;
|
|
833
|
+
if (options.continueLast)
|
|
834
|
+
return 'CONTINUE // most recent Claude thread';
|
|
835
|
+
return 'FRESH // new interactive shell';
|
|
836
|
+
}
|
|
837
|
+
function formatPulseModel(model) {
|
|
838
|
+
const normalized = (model || '').trim().toLowerCase();
|
|
839
|
+
if (!normalized)
|
|
840
|
+
return 'Claude default';
|
|
841
|
+
if (normalized === 'opus')
|
|
842
|
+
return 'Opus alias';
|
|
843
|
+
if (normalized === 'sonnet')
|
|
844
|
+
return 'Sonnet alias';
|
|
845
|
+
if (normalized === 'claude-opus-4-5')
|
|
846
|
+
return 'Claude Opus 4.5';
|
|
847
|
+
if (normalized === 'claude-opus-4-6')
|
|
848
|
+
return 'Claude Opus 4.6';
|
|
849
|
+
if (normalized === 'claude-sonnet-4-5')
|
|
850
|
+
return 'Claude Sonnet 4.5';
|
|
851
|
+
if (normalized === 'claude-sonnet-4-6')
|
|
852
|
+
return 'Claude Sonnet 4.6';
|
|
853
|
+
if (normalized === 'claude-haiku-4-5')
|
|
854
|
+
return 'Claude Haiku 4.5';
|
|
855
|
+
return model || 'Claude default';
|
|
856
|
+
}
|
|
857
|
+
function formatPulseContextLine(contextWindow) {
|
|
858
|
+
if (contextWindow === '200k') {
|
|
859
|
+
return '200K // 200,000 token window // forced 32,768 output';
|
|
860
|
+
}
|
|
861
|
+
if (contextWindow === '1m') {
|
|
862
|
+
return '1M // 1,000,000 token window // unlocked 128,000 output';
|
|
863
|
+
}
|
|
864
|
+
return 'AUTO // ekkOS resolves 200K vs 1M from the selected profile';
|
|
865
|
+
}
|
|
866
|
+
async function showPulseLaunchLoader(options) {
|
|
867
|
+
const cyan = chalk_1.default.hex('#00f0ff');
|
|
868
|
+
const amber = chalk_1.default.hex('#ffb800');
|
|
869
|
+
const green = chalk_1.default.hex('#00ff88');
|
|
870
|
+
const steel = chalk_1.default.hex('#7a7a8e');
|
|
871
|
+
const panel = chalk_1.default.hex('#111118');
|
|
872
|
+
const wordmark = [
|
|
873
|
+
'██████╗ ██╗ ██╗██╗ ███████╗███████╗',
|
|
874
|
+
'██╔══██╗██║ ██║██║ ██╔════╝██╔════╝',
|
|
875
|
+
'██████╔╝██║ ██║██║ ███████╗█████╗ ',
|
|
876
|
+
'██╔═══╝ ██║ ██║██║ ╚════██║██╔══╝ ',
|
|
877
|
+
'██║ ╚██████╔╝███████╗███████║███████╗',
|
|
878
|
+
'╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝',
|
|
879
|
+
];
|
|
880
|
+
const stages = [
|
|
881
|
+
'arming pulse launcher',
|
|
882
|
+
'syncing dashboard split',
|
|
883
|
+
'routing claude session',
|
|
884
|
+
];
|
|
885
|
+
const barWidth = 26;
|
|
886
|
+
const panelInnerWidth = 76;
|
|
887
|
+
const buildPanelLine = (labelColor, label, value) => {
|
|
888
|
+
const content = ` ${label.padEnd(7)} ${value}`;
|
|
889
|
+
const pad = Math.max(0, panelInnerWidth - stripAnsi(content).length);
|
|
890
|
+
return ` ${cyan('║')} ${labelColor(label.padEnd(7))} ${chalk_1.default.white(value)}${' '.repeat(pad)}${cyan('║')}`;
|
|
891
|
+
};
|
|
892
|
+
const headerContent = ' ekkOS.dev // PULSE BOOTSTRAP live launch matrix for Claude + dashboard';
|
|
893
|
+
const headerPad = Math.max(0, panelInnerWidth - headerContent.length);
|
|
894
|
+
console.log('');
|
|
895
|
+
for (const [idx, line] of wordmark.entries()) {
|
|
896
|
+
const color = idx % 2 === 0 ? cyan.bold : chalk_1.default.white.bold;
|
|
897
|
+
console.log(' ' + color(line));
|
|
898
|
+
await sleep(22);
|
|
899
|
+
}
|
|
900
|
+
console.log(' ' + amber('▔').repeat(54));
|
|
901
|
+
console.log(cyan(' ╔══════════════════════════════════════════════════════════════════════════════╗'));
|
|
902
|
+
console.log(` ${cyan('║')}${chalk_1.default.white.bold(' ekkOS.dev // PULSE BOOTSTRAP')}${steel(' live launch matrix for Claude + dashboard')}${' '.repeat(headerPad)}${cyan('║')}`);
|
|
903
|
+
console.log(buildPanelLine(green, 'MODE', formatPulseLaunchMode(options)));
|
|
904
|
+
console.log(buildPanelLine(green, 'MODEL', formatPulseModel(typeof options.model === 'string' ? options.model : undefined)));
|
|
905
|
+
console.log(buildPanelLine(amber, 'WINDOW', formatPulseContextLine(normalizeContextWindowOption(options.contextWindow))));
|
|
906
|
+
console.log(buildPanelLine(steel, 'PATH', 'selector -> dashboard -> claude -> proxy'));
|
|
907
|
+
console.log(cyan(' ╚══════════════════════════════════════════════════════════════════════════════╝'));
|
|
908
|
+
console.log(` ${green('200K')} ${steel('// Opus/Sonnet 4.5/4.6 => 32,768 output')}`);
|
|
909
|
+
console.log(` ${amber('1M ')} ${steel('// Opus/Sonnet 4.6 => 128,000 output')}`);
|
|
910
|
+
console.log('');
|
|
911
|
+
for (const stage of stages) {
|
|
912
|
+
for (let filled = 0; filled <= barWidth; filled += 1) {
|
|
913
|
+
const complete = '█'.repeat(filled);
|
|
914
|
+
const pending = '░'.repeat(barWidth - filled);
|
|
915
|
+
process.stdout.write(`\r ${amber('[')}${cyan(complete)}${steel(pending)}${amber(']')} ${chalk_1.default.white(stage)}`);
|
|
916
|
+
await sleep(16);
|
|
917
|
+
}
|
|
918
|
+
process.stdout.write('\n');
|
|
919
|
+
}
|
|
920
|
+
console.log(` ${green('READY')} ${panel('dashboard split armed')} ${steel('// selector handoff imminent')}`);
|
|
921
|
+
if (options.bypass) {
|
|
922
|
+
console.log(` ${amber('BYPASS')} ${chalk_1.default.white('permission prompts disabled for this launch')}`);
|
|
923
|
+
}
|
|
924
|
+
if (options.verbose) {
|
|
925
|
+
console.log(` ${steel('TRACE')} ${steel('verbose telemetry enabled')}`);
|
|
926
|
+
}
|
|
927
|
+
console.log('');
|
|
928
|
+
}
|
|
578
929
|
/**
|
|
579
930
|
* Show morning dreams with WOW factor right after sparkle animation.
|
|
580
931
|
* Colorful, lively, readable — real magic moment. Then pause for Enter.
|
|
@@ -811,6 +1162,9 @@ function writeSessionFiles(sessionId, sessionName) {
|
|
|
811
1162
|
* Launch ekkos run + dashboard in isolated tmux panes (60/40 split)
|
|
812
1163
|
*/
|
|
813
1164
|
function launchWithDashboard(options) {
|
|
1165
|
+
// Prune dead-PID entries from active-sessions.json so the dashboard
|
|
1166
|
+
// (and resolveSessionName) never cross-bind to stale sessions after restart.
|
|
1167
|
+
(0, state_1.getActiveSessions)();
|
|
814
1168
|
const tmuxSession = `ekkos-${Date.now().toString(36)}`;
|
|
815
1169
|
const launchTime = Date.now();
|
|
816
1170
|
// Pre-generate session name so dashboard can start immediately (no polling).
|
|
@@ -831,6 +1185,15 @@ function launchWithDashboard(options) {
|
|
|
831
1185
|
runArgs.push('--skip-inject');
|
|
832
1186
|
if (options.noProxy)
|
|
833
1187
|
runArgs.push('--skip-proxy');
|
|
1188
|
+
if (options.continueLast)
|
|
1189
|
+
runArgs.push('--continue-last');
|
|
1190
|
+
if (options.resumeSession)
|
|
1191
|
+
runArgs.push('--resume-session', options.resumeSession);
|
|
1192
|
+
if (typeof options.model === 'string')
|
|
1193
|
+
runArgs.push('--model', options.model);
|
|
1194
|
+
if (options.contextWindow && options.contextWindow !== 'auto') {
|
|
1195
|
+
runArgs.push('--context-window', options.contextWindow);
|
|
1196
|
+
}
|
|
834
1197
|
const ekkosCmd = process.argv[1]; // Path to ekkos CLI
|
|
835
1198
|
const cwd = process.cwd();
|
|
836
1199
|
const termCols = process.stdout.columns ?? 160;
|
|
@@ -916,6 +1279,15 @@ function launchWithWindowsTerminal(options) {
|
|
|
916
1279
|
runArgs.push('--skip-inject');
|
|
917
1280
|
if (options.noProxy)
|
|
918
1281
|
runArgs.push('--skip-proxy');
|
|
1282
|
+
if (options.continueLast)
|
|
1283
|
+
runArgs.push('--continue-last');
|
|
1284
|
+
if (options.resumeSession)
|
|
1285
|
+
runArgs.push('--resume-session', options.resumeSession);
|
|
1286
|
+
if (typeof options.model === 'string')
|
|
1287
|
+
runArgs.push('--model', options.model);
|
|
1288
|
+
if (options.contextWindow && options.contextWindow !== 'auto') {
|
|
1289
|
+
runArgs.push('--context-window', options.contextWindow);
|
|
1290
|
+
}
|
|
919
1291
|
// Write a temp batch file to avoid all quoting issues
|
|
920
1292
|
// wt.exe doesn't resolve PATH for npm global bins — must use `cmd /c`
|
|
921
1293
|
const batPath = path.join(os.tmpdir(), `ekkos-wt-${Date.now()}.cmd`);
|
|
@@ -963,6 +1335,14 @@ async function run(options) {
|
|
|
963
1335
|
const verbose = options.verbose || false;
|
|
964
1336
|
const bypass = options.bypass || false;
|
|
965
1337
|
const noInject = options.noInject || false;
|
|
1338
|
+
const suppressPreClaudeOutput = process.env.EKKOS_NO_SPLASH === '1';
|
|
1339
|
+
const launchSelection = await resolveClaudeLaunchSelection(options);
|
|
1340
|
+
options.model = launchSelection.model;
|
|
1341
|
+
options.contextWindow = launchSelection.contextWindow;
|
|
1342
|
+
options.continueLast = launchSelection.continueLast;
|
|
1343
|
+
options.resumeSession = launchSelection.resumeSession;
|
|
1344
|
+
runtimeClaudeLaunchModel = launchSelection.model;
|
|
1345
|
+
runtimeClaudeContextWindow = launchSelection.contextWindow;
|
|
966
1346
|
// Honour -s flag: lock the session name before getEkkosEnv() can mint a new one.
|
|
967
1347
|
// This is critical for --dashboard mode where launchWithDashboard() pre-generates
|
|
968
1348
|
// a session name and passes it via -s so the dashboard and run command agree.
|
|
@@ -973,11 +1353,28 @@ async function run(options) {
|
|
|
973
1353
|
// Set proxy mode based on options (used by getEkkosEnv)
|
|
974
1354
|
proxyModeEnabled = !(options.noProxy || false);
|
|
975
1355
|
if (proxyModeEnabled) {
|
|
976
|
-
|
|
1356
|
+
if (!suppressPreClaudeOutput && !options.pulse) {
|
|
1357
|
+
console.log(chalk_1.default.cyan(' 🧠 ekkOS_Continuum Loaded!'));
|
|
1358
|
+
}
|
|
977
1359
|
}
|
|
978
|
-
else if (verbose) {
|
|
1360
|
+
else if (verbose && !suppressPreClaudeOutput) {
|
|
979
1361
|
console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled (--no-proxy)'));
|
|
980
1362
|
}
|
|
1363
|
+
let pulseIntroShown = false;
|
|
1364
|
+
if (options.pulse
|
|
1365
|
+
&& process.env.EKKOS_REMOTE_SESSION !== '1'
|
|
1366
|
+
&& process.env.EKKOS_NO_SPLASH !== '1'
|
|
1367
|
+
&& process.stdout.isTTY === true) {
|
|
1368
|
+
await showPulseLaunchLoader({
|
|
1369
|
+
model: options.model,
|
|
1370
|
+
contextWindow: options.contextWindow,
|
|
1371
|
+
continueLast: options.continueLast,
|
|
1372
|
+
resumeSession: options.resumeSession,
|
|
1373
|
+
bypass: options.bypass,
|
|
1374
|
+
verbose: options.verbose,
|
|
1375
|
+
});
|
|
1376
|
+
pulseIntroShown = true;
|
|
1377
|
+
}
|
|
981
1378
|
// ══════════════════════════════════════════════════════════════════════════
|
|
982
1379
|
// DASHBOARD MODE: Launch via tmux (Unix) or Windows Terminal split pane
|
|
983
1380
|
// ══════════════════════════════════════════════════════════════════════════
|
|
@@ -1043,16 +1440,45 @@ async function run(options) {
|
|
|
1043
1440
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1044
1441
|
(0, state_1.ensureEkkosDir)();
|
|
1045
1442
|
(0, state_1.clearAutoClearFlag)();
|
|
1443
|
+
// Construct manager early but defer .start() until Claude's first idle prompt
|
|
1444
|
+
// (filesystem scans run only after Claude is fully loaded and waiting for input)
|
|
1445
|
+
let localLivingDocsManager = null;
|
|
1446
|
+
if (process.env.EKKOS_LOCAL_LIVING_DOCS !== '0') {
|
|
1447
|
+
try {
|
|
1448
|
+
localLivingDocsManager = new living_docs_manager_js_1.LocalLivingDocsManager({
|
|
1449
|
+
targetPath: process.cwd(),
|
|
1450
|
+
apiUrl: process.env.EKKOS_API_URL || MEMORY_API_URL,
|
|
1451
|
+
apiKey: (0, state_1.getAuthToken)(),
|
|
1452
|
+
timeZone: process.env.EKKOS_USER_TIMEZONE || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
1453
|
+
onLog: message => dlog(message),
|
|
1454
|
+
});
|
|
1455
|
+
// .start() deferred to first idle prompt — see idle detection below (~line 2685)
|
|
1456
|
+
}
|
|
1457
|
+
catch (err) {
|
|
1458
|
+
dlog(`[LivingDocs:Local] Failed to start: ${err.message}`);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1046
1461
|
// Resolve Claude path
|
|
1047
1462
|
const rawClaudePath = resolveClaudePath();
|
|
1048
1463
|
const isNpxMode = rawClaudePath.startsWith('npx:');
|
|
1049
1464
|
const pinnedVersion = isNpxMode ? rawClaudePath.split(':')[1] : null;
|
|
1050
1465
|
const claudePath = isNpxMode ? 'npx' : rawClaudePath;
|
|
1466
|
+
runtimeClaudeCodeVersion = pinnedVersion || getClaudeVersion(claudePath);
|
|
1051
1467
|
// Build args early
|
|
1052
1468
|
const earlyArgs = [];
|
|
1053
1469
|
if (isNpxMode) {
|
|
1054
1470
|
earlyArgs.push(`@anthropic-ai/claude-code@${pinnedVersion}`);
|
|
1055
1471
|
}
|
|
1472
|
+
if (options.resumeSession) {
|
|
1473
|
+
earlyArgs.push('--resume', options.resumeSession);
|
|
1474
|
+
}
|
|
1475
|
+
else if (options.continueLast) {
|
|
1476
|
+
earlyArgs.push('--continue');
|
|
1477
|
+
}
|
|
1478
|
+
const launchModelArg = buildClaudeLaunchModelArg(options.model, options.contextWindow);
|
|
1479
|
+
if (launchModelArg) {
|
|
1480
|
+
earlyArgs.push('--model', launchModelArg);
|
|
1481
|
+
}
|
|
1056
1482
|
if (bypass) {
|
|
1057
1483
|
earlyArgs.push('--dangerously-skip-permissions');
|
|
1058
1484
|
}
|
|
@@ -1115,7 +1541,7 @@ async function run(options) {
|
|
|
1115
1541
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1116
1542
|
// STARTUP BANNER WITH COLOR PULSE ANIMATION
|
|
1117
1543
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1118
|
-
const skipFancyIntro = process.env.EKKOS_REMOTE_SESSION === '1' ||
|
|
1544
|
+
const skipFancyIntro = pulseIntroShown || process.env.EKKOS_REMOTE_SESSION === '1' || suppressPreClaudeOutput || !!process.env.TMUX;
|
|
1119
1545
|
if (!skipFancyIntro) {
|
|
1120
1546
|
const logoLines = [
|
|
1121
1547
|
' ▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄ ▄▄',
|
|
@@ -1297,7 +1723,7 @@ async function run(options) {
|
|
|
1297
1723
|
}
|
|
1298
1724
|
console.log('');
|
|
1299
1725
|
}
|
|
1300
|
-
else {
|
|
1726
|
+
else if (!suppressPreClaudeOutput) {
|
|
1301
1727
|
// Static banner for Windows / remote / no-splash — no cursor manipulation
|
|
1302
1728
|
console.log('');
|
|
1303
1729
|
console.log(chalk_1.default.hex('#FF6B35').bold(' ekkOS_Pulse') + chalk_1.default.gray(' — Context is finite. Intelligence isn\'t.'));
|
|
@@ -1313,7 +1739,9 @@ async function run(options) {
|
|
|
1313
1739
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1314
1740
|
// MAGIC MOMENT: Morning dreams right after sparkle, before Claude appears
|
|
1315
1741
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1316
|
-
|
|
1742
|
+
if (!suppressPreClaudeOutput && !options.pulse) {
|
|
1743
|
+
await showMorningDreamsIfNeeded();
|
|
1744
|
+
}
|
|
1317
1745
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1318
1746
|
// ANIMATION COMPLETE: Mark ready and flush buffered Claude output
|
|
1319
1747
|
// ══════════════════════════════════════════════════════════════════════════
|
|
@@ -1321,6 +1749,7 @@ async function run(options) {
|
|
|
1321
1749
|
// Clear terminal to prevent startup banner artifacts bleeding into Claude Code's Ink TUI
|
|
1322
1750
|
process.stdout.write('\x1B[2J\x1B[H');
|
|
1323
1751
|
dlog(`Animation complete. shellReady=${shellReady}, buffered=${bufferedOutput.length} chunks`);
|
|
1752
|
+
// Living docs .start() is deferred further — triggers on first idle prompt (Claude fully loaded)
|
|
1324
1753
|
// Show loading indicator if Claude is still initializing
|
|
1325
1754
|
if (shellReady && bufferedOutput.length === 0) {
|
|
1326
1755
|
process.stdout.write(chalk_1.default.gray(' Connecting to Claude...'));
|
|
@@ -1367,6 +1796,7 @@ async function run(options) {
|
|
|
1367
1796
|
// Tracks idle→active transitions to print the session banner once per turn
|
|
1368
1797
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1369
1798
|
let wasIdle = false; // Start as NOT idle — first idle→active fires after startup
|
|
1799
|
+
let livingDocsStarted = false; // Deferred until Claude's first idle prompt
|
|
1370
1800
|
let turnCount = 0; // Incremented each time a new turn banner prints
|
|
1371
1801
|
let lastBannerTime = Date.now(); // Grace period so startup output doesn't trigger banner
|
|
1372
1802
|
// Stream tailer for mid-turn context capture (must be declared before polling code)
|
|
@@ -1802,9 +2232,21 @@ async function run(options) {
|
|
|
1802
2232
|
}
|
|
1803
2233
|
if (verbose) {
|
|
1804
2234
|
// Show Claude version
|
|
1805
|
-
const ccVersion = pinnedVersion || PINNED_CLAUDE_VERSION;
|
|
2235
|
+
const ccVersion = runtimeClaudeCodeVersion || pinnedVersion || PINNED_CLAUDE_VERSION;
|
|
1806
2236
|
const versionStr = `Claude Code v${ccVersion}`;
|
|
1807
2237
|
console.log(chalk_1.default.gray(` 🤖 ${versionStr}`));
|
|
2238
|
+
if (options.model) {
|
|
2239
|
+
console.log(chalk_1.default.gray(` 🧩 Model: ${buildClaudeLaunchModelArg(options.model, options.contextWindow)}`));
|
|
2240
|
+
}
|
|
2241
|
+
if (options.contextWindow && options.contextWindow !== 'auto') {
|
|
2242
|
+
console.log(chalk_1.default.gray(` 🪟 Context: ${options.contextWindow}`));
|
|
2243
|
+
}
|
|
2244
|
+
if (options.continueLast) {
|
|
2245
|
+
console.log(chalk_1.default.gray(' ↩ Continue: most recent Claude conversation'));
|
|
2246
|
+
}
|
|
2247
|
+
if (options.resumeSession) {
|
|
2248
|
+
console.log(chalk_1.default.gray(` ↪ Resume: ${options.resumeSession}`));
|
|
2249
|
+
}
|
|
1808
2250
|
if (currentSession) {
|
|
1809
2251
|
console.log(chalk_1.default.green(` 📍 Session: ${currentSession}`));
|
|
1810
2252
|
}
|
|
@@ -2324,6 +2766,20 @@ async function run(options) {
|
|
|
2324
2766
|
// We check the raw outputBuffer (stripped) so the idle regex fires reliably.
|
|
2325
2767
|
if (IDLE_PROMPT_REGEX.test(stripAnsi(outputBuffer))) {
|
|
2326
2768
|
wasIdle = true;
|
|
2769
|
+
// Start living docs on first idle prompt — Claude is fully loaded, won't compete for I/O
|
|
2770
|
+
if (!livingDocsStarted && localLivingDocsManager) {
|
|
2771
|
+
livingDocsStarted = true;
|
|
2772
|
+
// Use setImmediate so the current data handler returns first
|
|
2773
|
+
setImmediate(() => {
|
|
2774
|
+
try {
|
|
2775
|
+
localLivingDocsManager.start();
|
|
2776
|
+
dlog('[LivingDocs:Local] Manager started (first idle prompt)');
|
|
2777
|
+
}
|
|
2778
|
+
catch (err) {
|
|
2779
|
+
dlog(`[LivingDocs:Local] Failed to start: ${err.message}`);
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2327
2783
|
}
|
|
2328
2784
|
// ══════════════════════════════════════════════════════════════════════════
|
|
2329
2785
|
// ORPHAN TOOL_RESULT DETECTION (LOCAL MODE ONLY)
|
|
@@ -2711,6 +3167,7 @@ Use Perplexity for deep research. Be thorough but efficient. Start now.`;
|
|
|
2711
3167
|
// Handle PTY exit
|
|
2712
3168
|
shell.onExit(async ({ exitCode }) => {
|
|
2713
3169
|
(0, state_1.clearAutoClearFlag)();
|
|
3170
|
+
localLivingDocsManager?.stop();
|
|
2714
3171
|
stopStreamTailer(); // Stop stream capture
|
|
2715
3172
|
(0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
|
|
2716
3173
|
cleanupInstanceFile(instanceId); // Clean up instance file
|
|
@@ -2730,6 +3187,7 @@ Use Perplexity for deep research. Be thorough but efficient. Start now.`;
|
|
|
2730
3187
|
// Cleanup on exit signals
|
|
2731
3188
|
const cleanup = () => {
|
|
2732
3189
|
(0, state_1.clearAutoClearFlag)();
|
|
3190
|
+
localLivingDocsManager?.stop();
|
|
2733
3191
|
stopStreamTailer(); // Stop stream capture
|
|
2734
3192
|
(0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
|
|
2735
3193
|
cleanupInstanceFile(instanceId); // Clean up instance file
|