@scotthamilton77/sidekick 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/bin.js +281 -78
  2. package/dist/daemon.js +258 -67
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -33736,7 +33736,12 @@ var require_log_events = __commonJS({
33736
33736
  "use strict";
33737
33737
  Object.defineProperty(exports2, "__esModule", { value: true });
33738
33738
  exports2.LogEvents = void 0;
33739
+ exports2.setSessionLogWriter = setSessionLogWriter;
33739
33740
  exports2.logEvent = logEvent;
33741
+ var sessionLogWriter = null;
33742
+ function setSessionLogWriter(writer) {
33743
+ sessionLogWriter = writer;
33744
+ }
33740
33745
  function buildContext(ctx) {
33741
33746
  return {
33742
33747
  sessionId: ctx.sessionId,
@@ -34143,7 +34148,199 @@ var require_log_events = __commonJS({
34143
34148
  source: event.source,
34144
34149
  ...meta
34145
34150
  });
34151
+ if (sessionLogWriter && event.context.sessionId) {
34152
+ const logFile = event.source === "cli" ? "sidekick.log" : "sidekickd.log";
34153
+ const line = JSON.stringify({
34154
+ time: event.time,
34155
+ type: event.type,
34156
+ source: event.source,
34157
+ context: event.context,
34158
+ ...meta
34159
+ }) + "\n";
34160
+ sessionLogWriter.write(event.context.sessionId, logFile, line).catch(() => {
34161
+ });
34162
+ }
34163
+ }
34164
+ }
34165
+ });
34166
+
34167
+ // ../sidekick-core/dist/staging-paths.js
34168
+ var require_staging_paths = __commonJS({
34169
+ "../sidekick-core/dist/staging-paths.js"(exports2) {
34170
+ "use strict";
34171
+ Object.defineProperty(exports2, "__esModule", { value: true });
34172
+ exports2.CONSUMED_FILE_PATTERN = void 0;
34173
+ exports2.getStagingRoot = getStagingRoot;
34174
+ exports2.getHookDir = getHookDir;
34175
+ exports2.getReminderPath = getReminderPath;
34176
+ exports2.isValidPathSegment = isValidPathSegment;
34177
+ exports2.validatePathSegment = validatePathSegment;
34178
+ exports2.filterActiveReminderFiles = filterActiveReminderFiles;
34179
+ exports2.createConsumedFilePattern = createConsumedFilePattern;
34180
+ exports2.extractConsumedTimestamp = extractConsumedTimestamp;
34181
+ var node_path_1 = require("node:path");
34182
+ function getStagingRoot(stateDir, sessionId) {
34183
+ return (0, node_path_1.join)(stateDir, "sessions", sessionId, "stage");
34184
+ }
34185
+ function getHookDir(stateDir, sessionId, hookName) {
34186
+ return (0, node_path_1.join)(getStagingRoot(stateDir, sessionId), hookName);
34187
+ }
34188
+ function getReminderPath(stateDir, sessionId, hookName, reminderName) {
34189
+ return (0, node_path_1.join)(getHookDir(stateDir, sessionId, hookName), `${reminderName}.json`);
34190
+ }
34191
+ function isValidPathSegment(s) {
34192
+ if (s === "")
34193
+ return false;
34194
+ if (s === "." || s === "..")
34195
+ return false;
34196
+ if (s.includes("/") || s.includes("\\"))
34197
+ return false;
34198
+ if ((0, node_path_1.basename)(s) !== s)
34199
+ return false;
34200
+ return /^[a-zA-Z0-9._-]+$/.test(s);
34201
+ }
34202
+ function validatePathSegment(segment, name) {
34203
+ if (!isValidPathSegment(segment)) {
34204
+ throw new Error(`Invalid ${name}: must be a non-empty alphanumeric string without path separators`);
34205
+ }
34146
34206
  }
34207
+ exports2.CONSUMED_FILE_PATTERN = /\.\d+\.json$/;
34208
+ function filterActiveReminderFiles(files) {
34209
+ return files.filter((f) => f.endsWith(".json") && !exports2.CONSUMED_FILE_PATTERN.test(f));
34210
+ }
34211
+ function createConsumedFilePattern(reminderName) {
34212
+ return new RegExp(`^${reminderName}\\.(\\d+)\\.json$`);
34213
+ }
34214
+ function extractConsumedTimestamp(filename, reminderName) {
34215
+ const pattern = createConsumedFilePattern(reminderName);
34216
+ const match = pattern.exec(filename);
34217
+ return match ? parseInt(match[1], 10) : null;
34218
+ }
34219
+ }
34220
+ });
34221
+
34222
+ // ../sidekick-core/dist/session-log-writer.js
34223
+ var require_session_log_writer = __commonJS({
34224
+ "../sidekick-core/dist/session-log-writer.js"(exports2) {
34225
+ "use strict";
34226
+ Object.defineProperty(exports2, "__esModule", { value: true });
34227
+ exports2.SessionLogWriter = void 0;
34228
+ var promises_12 = require("node:fs/promises");
34229
+ var node_path_1 = require("node:path");
34230
+ var node_fs_1 = require("node:fs");
34231
+ var staging_paths_js_1 = require_staging_paths();
34232
+ var SessionLogWriter = class {
34233
+ sessionsDir;
34234
+ maxHandles;
34235
+ idleTimeoutMs;
34236
+ /** Map key: `${sessionId}/${logFile}` */
34237
+ handles = /* @__PURE__ */ new Map();
34238
+ constructor(options) {
34239
+ this.sessionsDir = options.sessionsDir;
34240
+ this.maxHandles = options.maxHandles ?? 10;
34241
+ this.idleTimeoutMs = options.idleTimeoutMs ?? 30 * 60 * 1e3;
34242
+ }
34243
+ get handleCount() {
34244
+ return this.handles.size;
34245
+ }
34246
+ /**
34247
+ * Write an NDJSON line to a per-session log file.
34248
+ * Creates the directory and file handle lazily.
34249
+ * Skips if sessionId is empty (daemon lifecycle events before session exists).
34250
+ */
34251
+ async write(sessionId, logFile, line) {
34252
+ if (!sessionId)
34253
+ return;
34254
+ if (!(0, staging_paths_js_1.isValidPathSegment)(sessionId) || !(0, staging_paths_js_1.isValidPathSegment)(logFile))
34255
+ return;
34256
+ const key = `${sessionId}/${logFile}`;
34257
+ let entry = this.handles.get(key);
34258
+ if (!entry) {
34259
+ if (this.handles.size >= this.maxHandles) {
34260
+ this.evictLRU();
34261
+ }
34262
+ const logDir = (0, node_path_1.join)(this.sessionsDir, sessionId, "logs");
34263
+ await (0, promises_12.mkdir)(logDir, { recursive: true });
34264
+ const filePath = (0, node_path_1.join)(logDir, logFile);
34265
+ const stream = (0, node_fs_1.createWriteStream)(filePath, { flags: "a" });
34266
+ const ready = new Promise((resolve3, reject) => {
34267
+ stream.once("open", () => resolve3());
34268
+ stream.once("error", (err) => {
34269
+ this.handles.delete(key);
34270
+ stream.destroy();
34271
+ reject(err);
34272
+ });
34273
+ });
34274
+ entry = {
34275
+ stream,
34276
+ lastUsed: Date.now(),
34277
+ timer: null,
34278
+ ready
34279
+ };
34280
+ this.handles.set(key, entry);
34281
+ }
34282
+ await entry.ready;
34283
+ entry.lastUsed = Date.now();
34284
+ this.resetIdleTimer(key, entry);
34285
+ return new Promise((resolve3, reject) => {
34286
+ entry.stream.write(line, (err) => {
34287
+ if (err) {
34288
+ void this.closeHandle(key);
34289
+ reject(err);
34290
+ } else {
34291
+ resolve3();
34292
+ }
34293
+ });
34294
+ });
34295
+ }
34296
+ /** Close all handles for a specific session. */
34297
+ async closeSession(sessionId) {
34298
+ const prefix = `${sessionId}/`;
34299
+ const toClose = [];
34300
+ for (const key of this.handles.keys()) {
34301
+ if (key.startsWith(prefix)) {
34302
+ toClose.push(key);
34303
+ }
34304
+ }
34305
+ await Promise.all(toClose.map((key) => this.closeHandle(key)));
34306
+ }
34307
+ /** Close all open handles. */
34308
+ async closeAll() {
34309
+ const keys = [...this.handles.keys()];
34310
+ await Promise.all(keys.map((key) => this.closeHandle(key)));
34311
+ }
34312
+ async closeHandle(key) {
34313
+ const entry = this.handles.get(key);
34314
+ if (!entry)
34315
+ return;
34316
+ if (entry.timer)
34317
+ clearTimeout(entry.timer);
34318
+ this.handles.delete(key);
34319
+ return new Promise((resolve3) => {
34320
+ entry.stream.end(() => resolve3());
34321
+ });
34322
+ }
34323
+ evictLRU() {
34324
+ let oldestKey = "";
34325
+ let oldestTime = Infinity;
34326
+ for (const [key, entry] of this.handles) {
34327
+ if (entry.lastUsed < oldestTime) {
34328
+ oldestTime = entry.lastUsed;
34329
+ oldestKey = key;
34330
+ }
34331
+ }
34332
+ void this.closeHandle(oldestKey);
34333
+ }
34334
+ resetIdleTimer(key, entry) {
34335
+ if (entry.timer)
34336
+ clearTimeout(entry.timer);
34337
+ entry.timer = setTimeout(() => {
34338
+ void this.closeHandle(key);
34339
+ }, this.idleTimeoutMs);
34340
+ entry.timer.unref();
34341
+ }
34342
+ };
34343
+ exports2.SessionLogWriter = SessionLogWriter;
34147
34344
  }
34148
34345
  });
34149
34346
 
@@ -45886,7 +46083,7 @@ var require_structured_logging = __commonJS({
45886
46083
  return mod && mod.__esModule ? mod : { "default": mod };
45887
46084
  };
45888
46085
  Object.defineProperty(exports2, "__esModule", { value: true });
45889
- exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.LOG_LEVELS = exports2.logEvent = exports2.LogEvents = void 0;
46086
+ exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.LOG_LEVELS = exports2.SessionLogWriter = exports2.setSessionLogWriter = exports2.logEvent = exports2.LogEvents = void 0;
45890
46087
  exports2.getComponentLogLevel = getComponentLogLevel;
45891
46088
  exports2.createLogManager = createLogManager;
45892
46089
  exports2.createContextLogger = createContextLogger;
@@ -45903,6 +46100,13 @@ var require_structured_logging = __commonJS({
45903
46100
  Object.defineProperty(exports2, "logEvent", { enumerable: true, get: function() {
45904
46101
  return log_events_1.logEvent;
45905
46102
  } });
46103
+ Object.defineProperty(exports2, "setSessionLogWriter", { enumerable: true, get: function() {
46104
+ return log_events_1.setSessionLogWriter;
46105
+ } });
46106
+ var session_log_writer_1 = require_session_log_writer();
46107
+ Object.defineProperty(exports2, "SessionLogWriter", { enumerable: true, get: function() {
46108
+ return session_log_writer_1.SessionLogWriter;
46109
+ } });
45906
46110
  exports2.LOG_LEVELS = {
45907
46111
  trace: 10,
45908
46112
  debug: 20,
@@ -57726,8 +57930,8 @@ var require_doctor_engine = __commonJS({
57726
57930
  });
57727
57931
  }
57728
57932
  } else {
57729
- const userStatus2 = await io.getUserStatus();
57730
- if (userStatus2) {
57933
+ const userStatus = await io.getUserStatus();
57934
+ if (userStatus) {
57731
57935
  await io.updateUserStatus({ statusline: actualStatusline });
57732
57936
  } else {
57733
57937
  await io.writeUserStatus({
@@ -57783,8 +57987,8 @@ var require_doctor_engine = __commonJS({
57783
57987
  },
57784
57988
  gitignore: "unknown"
57785
57989
  });
57786
- const userStatus2 = await io.getUserStatus();
57787
- if (!userStatus2) {
57990
+ const userStatus = await io.getUserStatus();
57991
+ if (!userStatus) {
57788
57992
  await io.writeUserStatus({
57789
57993
  version: 1,
57790
57994
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -57810,8 +58014,29 @@ var require_doctor_engine = __commonJS({
57810
58014
  scopes: projectApiKeyStatus.scopes
57811
58015
  };
57812
58016
  }
57813
- const userStatus = await io.getUserStatus();
57814
- const userSetupExists = userStatus !== null;
58017
+ const currentUserStatus = await io.getUserStatus();
58018
+ if (currentUserStatus) {
58019
+ let userNeedsUpdate = false;
58020
+ const updatedUserApiKeys = { ...currentUserStatus.apiKeys };
58021
+ for (let i = 0; i < keysToCheck.length; i++) {
58022
+ const keyName = keysToCheck[i];
58023
+ const detection = detections[i];
58024
+ const expectedUserStatus = (0, api_key_detector_js_1.buildUserApiKeyStatus)(detection);
58025
+ const currentUserEntry = currentUserStatus.apiKeys[keyName];
58026
+ const currentStatus = typeof currentUserEntry === "object" ? currentUserEntry.status : currentUserEntry ?? "missing";
58027
+ if (currentStatus !== "not-required" && (0, api_key_detector_js_1.toScopeStatus)(currentStatus) !== (0, api_key_detector_js_1.toScopeStatus)(expectedUserStatus.status)) {
58028
+ updatedUserApiKeys[keyName] = expectedUserStatus;
58029
+ userNeedsUpdate = true;
58030
+ }
58031
+ }
58032
+ if (userNeedsUpdate) {
58033
+ await io.updateUserStatus({
58034
+ apiKeys: updatedUserApiKeys
58035
+ });
58036
+ fixes.push("Updated stale user setup-status with current API key status");
58037
+ }
58038
+ }
58039
+ const userSetupExists = currentUserStatus !== null;
57815
58040
  const isStatuslineHealthy = actualStatusline !== "none";
57816
58041
  const openRouterActual = apiKeyResults.OPENROUTER_API_KEY.actual;
57817
58042
  const openRouterCached = apiKeyResults.OPENROUTER_API_KEY.cached;
@@ -58364,61 +58589,6 @@ var require_errors6 = __commonJS({
58364
58589
  }
58365
58590
  });
58366
58591
 
58367
- // ../sidekick-core/dist/staging-paths.js
58368
- var require_staging_paths = __commonJS({
58369
- "../sidekick-core/dist/staging-paths.js"(exports2) {
58370
- "use strict";
58371
- Object.defineProperty(exports2, "__esModule", { value: true });
58372
- exports2.CONSUMED_FILE_PATTERN = void 0;
58373
- exports2.getStagingRoot = getStagingRoot;
58374
- exports2.getHookDir = getHookDir;
58375
- exports2.getReminderPath = getReminderPath;
58376
- exports2.isValidPathSegment = isValidPathSegment;
58377
- exports2.validatePathSegment = validatePathSegment;
58378
- exports2.filterActiveReminderFiles = filterActiveReminderFiles;
58379
- exports2.createConsumedFilePattern = createConsumedFilePattern;
58380
- exports2.extractConsumedTimestamp = extractConsumedTimestamp;
58381
- var node_path_1 = require("node:path");
58382
- function getStagingRoot(stateDir, sessionId) {
58383
- return (0, node_path_1.join)(stateDir, "sessions", sessionId, "stage");
58384
- }
58385
- function getHookDir(stateDir, sessionId, hookName) {
58386
- return (0, node_path_1.join)(getStagingRoot(stateDir, sessionId), hookName);
58387
- }
58388
- function getReminderPath(stateDir, sessionId, hookName, reminderName) {
58389
- return (0, node_path_1.join)(getHookDir(stateDir, sessionId, hookName), `${reminderName}.json`);
58390
- }
58391
- function isValidPathSegment(s) {
58392
- if (s === "")
58393
- return false;
58394
- if (s === "." || s === "..")
58395
- return false;
58396
- if (s.includes("/") || s.includes("\\"))
58397
- return false;
58398
- if ((0, node_path_1.basename)(s) !== s)
58399
- return false;
58400
- return /^[a-zA-Z0-9._-]+$/.test(s);
58401
- }
58402
- function validatePathSegment(segment, name) {
58403
- if (!isValidPathSegment(segment)) {
58404
- throw new Error(`Invalid ${name}: must be a non-empty alphanumeric string without path separators`);
58405
- }
58406
- }
58407
- exports2.CONSUMED_FILE_PATTERN = /\.\d+\.json$/;
58408
- function filterActiveReminderFiles(files) {
58409
- return files.filter((f) => f.endsWith(".json") && !exports2.CONSUMED_FILE_PATTERN.test(f));
58410
- }
58411
- function createConsumedFilePattern(reminderName) {
58412
- return new RegExp(`^${reminderName}\\.(\\d+)\\.json$`);
58413
- }
58414
- function extractConsumedTimestamp(filename, reminderName) {
58415
- const pattern = createConsumedFilePattern(reminderName);
58416
- const match = pattern.exec(filename);
58417
- return match ? parseInt(match[1], 10) : null;
58418
- }
58419
- }
58420
- });
58421
-
58422
58592
  // ../sidekick-core/dist/staging-service.js
58423
58593
  var require_staging_service = __commonJS({
58424
58594
  "../sidekick-core/dist/staging-service.js"(exports2) {
@@ -59294,6 +59464,9 @@ var require_path_resolver = __commonJS({
59294
59464
  sessionStagingDir(sessionId) {
59295
59465
  return (0, node_path_1.join)(this.sessionRootDir(sessionId), "stage");
59296
59466
  }
59467
+ sessionLogsDir(sessionId) {
59468
+ return (0, node_path_1.join)(this.sessionRootDir(sessionId), "logs");
59469
+ }
59297
59470
  hookStagingDir(sessionId, hookName) {
59298
59471
  return (0, node_path_1.join)(this.sessionStagingDir(sessionId), hookName);
59299
59472
  }
@@ -64014,9 +64187,9 @@ var require_dist4 = __commonJS({
64014
64187
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
64015
64188
  };
64016
64189
  Object.defineProperty(exports2, "__esModule", { value: true });
64017
- exports2.readKeyFromEnvFile = exports2.determineOverallStatus = exports2.toScopeStatus = exports2.LEGACY_USER_STATUS_FILENAME = exports2.PROJECT_STATUS_FILENAME = exports2.USER_STATUS_FILENAME = exports2.createSetupStatusService = exports2.SetupStatusService = exports2.DaemonClient = exports2.findZombieDaemons = exports2.killZombieDaemons = exports2.killAllDaemons = exports2.logEvent = exports2.LogEvents = exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.getComponentLogLevel = exports2.setupGlobalErrorHandlers = exports2.createLoggerFacade = exports2.createLogManager = exports2.createConsoleLogger = exports2.getUserDaemonsDir = exports2.getUserPidPath = exports2.getTokenPath = exports2.getSocketPath = exports2.getProjectHash = exports2.getPidPath = exports2.getLockPath = exports2.IpcService = exports2.IpcServer = exports2.loadPersonaFile = exports2.getDefaultPersonasDir = exports2.discoverPersonas = exports2.createPersonaLoader = exports2.reconstructTranscriptPath = exports2.encodeProjectPath = exports2.isPreCompactEvent = exports2.isStopEvent = exports2.isPostToolUseEvent = exports2.isPreToolUseEvent = exports2.isUserPromptSubmitEvent = exports2.isSessionEndEvent = exports2.isSessionStartEvent = exports2.isTranscriptEvent = exports2.isHookEvent = exports2.MetricsPersistPayloadSchema = exports2.CleanupPayloadSchema = exports2.ResumeGenerationPayloadSchema = exports2.SessionSummaryPayloadSchema = exports2.TaskTypes = void 0;
64018
- exports2.StateService = exports2.createHookableLogger = exports2.InstrumentedProfileProviderFactory = exports2.InstrumentedLLMProvider = exports2.ServiceFactoryImpl = exports2.TranscriptServiceImpl = exports2.createDefaultTokenUsage = exports2.createDefaultMetrics = exports2.copyWithTimestampSync = exports2.renameWithTimestampSync = exports2.renameWithTimestamp = exports2.copyWithTimestamp = exports2.getTimestampedPath = exports2.extractToolResultPreview = exports2.extractToolCallPreview = exports2.extractTextFromContent = exports2.extractContentPreview = exports2.HandlerRegistryImpl = exports2.extractConsumedTimestamp = exports2.createConsumedFilePattern = exports2.CONSUMED_FILE_PATTERN = exports2.filterActiveReminderFiles = exports2.validatePathSegment = exports2.isValidPathSegment = exports2.getReminderPath = exports2.getHookDir = exports2.getStagingRoot = exports2.SessionScopedStagingService = exports2.StagingServiceCore = exports2.GITIGNORE_ENTRIES = exports2.SIDEKICK_SECTION_END = exports2.SIDEKICK_SECTION_START = exports2.detectGitignoreStatus = exports2.removeGitignoreSection = exports2.installGitignoreSection = exports2.validateOpenAIKey = exports2.validateOpenRouterKey = exports2.runDoctorCheck = exports2.detectPluginLiveness = exports2.detectPluginInstallation = exports2.detectActualStatusline = exports2.spawnWithTimeout = exports2.getDoctorTimeout = exports2.DOCTOR_TIMEOUTS = exports2.projectApiKeyStatusFromHealth = exports2.userApiKeyStatusFromHealth = exports2.buildProjectApiKeyStatus = exports2.buildUserApiKeyStatus = exports2.detectAllApiKeys = exports2.detectActualApiKey = void 0;
64019
- exports2.CoalescingGuard = exports2.loadUserProfile = exports2.parseGitStatusOutput = exports2.getGitFileStatus = exports2.isInSandbox = exports2.updateDaemonHealth = exports2.readDaemonHealth = exports2.ProjectRegistryService = exports2.encodeProjectDir = exports2.DaemonGlobalLogMetricsDescriptor = exports2.CliLogMetricsDescriptor = exports2.DaemonLogMetricsDescriptor = exports2.CompactionHistoryDescriptor = exports2.TranscriptMetricsDescriptor = exports2.GlobalStateAccessor = exports2.SessionStateAccessor = exports2.globalState = exports2.sessionState = exports2.StateCorruptError = exports2.StateNotFoundError = void 0;
64190
+ exports2.toScopeStatus = exports2.LEGACY_USER_STATUS_FILENAME = exports2.PROJECT_STATUS_FILENAME = exports2.USER_STATUS_FILENAME = exports2.createSetupStatusService = exports2.SetupStatusService = exports2.DaemonClient = exports2.findZombieDaemons = exports2.killZombieDaemons = exports2.killAllDaemons = exports2.SessionLogWriter = exports2.setSessionLogWriter = exports2.logEvent = exports2.LogEvents = exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.getComponentLogLevel = exports2.setupGlobalErrorHandlers = exports2.createLoggerFacade = exports2.createLogManager = exports2.createConsoleLogger = exports2.getUserDaemonsDir = exports2.getUserPidPath = exports2.getTokenPath = exports2.getSocketPath = exports2.getProjectHash = exports2.getPidPath = exports2.getLockPath = exports2.IpcService = exports2.IpcServer = exports2.loadPersonaFile = exports2.getDefaultPersonasDir = exports2.discoverPersonas = exports2.createPersonaLoader = exports2.reconstructTranscriptPath = exports2.encodeProjectPath = exports2.isPreCompactEvent = exports2.isStopEvent = exports2.isPostToolUseEvent = exports2.isPreToolUseEvent = exports2.isUserPromptSubmitEvent = exports2.isSessionEndEvent = exports2.isSessionStartEvent = exports2.isTranscriptEvent = exports2.isHookEvent = exports2.MetricsPersistPayloadSchema = exports2.CleanupPayloadSchema = exports2.ResumeGenerationPayloadSchema = exports2.SessionSummaryPayloadSchema = exports2.TaskTypes = void 0;
64191
+ exports2.InstrumentedProfileProviderFactory = exports2.InstrumentedLLMProvider = exports2.ServiceFactoryImpl = exports2.TranscriptServiceImpl = exports2.createDefaultTokenUsage = exports2.createDefaultMetrics = exports2.copyWithTimestampSync = exports2.renameWithTimestampSync = exports2.renameWithTimestamp = exports2.copyWithTimestamp = exports2.getTimestampedPath = exports2.extractToolResultPreview = exports2.extractToolCallPreview = exports2.extractTextFromContent = exports2.extractContentPreview = exports2.HandlerRegistryImpl = exports2.extractConsumedTimestamp = exports2.createConsumedFilePattern = exports2.CONSUMED_FILE_PATTERN = exports2.filterActiveReminderFiles = exports2.validatePathSegment = exports2.isValidPathSegment = exports2.getReminderPath = exports2.getHookDir = exports2.getStagingRoot = exports2.SessionScopedStagingService = exports2.StagingServiceCore = exports2.GITIGNORE_ENTRIES = exports2.SIDEKICK_SECTION_END = exports2.SIDEKICK_SECTION_START = exports2.detectGitignoreStatus = exports2.removeGitignoreSection = exports2.installGitignoreSection = exports2.validateOpenAIKey = exports2.validateOpenRouterKey = exports2.runDoctorCheck = exports2.detectPluginLiveness = exports2.detectPluginInstallation = exports2.detectActualStatusline = exports2.spawnWithTimeout = exports2.getDoctorTimeout = exports2.DOCTOR_TIMEOUTS = exports2.projectApiKeyStatusFromHealth = exports2.userApiKeyStatusFromHealth = exports2.buildProjectApiKeyStatus = exports2.buildUserApiKeyStatus = exports2.detectAllApiKeys = exports2.detectActualApiKey = exports2.readKeyFromEnvFile = exports2.determineOverallStatus = void 0;
64192
+ exports2.CoalescingGuard = exports2.loadUserProfile = exports2.parseGitStatusOutput = exports2.getGitFileStatus = exports2.isInSandbox = exports2.updateDaemonHealth = exports2.readDaemonHealth = exports2.ProjectRegistryService = exports2.encodeProjectDir = exports2.DaemonGlobalLogMetricsDescriptor = exports2.CliLogMetricsDescriptor = exports2.DaemonLogMetricsDescriptor = exports2.CompactionHistoryDescriptor = exports2.TranscriptMetricsDescriptor = exports2.GlobalStateAccessor = exports2.SessionStateAccessor = exports2.globalState = exports2.sessionState = exports2.StateCorruptError = exports2.StateNotFoundError = exports2.StateService = exports2.createHookableLogger = void 0;
64020
64193
  var types_1 = require_dist();
64021
64194
  Object.defineProperty(exports2, "TaskTypes", { enumerable: true, get: function() {
64022
64195
  return types_1.TaskTypes;
@@ -64149,6 +64322,12 @@ var require_dist4 = __commonJS({
64149
64322
  Object.defineProperty(exports2, "logEvent", { enumerable: true, get: function() {
64150
64323
  return structured_logging_1.logEvent;
64151
64324
  } });
64325
+ Object.defineProperty(exports2, "setSessionLogWriter", { enumerable: true, get: function() {
64326
+ return structured_logging_1.setSessionLogWriter;
64327
+ } });
64328
+ Object.defineProperty(exports2, "SessionLogWriter", { enumerable: true, get: function() {
64329
+ return structured_logging_1.SessionLogWriter;
64330
+ } });
64152
64331
  var daemon_client_1 = require_daemon_client2();
64153
64332
  Object.defineProperty(exports2, "killAllDaemons", { enumerable: true, get: function() {
64154
64333
  return daemon_client_1.killAllDaemons;
@@ -64477,8 +64656,9 @@ var require_runtime = __commonJS({
64477
64656
  const rotation = config.core.logging.rotation;
64478
64657
  const fileDestination = enableFileLogging ? {
64479
64658
  path: logFilePath,
64480
- maxSizeBytes: rotation?.maxSizeBytes ?? 10485760,
64481
- maxFiles: rotation?.maxFiles ?? 5
64659
+ maxSizeBytes: rotation?.maxSizeBytes ?? 2097152,
64660
+ // 2MB (ephemeral debug window)
64661
+ maxFiles: rotation?.maxFiles ?? 2
64482
64662
  } : void 0;
64483
64663
  const logManager = (0, core_1.createLogManager)({
64484
64664
  name: "sidekick:cli",
@@ -64520,6 +64700,14 @@ var require_runtime = __commonJS({
64520
64700
  }
64521
64701
  });
64522
64702
  const telemetry = logManager.getTelemetry();
64703
+ const sessionsDir = projectRoot ? (0, node_path_1.join)(projectRoot, ".sidekick", "sessions") : (0, node_path_1.join)((0, node_os_1.homedir)(), ".sidekick", "sessions");
64704
+ const sessionLogWriter = new core_1.SessionLogWriter({
64705
+ sessionsDir,
64706
+ maxHandles: 2,
64707
+ // CLI typically has 1 session
64708
+ idleTimeoutMs: 10 * 60 * 1e3
64709
+ });
64710
+ (0, core_1.setSessionLogWriter)(sessionLogWriter);
64523
64711
  const cleanupErrorHandlers = (0, core_1.setupGlobalErrorHandlers)(logger);
64524
64712
  logger.debug("Runtime bootstrap complete", {
64525
64713
  projectRoot: projectRoot ?? null,
@@ -64542,6 +64730,8 @@ var require_runtime = __commonJS({
64542
64730
  stateService,
64543
64731
  correlationId,
64544
64732
  cleanup: () => {
64733
+ (0, core_1.setSessionLogWriter)(null);
64734
+ void sessionLogWriter.closeAll();
64545
64735
  cleanupErrorHandlers();
64546
64736
  },
64547
64737
  bindSessionId: (sessionId) => {
@@ -81142,8 +81332,18 @@ var require_prompt = __commonJS({
81142
81332
  };
81143
81333
  })();
81144
81334
  Object.defineProperty(exports2, "__esModule", { value: true });
81335
+ exports2.createSafeResolver = createSafeResolver;
81145
81336
  exports2.promptConfirm = promptConfirm;
81146
81337
  var readline = __importStar(require("node:readline"));
81338
+ function createSafeResolver(resolve3) {
81339
+ let resolved = false;
81340
+ return (value) => {
81341
+ if (!resolved) {
81342
+ resolved = true;
81343
+ resolve3(value);
81344
+ }
81345
+ };
81346
+ }
81147
81347
  var colors = {
81148
81348
  yellow: "\x1B[1;33m",
81149
81349
  reset: "\x1B[0m"
@@ -81157,13 +81357,7 @@ var require_prompt = __commonJS({
81157
81357
  });
81158
81358
  const prompt = `${question} ${hint} `;
81159
81359
  return new Promise((resolve3) => {
81160
- let resolved = false;
81161
- const safeResolve = (value) => {
81162
- if (!resolved) {
81163
- resolved = true;
81164
- resolve3(value);
81165
- }
81166
- };
81360
+ const safeResolve = createSafeResolver(resolve3);
81167
81361
  rl.once("close", () => {
81168
81362
  safeResolve(defaultYes);
81169
81363
  });
@@ -81804,6 +81998,7 @@ var require_prompts = __commonJS({
81804
81998
  Object.defineProperty(exports2, "promptConfirm", { enumerable: true, get: function() {
81805
81999
  return prompt_js_1.promptConfirm;
81806
82000
  } });
82001
+ var prompt_js_2 = require_prompt();
81807
82002
  var colors = {
81808
82003
  reset: "\x1B[0m",
81809
82004
  bold: "\x1B[1m",
@@ -81851,18 +82046,22 @@ var require_prompts = __commonJS({
81851
82046
  const defaultNum = defaultIndex + 1;
81852
82047
  const prompt = `Enter choice (1-${options.length}) [${defaultNum}]: `;
81853
82048
  return new Promise((resolve3) => {
82049
+ const safeResolve = (0, prompt_js_2.createSafeResolver)(resolve3);
82050
+ rl.once("close", () => {
82051
+ safeResolve(options[defaultIndex].value);
82052
+ });
81854
82053
  const ask = () => {
81855
82054
  ctx.stdout.write(prompt);
81856
82055
  rl.once("line", (answer) => {
81857
82056
  const trimmed = answer.trim();
81858
82057
  if (trimmed === "") {
82058
+ safeResolve(options[defaultIndex].value);
81859
82059
  rl.close();
81860
- resolve3(options[defaultIndex].value);
81861
82060
  } else {
81862
82061
  const num = parseInt(trimmed, 10);
81863
82062
  if (num >= 1 && num <= options.length) {
82063
+ safeResolve(options[num - 1].value);
81864
82064
  rl.close();
81865
- resolve3(options[num - 1].value);
81866
82065
  } else {
81867
82066
  ctx.stdout.write(`${colors.yellow}Invalid choice. Enter a number between 1 and ${options.length}.${colors.reset}
81868
82067
  `);
@@ -81881,10 +82080,14 @@ var require_prompts = __commonJS({
81881
82080
  terminal: false
81882
82081
  });
81883
82082
  return new Promise((resolve3) => {
82083
+ const safeResolve = (0, prompt_js_2.createSafeResolver)(resolve3);
82084
+ rl.once("close", () => {
82085
+ safeResolve("");
82086
+ });
81884
82087
  ctx.stdout.write(`${question}: `);
81885
82088
  rl.once("line", (answer) => {
82089
+ safeResolve(answer.trim());
81886
82090
  rl.close();
81887
- resolve3(answer.trim());
81888
82091
  });
81889
82092
  });
81890
82093
  }
@@ -84472,7 +84675,7 @@ var require_cli = __commonJS({
84472
84675
  var promises_12 = require("node:fs/promises");
84473
84676
  var node_stream_1 = require("node:stream");
84474
84677
  var yargs_parser_1 = __importDefault2(require_build());
84475
- var VERSION = true ? "0.1.17" : "dev";
84678
+ var VERSION = true ? "0.1.19" : "dev";
84476
84679
  var SANDBOX_ERROR_MESSAGE = `Error: Daemon commands cannot run in sandbox mode.
84477
84680
 
84478
84681
  Claude Code's sandbox blocks Unix socket operations required for daemon IPC.
package/dist/daemon.js CHANGED
@@ -32760,7 +32760,12 @@ var require_log_events = __commonJS({
32760
32760
  "use strict";
32761
32761
  Object.defineProperty(exports2, "__esModule", { value: true });
32762
32762
  exports2.LogEvents = void 0;
32763
+ exports2.setSessionLogWriter = setSessionLogWriter;
32763
32764
  exports2.logEvent = logEvent;
32765
+ var sessionLogWriter = null;
32766
+ function setSessionLogWriter(writer) {
32767
+ sessionLogWriter = writer;
32768
+ }
32764
32769
  function buildContext(ctx) {
32765
32770
  return {
32766
32771
  sessionId: ctx.sessionId,
@@ -33167,10 +33172,202 @@ var require_log_events = __commonJS({
33167
33172
  source: event.source,
33168
33173
  ...meta
33169
33174
  });
33175
+ if (sessionLogWriter && event.context.sessionId) {
33176
+ const logFile = event.source === "cli" ? "sidekick.log" : "sidekickd.log";
33177
+ const line = JSON.stringify({
33178
+ time: event.time,
33179
+ type: event.type,
33180
+ source: event.source,
33181
+ context: event.context,
33182
+ ...meta
33183
+ }) + "\n";
33184
+ sessionLogWriter.write(event.context.sessionId, logFile, line).catch(() => {
33185
+ });
33186
+ }
33170
33187
  }
33171
33188
  }
33172
33189
  });
33173
33190
 
33191
+ // ../sidekick-core/dist/staging-paths.js
33192
+ var require_staging_paths = __commonJS({
33193
+ "../sidekick-core/dist/staging-paths.js"(exports2) {
33194
+ "use strict";
33195
+ Object.defineProperty(exports2, "__esModule", { value: true });
33196
+ exports2.CONSUMED_FILE_PATTERN = void 0;
33197
+ exports2.getStagingRoot = getStagingRoot;
33198
+ exports2.getHookDir = getHookDir;
33199
+ exports2.getReminderPath = getReminderPath;
33200
+ exports2.isValidPathSegment = isValidPathSegment;
33201
+ exports2.validatePathSegment = validatePathSegment;
33202
+ exports2.filterActiveReminderFiles = filterActiveReminderFiles;
33203
+ exports2.createConsumedFilePattern = createConsumedFilePattern;
33204
+ exports2.extractConsumedTimestamp = extractConsumedTimestamp;
33205
+ var node_path_1 = require("node:path");
33206
+ function getStagingRoot(stateDir, sessionId) {
33207
+ return (0, node_path_1.join)(stateDir, "sessions", sessionId, "stage");
33208
+ }
33209
+ function getHookDir(stateDir, sessionId, hookName) {
33210
+ return (0, node_path_1.join)(getStagingRoot(stateDir, sessionId), hookName);
33211
+ }
33212
+ function getReminderPath(stateDir, sessionId, hookName, reminderName) {
33213
+ return (0, node_path_1.join)(getHookDir(stateDir, sessionId, hookName), `${reminderName}.json`);
33214
+ }
33215
+ function isValidPathSegment(s) {
33216
+ if (s === "")
33217
+ return false;
33218
+ if (s === "." || s === "..")
33219
+ return false;
33220
+ if (s.includes("/") || s.includes("\\"))
33221
+ return false;
33222
+ if ((0, node_path_1.basename)(s) !== s)
33223
+ return false;
33224
+ return /^[a-zA-Z0-9._-]+$/.test(s);
33225
+ }
33226
+ function validatePathSegment(segment, name) {
33227
+ if (!isValidPathSegment(segment)) {
33228
+ throw new Error(`Invalid ${name}: must be a non-empty alphanumeric string without path separators`);
33229
+ }
33230
+ }
33231
+ exports2.CONSUMED_FILE_PATTERN = /\.\d+\.json$/;
33232
+ function filterActiveReminderFiles(files) {
33233
+ return files.filter((f) => f.endsWith(".json") && !exports2.CONSUMED_FILE_PATTERN.test(f));
33234
+ }
33235
+ function createConsumedFilePattern(reminderName) {
33236
+ return new RegExp(`^${reminderName}\\.(\\d+)\\.json$`);
33237
+ }
33238
+ function extractConsumedTimestamp(filename, reminderName) {
33239
+ const pattern = createConsumedFilePattern(reminderName);
33240
+ const match = pattern.exec(filename);
33241
+ return match ? parseInt(match[1], 10) : null;
33242
+ }
33243
+ }
33244
+ });
33245
+
33246
+ // ../sidekick-core/dist/session-log-writer.js
33247
+ var require_session_log_writer = __commonJS({
33248
+ "../sidekick-core/dist/session-log-writer.js"(exports2) {
33249
+ "use strict";
33250
+ Object.defineProperty(exports2, "__esModule", { value: true });
33251
+ exports2.SessionLogWriter = void 0;
33252
+ var promises_1 = require("node:fs/promises");
33253
+ var node_path_1 = require("node:path");
33254
+ var node_fs_1 = require("node:fs");
33255
+ var staging_paths_js_1 = require_staging_paths();
33256
+ var SessionLogWriter = class {
33257
+ sessionsDir;
33258
+ maxHandles;
33259
+ idleTimeoutMs;
33260
+ /** Map key: `${sessionId}/${logFile}` */
33261
+ handles = /* @__PURE__ */ new Map();
33262
+ constructor(options) {
33263
+ this.sessionsDir = options.sessionsDir;
33264
+ this.maxHandles = options.maxHandles ?? 10;
33265
+ this.idleTimeoutMs = options.idleTimeoutMs ?? 30 * 60 * 1e3;
33266
+ }
33267
+ get handleCount() {
33268
+ return this.handles.size;
33269
+ }
33270
+ /**
33271
+ * Write an NDJSON line to a per-session log file.
33272
+ * Creates the directory and file handle lazily.
33273
+ * Skips if sessionId is empty (daemon lifecycle events before session exists).
33274
+ */
33275
+ async write(sessionId, logFile, line) {
33276
+ if (!sessionId)
33277
+ return;
33278
+ if (!(0, staging_paths_js_1.isValidPathSegment)(sessionId) || !(0, staging_paths_js_1.isValidPathSegment)(logFile))
33279
+ return;
33280
+ const key = `${sessionId}/${logFile}`;
33281
+ let entry = this.handles.get(key);
33282
+ if (!entry) {
33283
+ if (this.handles.size >= this.maxHandles) {
33284
+ this.evictLRU();
33285
+ }
33286
+ const logDir = (0, node_path_1.join)(this.sessionsDir, sessionId, "logs");
33287
+ await (0, promises_1.mkdir)(logDir, { recursive: true });
33288
+ const filePath = (0, node_path_1.join)(logDir, logFile);
33289
+ const stream = (0, node_fs_1.createWriteStream)(filePath, { flags: "a" });
33290
+ const ready = new Promise((resolve3, reject) => {
33291
+ stream.once("open", () => resolve3());
33292
+ stream.once("error", (err) => {
33293
+ this.handles.delete(key);
33294
+ stream.destroy();
33295
+ reject(err);
33296
+ });
33297
+ });
33298
+ entry = {
33299
+ stream,
33300
+ lastUsed: Date.now(),
33301
+ timer: null,
33302
+ ready
33303
+ };
33304
+ this.handles.set(key, entry);
33305
+ }
33306
+ await entry.ready;
33307
+ entry.lastUsed = Date.now();
33308
+ this.resetIdleTimer(key, entry);
33309
+ return new Promise((resolve3, reject) => {
33310
+ entry.stream.write(line, (err) => {
33311
+ if (err) {
33312
+ void this.closeHandle(key);
33313
+ reject(err);
33314
+ } else {
33315
+ resolve3();
33316
+ }
33317
+ });
33318
+ });
33319
+ }
33320
+ /** Close all handles for a specific session. */
33321
+ async closeSession(sessionId) {
33322
+ const prefix = `${sessionId}/`;
33323
+ const toClose = [];
33324
+ for (const key of this.handles.keys()) {
33325
+ if (key.startsWith(prefix)) {
33326
+ toClose.push(key);
33327
+ }
33328
+ }
33329
+ await Promise.all(toClose.map((key) => this.closeHandle(key)));
33330
+ }
33331
+ /** Close all open handles. */
33332
+ async closeAll() {
33333
+ const keys = [...this.handles.keys()];
33334
+ await Promise.all(keys.map((key) => this.closeHandle(key)));
33335
+ }
33336
+ async closeHandle(key) {
33337
+ const entry = this.handles.get(key);
33338
+ if (!entry)
33339
+ return;
33340
+ if (entry.timer)
33341
+ clearTimeout(entry.timer);
33342
+ this.handles.delete(key);
33343
+ return new Promise((resolve3) => {
33344
+ entry.stream.end(() => resolve3());
33345
+ });
33346
+ }
33347
+ evictLRU() {
33348
+ let oldestKey = "";
33349
+ let oldestTime = Infinity;
33350
+ for (const [key, entry] of this.handles) {
33351
+ if (entry.lastUsed < oldestTime) {
33352
+ oldestTime = entry.lastUsed;
33353
+ oldestKey = key;
33354
+ }
33355
+ }
33356
+ void this.closeHandle(oldestKey);
33357
+ }
33358
+ resetIdleTimer(key, entry) {
33359
+ if (entry.timer)
33360
+ clearTimeout(entry.timer);
33361
+ entry.timer = setTimeout(() => {
33362
+ void this.closeHandle(key);
33363
+ }, this.idleTimeoutMs);
33364
+ entry.timer.unref();
33365
+ }
33366
+ };
33367
+ exports2.SessionLogWriter = SessionLogWriter;
33368
+ }
33369
+ });
33370
+
33174
33371
  // ../../node_modules/.pnpm/date-fns@4.1.0/node_modules/date-fns/constants.cjs
33175
33372
  var require_constants2 = __commonJS({
33176
33373
  "../../node_modules/.pnpm/date-fns@4.1.0/node_modules/date-fns/constants.cjs"(exports2) {
@@ -44910,7 +45107,7 @@ var require_structured_logging = __commonJS({
44910
45107
  return mod && mod.__esModule ? mod : { "default": mod };
44911
45108
  };
44912
45109
  Object.defineProperty(exports2, "__esModule", { value: true });
44913
- exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.LOG_LEVELS = exports2.logEvent = exports2.LogEvents = void 0;
45110
+ exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.LOG_LEVELS = exports2.SessionLogWriter = exports2.setSessionLogWriter = exports2.logEvent = exports2.LogEvents = void 0;
44914
45111
  exports2.getComponentLogLevel = getComponentLogLevel;
44915
45112
  exports2.createLogManager = createLogManager;
44916
45113
  exports2.createContextLogger = createContextLogger;
@@ -44927,6 +45124,13 @@ var require_structured_logging = __commonJS({
44927
45124
  Object.defineProperty(exports2, "logEvent", { enumerable: true, get: function() {
44928
45125
  return log_events_1.logEvent;
44929
45126
  } });
45127
+ Object.defineProperty(exports2, "setSessionLogWriter", { enumerable: true, get: function() {
45128
+ return log_events_1.setSessionLogWriter;
45129
+ } });
45130
+ var session_log_writer_1 = require_session_log_writer();
45131
+ Object.defineProperty(exports2, "SessionLogWriter", { enumerable: true, get: function() {
45132
+ return session_log_writer_1.SessionLogWriter;
45133
+ } });
44930
45134
  exports2.LOG_LEVELS = {
44931
45135
  trace: 10,
44932
45136
  debug: 20,
@@ -56750,8 +56954,8 @@ var require_doctor_engine = __commonJS({
56750
56954
  });
56751
56955
  }
56752
56956
  } else {
56753
- const userStatus2 = await io.getUserStatus();
56754
- if (userStatus2) {
56957
+ const userStatus = await io.getUserStatus();
56958
+ if (userStatus) {
56755
56959
  await io.updateUserStatus({ statusline: actualStatusline });
56756
56960
  } else {
56757
56961
  await io.writeUserStatus({
@@ -56807,8 +57011,8 @@ var require_doctor_engine = __commonJS({
56807
57011
  },
56808
57012
  gitignore: "unknown"
56809
57013
  });
56810
- const userStatus2 = await io.getUserStatus();
56811
- if (!userStatus2) {
57014
+ const userStatus = await io.getUserStatus();
57015
+ if (!userStatus) {
56812
57016
  await io.writeUserStatus({
56813
57017
  version: 1,
56814
57018
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -56834,8 +57038,29 @@ var require_doctor_engine = __commonJS({
56834
57038
  scopes: projectApiKeyStatus.scopes
56835
57039
  };
56836
57040
  }
56837
- const userStatus = await io.getUserStatus();
56838
- const userSetupExists = userStatus !== null;
57041
+ const currentUserStatus = await io.getUserStatus();
57042
+ if (currentUserStatus) {
57043
+ let userNeedsUpdate = false;
57044
+ const updatedUserApiKeys = { ...currentUserStatus.apiKeys };
57045
+ for (let i = 0; i < keysToCheck.length; i++) {
57046
+ const keyName = keysToCheck[i];
57047
+ const detection = detections[i];
57048
+ const expectedUserStatus = (0, api_key_detector_js_1.buildUserApiKeyStatus)(detection);
57049
+ const currentUserEntry = currentUserStatus.apiKeys[keyName];
57050
+ const currentStatus = typeof currentUserEntry === "object" ? currentUserEntry.status : currentUserEntry ?? "missing";
57051
+ if (currentStatus !== "not-required" && (0, api_key_detector_js_1.toScopeStatus)(currentStatus) !== (0, api_key_detector_js_1.toScopeStatus)(expectedUserStatus.status)) {
57052
+ updatedUserApiKeys[keyName] = expectedUserStatus;
57053
+ userNeedsUpdate = true;
57054
+ }
57055
+ }
57056
+ if (userNeedsUpdate) {
57057
+ await io.updateUserStatus({
57058
+ apiKeys: updatedUserApiKeys
57059
+ });
57060
+ fixes.push("Updated stale user setup-status with current API key status");
57061
+ }
57062
+ }
57063
+ const userSetupExists = currentUserStatus !== null;
56839
57064
  const isStatuslineHealthy = actualStatusline !== "none";
56840
57065
  const openRouterActual = apiKeyResults.OPENROUTER_API_KEY.actual;
56841
57066
  const openRouterCached = apiKeyResults.OPENROUTER_API_KEY.cached;
@@ -57388,61 +57613,6 @@ var require_errors6 = __commonJS({
57388
57613
  }
57389
57614
  });
57390
57615
 
57391
- // ../sidekick-core/dist/staging-paths.js
57392
- var require_staging_paths = __commonJS({
57393
- "../sidekick-core/dist/staging-paths.js"(exports2) {
57394
- "use strict";
57395
- Object.defineProperty(exports2, "__esModule", { value: true });
57396
- exports2.CONSUMED_FILE_PATTERN = void 0;
57397
- exports2.getStagingRoot = getStagingRoot;
57398
- exports2.getHookDir = getHookDir;
57399
- exports2.getReminderPath = getReminderPath;
57400
- exports2.isValidPathSegment = isValidPathSegment;
57401
- exports2.validatePathSegment = validatePathSegment;
57402
- exports2.filterActiveReminderFiles = filterActiveReminderFiles;
57403
- exports2.createConsumedFilePattern = createConsumedFilePattern;
57404
- exports2.extractConsumedTimestamp = extractConsumedTimestamp;
57405
- var node_path_1 = require("node:path");
57406
- function getStagingRoot(stateDir, sessionId) {
57407
- return (0, node_path_1.join)(stateDir, "sessions", sessionId, "stage");
57408
- }
57409
- function getHookDir(stateDir, sessionId, hookName) {
57410
- return (0, node_path_1.join)(getStagingRoot(stateDir, sessionId), hookName);
57411
- }
57412
- function getReminderPath(stateDir, sessionId, hookName, reminderName) {
57413
- return (0, node_path_1.join)(getHookDir(stateDir, sessionId, hookName), `${reminderName}.json`);
57414
- }
57415
- function isValidPathSegment(s) {
57416
- if (s === "")
57417
- return false;
57418
- if (s === "." || s === "..")
57419
- return false;
57420
- if (s.includes("/") || s.includes("\\"))
57421
- return false;
57422
- if ((0, node_path_1.basename)(s) !== s)
57423
- return false;
57424
- return /^[a-zA-Z0-9._-]+$/.test(s);
57425
- }
57426
- function validatePathSegment(segment, name) {
57427
- if (!isValidPathSegment(segment)) {
57428
- throw new Error(`Invalid ${name}: must be a non-empty alphanumeric string without path separators`);
57429
- }
57430
- }
57431
- exports2.CONSUMED_FILE_PATTERN = /\.\d+\.json$/;
57432
- function filterActiveReminderFiles(files) {
57433
- return files.filter((f) => f.endsWith(".json") && !exports2.CONSUMED_FILE_PATTERN.test(f));
57434
- }
57435
- function createConsumedFilePattern(reminderName) {
57436
- return new RegExp(`^${reminderName}\\.(\\d+)\\.json$`);
57437
- }
57438
- function extractConsumedTimestamp(filename, reminderName) {
57439
- const pattern = createConsumedFilePattern(reminderName);
57440
- const match = pattern.exec(filename);
57441
- return match ? parseInt(match[1], 10) : null;
57442
- }
57443
- }
57444
- });
57445
-
57446
57616
  // ../sidekick-core/dist/staging-service.js
57447
57617
  var require_staging_service = __commonJS({
57448
57618
  "../sidekick-core/dist/staging-service.js"(exports2) {
@@ -58318,6 +58488,9 @@ var require_path_resolver = __commonJS({
58318
58488
  sessionStagingDir(sessionId) {
58319
58489
  return (0, node_path_1.join)(this.sessionRootDir(sessionId), "stage");
58320
58490
  }
58491
+ sessionLogsDir(sessionId) {
58492
+ return (0, node_path_1.join)(this.sessionRootDir(sessionId), "logs");
58493
+ }
58321
58494
  hookStagingDir(sessionId, hookName) {
58322
58495
  return (0, node_path_1.join)(this.sessionStagingDir(sessionId), hookName);
58323
58496
  }
@@ -63038,9 +63211,9 @@ var require_dist4 = __commonJS({
63038
63211
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
63039
63212
  };
63040
63213
  Object.defineProperty(exports2, "__esModule", { value: true });
63041
- exports2.readKeyFromEnvFile = exports2.determineOverallStatus = exports2.toScopeStatus = exports2.LEGACY_USER_STATUS_FILENAME = exports2.PROJECT_STATUS_FILENAME = exports2.USER_STATUS_FILENAME = exports2.createSetupStatusService = exports2.SetupStatusService = exports2.DaemonClient = exports2.findZombieDaemons = exports2.killZombieDaemons = exports2.killAllDaemons = exports2.logEvent = exports2.LogEvents = exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.getComponentLogLevel = exports2.setupGlobalErrorHandlers = exports2.createLoggerFacade = exports2.createLogManager = exports2.createConsoleLogger = exports2.getUserDaemonsDir = exports2.getUserPidPath = exports2.getTokenPath = exports2.getSocketPath = exports2.getProjectHash = exports2.getPidPath = exports2.getLockPath = exports2.IpcService = exports2.IpcServer = exports2.loadPersonaFile = exports2.getDefaultPersonasDir = exports2.discoverPersonas = exports2.createPersonaLoader = exports2.reconstructTranscriptPath = exports2.encodeProjectPath = exports2.isPreCompactEvent = exports2.isStopEvent = exports2.isPostToolUseEvent = exports2.isPreToolUseEvent = exports2.isUserPromptSubmitEvent = exports2.isSessionEndEvent = exports2.isSessionStartEvent = exports2.isTranscriptEvent = exports2.isHookEvent = exports2.MetricsPersistPayloadSchema = exports2.CleanupPayloadSchema = exports2.ResumeGenerationPayloadSchema = exports2.SessionSummaryPayloadSchema = exports2.TaskTypes = void 0;
63042
- exports2.StateService = exports2.createHookableLogger = exports2.InstrumentedProfileProviderFactory = exports2.InstrumentedLLMProvider = exports2.ServiceFactoryImpl = exports2.TranscriptServiceImpl = exports2.createDefaultTokenUsage = exports2.createDefaultMetrics = exports2.copyWithTimestampSync = exports2.renameWithTimestampSync = exports2.renameWithTimestamp = exports2.copyWithTimestamp = exports2.getTimestampedPath = exports2.extractToolResultPreview = exports2.extractToolCallPreview = exports2.extractTextFromContent = exports2.extractContentPreview = exports2.HandlerRegistryImpl = exports2.extractConsumedTimestamp = exports2.createConsumedFilePattern = exports2.CONSUMED_FILE_PATTERN = exports2.filterActiveReminderFiles = exports2.validatePathSegment = exports2.isValidPathSegment = exports2.getReminderPath = exports2.getHookDir = exports2.getStagingRoot = exports2.SessionScopedStagingService = exports2.StagingServiceCore = exports2.GITIGNORE_ENTRIES = exports2.SIDEKICK_SECTION_END = exports2.SIDEKICK_SECTION_START = exports2.detectGitignoreStatus = exports2.removeGitignoreSection = exports2.installGitignoreSection = exports2.validateOpenAIKey = exports2.validateOpenRouterKey = exports2.runDoctorCheck = exports2.detectPluginLiveness = exports2.detectPluginInstallation = exports2.detectActualStatusline = exports2.spawnWithTimeout = exports2.getDoctorTimeout = exports2.DOCTOR_TIMEOUTS = exports2.projectApiKeyStatusFromHealth = exports2.userApiKeyStatusFromHealth = exports2.buildProjectApiKeyStatus = exports2.buildUserApiKeyStatus = exports2.detectAllApiKeys = exports2.detectActualApiKey = void 0;
63043
- exports2.CoalescingGuard = exports2.loadUserProfile = exports2.parseGitStatusOutput = exports2.getGitFileStatus = exports2.isInSandbox = exports2.updateDaemonHealth = exports2.readDaemonHealth = exports2.ProjectRegistryService = exports2.encodeProjectDir = exports2.DaemonGlobalLogMetricsDescriptor = exports2.CliLogMetricsDescriptor = exports2.DaemonLogMetricsDescriptor = exports2.CompactionHistoryDescriptor = exports2.TranscriptMetricsDescriptor = exports2.GlobalStateAccessor = exports2.SessionStateAccessor = exports2.globalState = exports2.sessionState = exports2.StateCorruptError = exports2.StateNotFoundError = void 0;
63214
+ exports2.toScopeStatus = exports2.LEGACY_USER_STATUS_FILENAME = exports2.PROJECT_STATUS_FILENAME = exports2.USER_STATUS_FILENAME = exports2.createSetupStatusService = exports2.SetupStatusService = exports2.DaemonClient = exports2.findZombieDaemons = exports2.killZombieDaemons = exports2.killAllDaemons = exports2.SessionLogWriter = exports2.setSessionLogWriter = exports2.logEvent = exports2.LogEvents = exports2.DEFAULT_MAX_FILES = exports2.DEFAULT_ROTATE_SIZE_BYTES = exports2.getComponentLogLevel = exports2.setupGlobalErrorHandlers = exports2.createLoggerFacade = exports2.createLogManager = exports2.createConsoleLogger = exports2.getUserDaemonsDir = exports2.getUserPidPath = exports2.getTokenPath = exports2.getSocketPath = exports2.getProjectHash = exports2.getPidPath = exports2.getLockPath = exports2.IpcService = exports2.IpcServer = exports2.loadPersonaFile = exports2.getDefaultPersonasDir = exports2.discoverPersonas = exports2.createPersonaLoader = exports2.reconstructTranscriptPath = exports2.encodeProjectPath = exports2.isPreCompactEvent = exports2.isStopEvent = exports2.isPostToolUseEvent = exports2.isPreToolUseEvent = exports2.isUserPromptSubmitEvent = exports2.isSessionEndEvent = exports2.isSessionStartEvent = exports2.isTranscriptEvent = exports2.isHookEvent = exports2.MetricsPersistPayloadSchema = exports2.CleanupPayloadSchema = exports2.ResumeGenerationPayloadSchema = exports2.SessionSummaryPayloadSchema = exports2.TaskTypes = void 0;
63215
+ exports2.InstrumentedProfileProviderFactory = exports2.InstrumentedLLMProvider = exports2.ServiceFactoryImpl = exports2.TranscriptServiceImpl = exports2.createDefaultTokenUsage = exports2.createDefaultMetrics = exports2.copyWithTimestampSync = exports2.renameWithTimestampSync = exports2.renameWithTimestamp = exports2.copyWithTimestamp = exports2.getTimestampedPath = exports2.extractToolResultPreview = exports2.extractToolCallPreview = exports2.extractTextFromContent = exports2.extractContentPreview = exports2.HandlerRegistryImpl = exports2.extractConsumedTimestamp = exports2.createConsumedFilePattern = exports2.CONSUMED_FILE_PATTERN = exports2.filterActiveReminderFiles = exports2.validatePathSegment = exports2.isValidPathSegment = exports2.getReminderPath = exports2.getHookDir = exports2.getStagingRoot = exports2.SessionScopedStagingService = exports2.StagingServiceCore = exports2.GITIGNORE_ENTRIES = exports2.SIDEKICK_SECTION_END = exports2.SIDEKICK_SECTION_START = exports2.detectGitignoreStatus = exports2.removeGitignoreSection = exports2.installGitignoreSection = exports2.validateOpenAIKey = exports2.validateOpenRouterKey = exports2.runDoctorCheck = exports2.detectPluginLiveness = exports2.detectPluginInstallation = exports2.detectActualStatusline = exports2.spawnWithTimeout = exports2.getDoctorTimeout = exports2.DOCTOR_TIMEOUTS = exports2.projectApiKeyStatusFromHealth = exports2.userApiKeyStatusFromHealth = exports2.buildProjectApiKeyStatus = exports2.buildUserApiKeyStatus = exports2.detectAllApiKeys = exports2.detectActualApiKey = exports2.readKeyFromEnvFile = exports2.determineOverallStatus = void 0;
63216
+ exports2.CoalescingGuard = exports2.loadUserProfile = exports2.parseGitStatusOutput = exports2.getGitFileStatus = exports2.isInSandbox = exports2.updateDaemonHealth = exports2.readDaemonHealth = exports2.ProjectRegistryService = exports2.encodeProjectDir = exports2.DaemonGlobalLogMetricsDescriptor = exports2.CliLogMetricsDescriptor = exports2.DaemonLogMetricsDescriptor = exports2.CompactionHistoryDescriptor = exports2.TranscriptMetricsDescriptor = exports2.GlobalStateAccessor = exports2.SessionStateAccessor = exports2.globalState = exports2.sessionState = exports2.StateCorruptError = exports2.StateNotFoundError = exports2.StateService = exports2.createHookableLogger = void 0;
63044
63217
  var types_1 = require_dist();
63045
63218
  Object.defineProperty(exports2, "TaskTypes", { enumerable: true, get: function() {
63046
63219
  return types_1.TaskTypes;
@@ -63173,6 +63346,12 @@ var require_dist4 = __commonJS({
63173
63346
  Object.defineProperty(exports2, "logEvent", { enumerable: true, get: function() {
63174
63347
  return structured_logging_1.logEvent;
63175
63348
  } });
63349
+ Object.defineProperty(exports2, "setSessionLogWriter", { enumerable: true, get: function() {
63350
+ return structured_logging_1.setSessionLogWriter;
63351
+ } });
63352
+ Object.defineProperty(exports2, "SessionLogWriter", { enumerable: true, get: function() {
63353
+ return structured_logging_1.SessionLogWriter;
63354
+ } });
63176
63355
  var daemon_client_1 = require_daemon_client2();
63177
63356
  Object.defineProperty(exports2, "killAllDaemons", { enumerable: true, get: function() {
63178
63357
  return daemon_client_1.killAllDaemons;
@@ -78117,6 +78296,7 @@ var require_daemon = __commonJS({
78117
78296
  token = "";
78118
78297
  registryService;
78119
78298
  timerManager;
78299
+ sessionLogWriter;
78120
78300
  /** Cache persona for handoff on clear. */
78121
78301
  cachePersonaForClear(personaId) {
78122
78302
  this.lastClearedPersona = { personaId, timestamp: Date.now() };
@@ -78159,8 +78339,9 @@ var require_daemon = __commonJS({
78159
78339
  destinations: {
78160
78340
  file: {
78161
78341
  path: path_1.default.join(logDir, "sidekickd.log"),
78162
- maxSizeBytes: this.configService.core.logging.rotation?.maxSizeBytes ?? 10485760,
78163
- maxFiles: this.configService.core.logging.rotation?.maxFiles ?? 5
78342
+ maxSizeBytes: this.configService.core.logging.rotation?.maxSizeBytes ?? 2097152,
78343
+ // 2MB (ephemeral debug window)
78344
+ maxFiles: this.configService.core.logging.rotation?.maxFiles ?? 2
78164
78345
  },
78165
78346
  console: { enabled: this.configService.core.logging.consoleEnabled }
78166
78347
  }
@@ -78172,6 +78353,13 @@ var require_daemon = __commonJS({
78172
78353
  getStartTime: () => this.timerManager.startTime
78173
78354
  });
78174
78355
  this.logger = this.logMetrics.createCountingLogger();
78356
+ const sessionsDir = path_1.default.join(projectDir2, ".sidekick", "sessions");
78357
+ this.sessionLogWriter = new core_1.SessionLogWriter({
78358
+ sessionsDir,
78359
+ maxHandles: 10,
78360
+ idleTimeoutMs: 30 * 60 * 1e3
78361
+ });
78362
+ (0, core_1.setSessionLogWriter)(this.sessionLogWriter);
78175
78363
  this.stateService = new core_1.StateService(projectDir2, {
78176
78364
  cache: true,
78177
78365
  logger: this.logger,
@@ -78279,6 +78467,8 @@ var require_daemon = __commonJS({
78279
78467
  }
78280
78468
  async stop() {
78281
78469
  this.logger.info("Daemon stopping");
78470
+ (0, core_1.setSessionLogWriter)(null);
78471
+ await this.sessionLogWriter.closeAll();
78282
78472
  this.timerManager.stopAll();
78283
78473
  this.configWatcher.stop();
78284
78474
  this.personaWatcher.stop();
@@ -78604,6 +78794,7 @@ var require_daemon = __commonJS({
78604
78794
  await this.llmManager.shutdownSessionProvider(sessionId, log);
78605
78795
  this.logMetrics.deleteSessionCounters(sessionId);
78606
78796
  await this.serviceFactory.shutdownSession(sessionId);
78797
+ await this.sessionLogWriter.closeSession(sessionId);
78607
78798
  log.info("Session ended");
78608
78799
  }
78609
78800
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scotthamilton77/sidekick",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "AI pair programming assistant with personas, session tracking, and contextual nudges",
5
5
  "bin": {
6
6
  "sidekick": "dist/bin.js"