@jmylchreest/aide-plugin 0.0.56 → 0.0.58

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.
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session End Hook (SessionEnd)
4
+ *
5
+ * Delegates cleanup to `aide session end` — single binary invocation.
6
+ *
7
+ * When invoked from Codex CLI's Stop hook (which has no separate SessionEnd),
8
+ * checks whether autopilot mode is active and skips cleanup if so — the
9
+ * session is continuing, not ending.
10
+ *
11
+ * CONSTRAINTS:
12
+ * - No ES module imports (hoisted resolution adds ~3s)
13
+ * - Output {"continue": true} before require() resolves
14
+ * - Cleanup via detached spawn to avoid blocking exit
15
+ * - MUST complete well within Claude Code's ~1.5s hook timeout
16
+ *
17
+ * STDIN:
18
+ * Claude Code pipes JSON via stdin and closes the pipe. The documented
19
+ * payload includes session_id, but in practice Claude Code often sends
20
+ * just `{}`. Bun's `for await` async iterator on stdin hangs even when
21
+ * the pipe is closed — use synchronous readFileSync(0) instead.
22
+ */
23
+
24
+ const T0 = performance.now();
25
+
26
+ // Output continue IMMEDIATELY — before require(), before anything.
27
+ console.log(JSON.stringify({ continue: true }));
28
+
29
+ const { spawn, execFileSync } = require("child_process") as typeof import("child_process");
30
+ const { existsSync, realpathSync, appendFileSync, mkdirSync, readFileSync } = require("fs") as typeof import("fs");
31
+ const { join } = require("path") as typeof import("path");
32
+ const whichSync = (require("which") as typeof import("which")).sync;
33
+
34
+ const SESSION_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
35
+
36
+ /** Elapsed ms since T0. */
37
+ function ms(): string {
38
+ return `+${(performance.now() - T0).toFixed(0)}ms`;
39
+ }
40
+
41
+ /**
42
+ * Always log to .aide/_logs/session-end.log (NOT gated on AIDE_DEBUG).
43
+ * This hook has historically been invisible when it fails — always log.
44
+ */
45
+ function log(cwd: string, msg: string): void {
46
+ try {
47
+ const logDir = join(cwd, ".aide", "_logs");
48
+ if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
49
+ const line = `[${new Date().toISOString()}] [session-end] ${ms()} ${msg}\n`;
50
+ appendFileSync(join(logDir, "session-end.log"), line);
51
+ } catch { /* best effort */ }
52
+ }
53
+
54
+ /** Find aide binary — inline, no external module imports. */
55
+ function findBinary(cwd?: string): string | null {
56
+ const pluginRoot = process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;
57
+ let resolvedRoot = pluginRoot;
58
+ if (resolvedRoot) {
59
+ try { resolvedRoot = realpathSync(resolvedRoot); } catch { /* symlink may not resolve */ }
60
+ const p = join(resolvedRoot, "bin", "aide");
61
+ if (existsSync(p)) return p;
62
+ }
63
+ if (cwd) {
64
+ const p = join(cwd, ".aide", "bin", "aide");
65
+ if (existsSync(p)) return p;
66
+ }
67
+ try {
68
+ return whichSync("aide", { nothrow: true });
69
+ } catch { return null; }
70
+ }
71
+
72
+ function main(): void {
73
+ const cwd = process.cwd();
74
+ log(cwd, "started");
75
+
76
+ try {
77
+ // Read stdin synchronously — avoids bun's broken async iterator.
78
+ // Claude Code closes the pipe so this returns immediately.
79
+ let input = "";
80
+ try {
81
+ input = readFileSync(0, "utf-8");
82
+ } catch {
83
+ log(cwd, "stdin not readable (not a pipe?)");
84
+ }
85
+ log(cwd, `stdin (${input.length} bytes): ${input.trim().slice(0, 200)}`);
86
+
87
+ // Parse session_id from stdin JSON
88
+ let sessionId = "";
89
+ if (input.trim()) {
90
+ try {
91
+ const data = JSON.parse(input);
92
+ sessionId = data.session_id || "";
93
+ } catch {
94
+ log(cwd, "stdin is not valid JSON");
95
+ }
96
+ }
97
+
98
+ if (!sessionId) {
99
+ log(cwd, "no session_id in payload, skipping cleanup");
100
+ return;
101
+ }
102
+
103
+ if (!SESSION_ID_RE.test(sessionId)) {
104
+ log(cwd, `invalid session_id: ${sessionId}`);
105
+ return;
106
+ }
107
+
108
+ const binary = findBinary(cwd);
109
+ log(cwd, `binary: ${binary}`);
110
+ if (!binary) {
111
+ log(cwd, "no binary found, skipping cleanup");
112
+ return;
113
+ }
114
+
115
+ // Mode guard: skip cleanup if autopilot is active (session is continuing,
116
+ // not ending). This matters when Codex CLI invokes session-end from Stop
117
+ // hook since there's no separate SessionEnd event.
118
+ try {
119
+ const mode = execFileSync(binary, ["state", "get", "mode"], {
120
+ cwd, timeout: 500, encoding: "utf-8",
121
+ }).trim();
122
+ if (mode === "autopilot") {
123
+ log(cwd, "autopilot mode active, skipping cleanup (session continuing)");
124
+ return;
125
+ }
126
+ } catch {
127
+ // Binary may not support 'state get' or state may not exist — proceed
128
+ }
129
+
130
+ log(cwd, `spawning cleanup: session end --session=${sessionId}`);
131
+ const child = spawn(binary, ["session", "end", `--session=${sessionId}`], {
132
+ cwd, detached: true, stdio: "ignore",
133
+ });
134
+ child.unref();
135
+ log(cwd, "cleanup spawned (detached)");
136
+ } catch (error) {
137
+ log(cwd, `error: ${error}`);
138
+ }
139
+
140
+ log(cwd, `done ${ms()}`);
141
+ }
142
+
143
+ // On SIGINT/SIGTERM, exit cleanly (continue already output).
144
+ process.on("SIGINT", () => process.exit(0));
145
+ process.on("SIGTERM", () => process.exit(0));
146
+
147
+ main();
148
+ process.exit(0);
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session Start Hook (SessionStart)
4
+ *
5
+ * Initializes aide state and configuration on session start.
6
+ * - Creates .aide directories if needed
7
+ * - Loads config files
8
+ * - Initializes HUD state
9
+ * - Runs `aide session init` (single binary call for state reset, cleanup, memory fetch)
10
+ *
11
+ * Debug logging: Set AIDE_DEBUG=1 to enable startup tracing
12
+ * Logs written to: .aide/_logs/startup.log
13
+ */
14
+
15
+ import {
16
+ existsSync,
17
+ readFileSync,
18
+ writeFileSync,
19
+ mkdirSync,
20
+ copyFileSync,
21
+ chmodSync,
22
+ } from "fs";
23
+ import { join, dirname } from "path";
24
+ import { fileURLToPath } from "url";
25
+ import { homedir } from "os";
26
+ import { Logger, debug, setDebugCwd } from "../lib/logger.js";
27
+ import { readStdin, detectPlatform } from "../lib/hook-utils.js";
28
+ import { findAideBinary, ensureAideBinary } from "../lib/aide-downloader.js";
29
+ import {
30
+ ensureDirectories as coreEnsureDirectories,
31
+ loadConfig as coreLoadConfig,
32
+ initializeSession as coreInitializeSession,
33
+ cleanupStaleStateFiles as coreCleanupStaleStateFiles,
34
+ resetHudState as coreResetHudState,
35
+ getProjectName,
36
+ runSessionInit as coreRunSessionInit,
37
+ buildWelcomeContext as coreBuildWelcomeContext,
38
+ formatTimeAgo,
39
+ } from "../core/session-init.js";
40
+ import { syncMcpServers } from "../core/mcp-sync.js";
41
+ import type {
42
+ AideConfig,
43
+ SessionState,
44
+ SessionInitResult,
45
+ MemoryInjection,
46
+ StartupNotices,
47
+ } from "../core/types.js";
48
+
49
+ const SOURCE = "session-start";
50
+ debug(SOURCE, `Hook started (AIDE_DEBUG=${process.env.AIDE_DEBUG || "unset"})`);
51
+
52
+ interface HookInput {
53
+ hook_event_name: string;
54
+ session_id: string;
55
+ cwd: string;
56
+ transcript_path?: string;
57
+ permission_mode?: string;
58
+ }
59
+
60
+ interface HookOutput {
61
+ continue: boolean;
62
+ hookSpecificOutput?: {
63
+ hookEventName: string;
64
+ additionalContext?: string;
65
+ };
66
+ }
67
+
68
+ interface BinaryCheckResult {
69
+ binary: string | null;
70
+ error: string | null;
71
+ warning: string | null;
72
+ downloaded: boolean;
73
+ }
74
+
75
+ /**
76
+ * Check for aide binary with logging, version checking, and auto-download
77
+ */
78
+ async function checkAideBinary(
79
+ cwd: string,
80
+ log: Logger,
81
+ ): Promise<BinaryCheckResult> {
82
+ log.start("checkAideBinary");
83
+
84
+ const result = await ensureAideBinary(cwd);
85
+
86
+ if (result.binary) {
87
+ if (result.downloaded) {
88
+ log.info(`aide binary downloaded successfully to ${result.binary}`);
89
+ }
90
+ if (result.warning) {
91
+ log.info("aide update available");
92
+ }
93
+ log.end("checkAideBinary", {
94
+ found: true,
95
+ path: result.binary,
96
+ downloaded: result.downloaded,
97
+ hasWarning: !!result.warning,
98
+ });
99
+ return result;
100
+ }
101
+
102
+ log.warn("aide binary not found and download failed");
103
+ log.end("checkAideBinary", { found: false });
104
+
105
+ return result;
106
+ }
107
+
108
+ /**
109
+ * Reset HUD state file for clean session start — delegates to core
110
+ */
111
+ function resetHudState(cwd: string, log: Logger): void {
112
+ log.start("resetHudState");
113
+ try {
114
+ coreResetHudState(cwd);
115
+ log.end("resetHudState", { success: true });
116
+ } catch (err) {
117
+ log.warn("Failed to reset HUD state", err);
118
+ log.end("resetHudState", { success: false, error: String(err) });
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Get the plugin root directory
124
+ */
125
+ function getPluginRoot(): string | null {
126
+ // Check AIDE_PLUGIN_ROOT or CLAUDE_PLUGIN_ROOT env var
127
+ const envRoot =
128
+ process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;
129
+ if (envRoot && existsSync(join(envRoot, "package.json"))) {
130
+ return envRoot;
131
+ }
132
+
133
+ // Calculate from this script's location: dist/hooks/session-start.js -> ../../
134
+ try {
135
+ const scriptPath = fileURLToPath(import.meta.url);
136
+ const pluginRoot = join(dirname(scriptPath), "..", "..");
137
+ if (existsSync(join(pluginRoot, "package.json"))) {
138
+ return pluginRoot;
139
+ }
140
+ } catch {
141
+ // import.meta.url not available
142
+ }
143
+
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Install the HUD wrapper script to ~/.claude/bin/
149
+ *
150
+ * This installs a thin wrapper that delegates to the real HUD script in the plugin.
151
+ * The wrapper allows plugin updates to take effect without reinstalling.
152
+ */
153
+ function installHudWrapper(log: Logger): void {
154
+ log.start("installHudWrapper");
155
+
156
+ const claudeBinDir = join(homedir(), ".claude", "bin");
157
+ const wrapperDest = join(claudeBinDir, "aide-hud.ts");
158
+
159
+ // Check if wrapper already exists
160
+ if (existsSync(wrapperDest)) {
161
+ log.debug("HUD wrapper already installed");
162
+ log.end("installHudWrapper", { skipped: true, reason: "exists" });
163
+ return;
164
+ }
165
+
166
+ // Find our wrapper source
167
+ const pluginRoot = getPluginRoot();
168
+ if (!pluginRoot) {
169
+ log.warn("Could not determine plugin root, skipping HUD wrapper install");
170
+ log.end("installHudWrapper", { skipped: true, reason: "no-plugin-root" });
171
+ return;
172
+ }
173
+
174
+ const wrapperSrc = join(pluginRoot, "scripts", "aide-hud-wrapper.ts");
175
+ if (!existsSync(wrapperSrc)) {
176
+ log.warn(`HUD wrapper source not found: ${wrapperSrc}`);
177
+ log.end("installHudWrapper", { skipped: true, reason: "no-source" });
178
+ return;
179
+ }
180
+
181
+ try {
182
+ // Create ~/.claude/bin if needed
183
+ if (!existsSync(claudeBinDir)) {
184
+ mkdirSync(claudeBinDir, { recursive: true });
185
+ }
186
+
187
+ // Copy wrapper script
188
+ copyFileSync(wrapperSrc, wrapperDest);
189
+ try { chmodSync(wrapperDest, 0o755); } catch { /* no-op on Windows */ }
190
+
191
+ log.info(`Installed HUD wrapper to ${wrapperDest}`);
192
+ log.end("installHudWrapper", { success: true, path: wrapperDest });
193
+ } catch (err) {
194
+ log.warn("Failed to install HUD wrapper", err);
195
+ log.end("installHudWrapper", { success: false, error: String(err) });
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Ensure all .aide directories exist — delegates to core
201
+ */
202
+ function ensureDirectories(cwd: string, log: Logger): void {
203
+ log.start("ensureDirectories");
204
+ const { created, existed } = coreEnsureDirectories(cwd);
205
+ log.end("ensureDirectories", { created, existed });
206
+ }
207
+
208
+ /**
209
+ * Load or create config file — delegates to core
210
+ */
211
+ function loadConfig(cwd: string, log: Logger): AideConfig {
212
+ log.start("loadConfig");
213
+ const config = coreLoadConfig(cwd);
214
+ log.end("loadConfig");
215
+ return config;
216
+ }
217
+
218
+ /**
219
+ * Clean up stale state files — delegates to core
220
+ */
221
+ function cleanupStaleStateFiles(cwd: string, log: Logger): void {
222
+ log.start("cleanupStaleStateFiles");
223
+ const { scanned, deleted } = coreCleanupStaleStateFiles(cwd);
224
+ log.end("cleanupStaleStateFiles", { scanned, deleted });
225
+ }
226
+
227
+ /**
228
+ * Initialize session state — delegates to core
229
+ */
230
+ function initializeSession(
231
+ sessionId: string,
232
+ cwd: string,
233
+ log: Logger,
234
+ ): SessionState {
235
+ log.start("initializeSession");
236
+ const state = coreInitializeSession(sessionId, cwd);
237
+ log.end("initializeSession", { sessionId: sessionId.slice(0, 8) });
238
+ return state;
239
+ }
240
+
241
+ // getProjectName, runSessionInit, formatTimeAgo, buildWelcomeContext
242
+ // are now imported from ../core/session-init.js above.
243
+ // The runSessionInit wrapper below adds logging around the core function.
244
+
245
+ /**
246
+ * Run session init with logging — wraps core function
247
+ */
248
+ function runSessionInit(
249
+ cwd: string,
250
+ projectName: string,
251
+ sessionLimit: number,
252
+ log: Logger,
253
+ config?: AideConfig,
254
+ ): MemoryInjection {
255
+ log.start("sessionInit");
256
+
257
+ const binary = findAideBinary(cwd);
258
+ if (!binary) {
259
+ log.debug("aide binary not found, skipping session init");
260
+ log.end("sessionInit", { skipped: true, reason: "no-binary" });
261
+ return {
262
+ static: { global: [], project: [], decisions: [] },
263
+ dynamic: { sessions: [] },
264
+ };
265
+ }
266
+
267
+ const result = coreRunSessionInit(
268
+ binary,
269
+ cwd,
270
+ projectName,
271
+ sessionLimit,
272
+ config,
273
+ );
274
+
275
+ log.end("sessionInit", {
276
+ globalCount: result.static.global.length,
277
+ projectCount: result.static.project.length,
278
+ decisionCount: result.static.decisions.length,
279
+ sessionCount: result.dynamic.sessions.length,
280
+ });
281
+
282
+ return result;
283
+ }
284
+
285
+ /**
286
+ * Build welcome context — wraps core function
287
+ */
288
+ function buildWelcomeContext(
289
+ state: SessionState,
290
+ memories: MemoryInjection,
291
+ notices: StartupNotices = {},
292
+ ): string {
293
+ return coreBuildWelcomeContext(state, memories, notices);
294
+ }
295
+
296
+ // Debug helper - writes to debug.log (not stderr)
297
+ function debugLog(msg: string): void {
298
+ debug(SOURCE, msg);
299
+ }
300
+
301
+ // Ensure we always output valid JSON, even on catastrophic errors
302
+ function outputContinue(): void {
303
+ try {
304
+ console.log(JSON.stringify({ continue: true }));
305
+ } catch {
306
+ // Last resort - raw JSON string
307
+ console.log('{"continue":true}');
308
+ }
309
+ }
310
+
311
+ // Global error handlers to prevent hook crashes without JSON output
312
+ process.on("uncaughtException", (err) => {
313
+ debugLog(`UNCAUGHT EXCEPTION: ${err}`);
314
+ outputContinue();
315
+ process.exit(0);
316
+ });
317
+
318
+ process.on("unhandledRejection", (reason) => {
319
+ debugLog(`UNHANDLED REJECTION: ${reason}`);
320
+ outputContinue();
321
+ process.exit(0);
322
+ });
323
+
324
+ async function main(): Promise<void> {
325
+ let log: Logger | null = null;
326
+ const hookStart = Date.now();
327
+
328
+ debugLog(`Hook started at ${new Date().toISOString()}`);
329
+
330
+ try {
331
+ debugLog("Reading stdin...");
332
+ const input = await readStdin();
333
+ debugLog(`Stdin read complete (${Date.now() - hookStart}ms)`);
334
+
335
+ if (!input.trim()) {
336
+ debugLog("Empty input, exiting");
337
+ console.log(JSON.stringify({ continue: true }));
338
+ return;
339
+ }
340
+
341
+ const data: HookInput = JSON.parse(input);
342
+ const cwd = data.cwd || process.cwd();
343
+ const sessionId = data.session_id || "unknown";
344
+
345
+ // Switch debug logging to project-local logs
346
+ setDebugCwd(cwd);
347
+
348
+ debugLog(`Parsed input: cwd=${cwd}, sessionId=${sessionId.slice(0, 8)}`);
349
+
350
+ // Initialize logger
351
+ log = new Logger("session-start", cwd);
352
+ log.info(`Session starting: ${sessionId.slice(0, 8)}`);
353
+ log.start("total");
354
+
355
+ debugLog(`Logger initialized, enabled=${log.isEnabled()}`);
356
+
357
+ // Initialize directories (FS only, fast)
358
+ debugLog("ensureDirectories starting...");
359
+ ensureDirectories(cwd, log);
360
+ debugLog(`ensureDirectories complete (${Date.now() - hookStart}ms)`);
361
+
362
+ // Install HUD wrapper script if not present (FS only, fast)
363
+ debugLog("installHudWrapper starting...");
364
+ installHudWrapper(log);
365
+ debugLog(`installHudWrapper complete (${Date.now() - hookStart}ms)`);
366
+
367
+ // Sync MCP server configs across assistants (FS only, fast)
368
+ debugLog("mcpSync starting...");
369
+ log.start("mcpSync");
370
+ try {
371
+ const mcpResult = syncMcpServers(detectPlatform(), cwd);
372
+ const totalImported =
373
+ mcpResult.user.imported + mcpResult.project.imported;
374
+ const totalWritten =
375
+ mcpResult.user.serversWritten + mcpResult.project.serversWritten;
376
+ const totalSkipped = mcpResult.user.skipped + mcpResult.project.skipped;
377
+ log.end("mcpSync", {
378
+ userServers: mcpResult.user.serversWritten,
379
+ projectServers: mcpResult.project.serversWritten,
380
+ imported: totalImported,
381
+ skipped: totalSkipped,
382
+ });
383
+ if (totalImported > 0) {
384
+ debugLog(
385
+ `mcp-sync: imported ${totalImported} server(s), ${totalWritten} total`,
386
+ );
387
+ }
388
+ } catch (err) {
389
+ log.warn("MCP sync failed (non-fatal)", err);
390
+ log.end("mcpSync", { success: false, error: String(err) });
391
+ }
392
+ debugLog(`mcpSync complete (${Date.now() - hookStart}ms)`);
393
+
394
+ // Check that aide binary is available (auto-downloads if missing/outdated)
395
+ debugLog("checkAideBinary starting...");
396
+ const {
397
+ error: binaryError,
398
+ warning: binaryWarning,
399
+ downloaded: binaryDownloaded,
400
+ } = await checkAideBinary(cwd, log);
401
+ if (binaryDownloaded) {
402
+ debugLog(`aide binary was downloaded`);
403
+ }
404
+ debugLog(`checkAideBinary complete (${Date.now() - hookStart}ms)`);
405
+
406
+ // Reset HUD state for clean session start (FS only, fast)
407
+ debugLog("resetHudState starting...");
408
+ resetHudState(cwd, log);
409
+ debugLog(`resetHudState complete (${Date.now() - hookStart}ms)`);
410
+
411
+ // Load config (FS only, fast)
412
+ debugLog("loadConfig starting...");
413
+ const config = loadConfig(cwd, log);
414
+ debugLog(`loadConfig complete (${Date.now() - hookStart}ms)`);
415
+
416
+ // Cleanup stale state files on disk (FS only, fast)
417
+ debugLog("cleanupStaleStateFiles starting...");
418
+ cleanupStaleStateFiles(cwd, log);
419
+ debugLog(`cleanupStaleStateFiles complete (${Date.now() - hookStart}ms)`);
420
+
421
+ // Initialize session state file (FS only, fast)
422
+ debugLog("initializeSession starting...");
423
+ const state = initializeSession(sessionId, cwd, log);
424
+ debugLog(`initializeSession complete (${Date.now() - hookStart}ms)`);
425
+
426
+ // Single aide binary call: reset state + cleanup agents + fetch all memories
427
+ // Replaces 7 separate binary spawns (~35-50s) with 1 (~5s)
428
+ const projectName = getProjectName(cwd);
429
+ debugLog("sessionInit starting...");
430
+ const memories = runSessionInit(cwd, projectName, 2, log, config);
431
+ debugLog(`sessionInit complete (${Date.now() - hookStart}ms)`);
432
+
433
+ // Build startup notices
434
+ const notices: StartupNotices = {
435
+ error: binaryError,
436
+ warning: binaryWarning,
437
+ info: [],
438
+ };
439
+ if (binaryDownloaded) {
440
+ notices.info!.push("aide binary downloaded");
441
+ }
442
+
443
+ // Log notices via debug (avoids stderr which Claude Code interprets as error)
444
+ if (notices.error) {
445
+ debugLog(`NOTICE ERROR: ${notices.error}`);
446
+ }
447
+ if (notices.warning) {
448
+ debugLog(`NOTICE WARNING: ${notices.warning}`);
449
+ }
450
+ for (const info of notices.info || []) {
451
+ debugLog(`NOTICE INFO: ${info}`);
452
+ }
453
+
454
+ // Build welcome context with injected memories
455
+ debugLog("buildWelcomeContext starting...");
456
+ log.start("buildWelcomeContext");
457
+ const context = buildWelcomeContext(state, memories, notices);
458
+ log.end("buildWelcomeContext");
459
+ debugLog(`buildWelcomeContext complete (${Date.now() - hookStart}ms)`);
460
+
461
+ log.end("total");
462
+ log.info("Session start complete");
463
+ debugLog(`Flushing logs to ${log.getLogFile()}...`);
464
+ log.flush();
465
+ debugLog(`Hook complete (${Date.now() - hookStart}ms total)`);
466
+
467
+ const output: HookOutput = {
468
+ continue: true,
469
+ hookSpecificOutput: {
470
+ hookEventName: "SessionStart",
471
+ additionalContext: context,
472
+ },
473
+ };
474
+
475
+ console.log(JSON.stringify(output));
476
+ } catch (error) {
477
+ debugLog(`ERROR: ${error}`);
478
+ // Log error if logger is available
479
+ if (log) {
480
+ log.error("Session start failed", error);
481
+ log.flush();
482
+ }
483
+ // On error, allow continuation without context
484
+ console.log(JSON.stringify({ continue: true }));
485
+ }
486
+ }
487
+
488
+ main();