aisnitch 0.2.3 → 0.2.4

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/index.d.cts CHANGED
@@ -81,10 +81,10 @@ declare const ConfigSchema: z.ZodObject<{
81
81
  }, z.core.$strict>>;
82
82
  idleTimeoutMs: z.ZodDefault<z.ZodNumber>;
83
83
  logLevel: z.ZodDefault<z.ZodEnum<{
84
- error: "error";
85
84
  debug: "debug";
86
85
  info: "info";
87
86
  warn: "warn";
87
+ error: "error";
88
88
  }>>;
89
89
  }, z.core.$strict>;
90
90
  /**
@@ -948,10 +948,14 @@ declare class AdapterRegistry {
948
948
  getStatus(): AdapterStatus[];
949
949
  /**
950
950
  * Starts every adapter enabled in the current AISnitch config.
951
+ * 📖 Each adapter is started independently — one failure does not prevent
952
+ * the others from starting.
951
953
  */
952
954
  startAll(config: AISnitchConfig): Promise<void>;
953
955
  /**
954
956
  * Stops every adapter in reverse registration order.
957
+ * 📖 Each adapter is stopped independently — one failure does not prevent
958
+ * the others from being stopped.
955
959
  */
956
960
  stopAll(): Promise<void>;
957
961
  }
@@ -1615,8 +1619,15 @@ declare class Pipeline {
1615
1619
  * Registers a future adapter-specific hook handler for one tool.
1616
1620
  */
1617
1621
  registerHookHandler(tool: ToolName, handler: HookHandler): void;
1622
+ /**
1623
+ * 📖 Rolls back any components that were successfully started before a
1624
+ * failure occurred, preventing orphaned servers or leaking resources.
1625
+ */
1626
+ private rollbackPartialStart;
1618
1627
  /**
1619
1628
  * Publishes an event after best-effort context enrichment.
1629
+ * 📖 If enrichment fails, the original event is published un-enriched
1630
+ * rather than being dropped entirely.
1620
1631
  */
1621
1632
  publishEvent(event: AISnitchEvent, context?: ProcessContext): Promise<boolean>;
1622
1633
  /**
@@ -1629,6 +1640,7 @@ declare class Pipeline {
1629
1640
  getEventBus(): EventBus;
1630
1641
  private getHealthSnapshot;
1631
1642
  private handleHook;
1643
+ private handleHookInner;
1632
1644
  private isPlainRecord;
1633
1645
  }
1634
1646
 
package/dist/index.d.ts CHANGED
@@ -81,10 +81,10 @@ declare const ConfigSchema: z.ZodObject<{
81
81
  }, z.core.$strict>>;
82
82
  idleTimeoutMs: z.ZodDefault<z.ZodNumber>;
83
83
  logLevel: z.ZodDefault<z.ZodEnum<{
84
- error: "error";
85
84
  debug: "debug";
86
85
  info: "info";
87
86
  warn: "warn";
87
+ error: "error";
88
88
  }>>;
89
89
  }, z.core.$strict>;
90
90
  /**
@@ -948,10 +948,14 @@ declare class AdapterRegistry {
948
948
  getStatus(): AdapterStatus[];
949
949
  /**
950
950
  * Starts every adapter enabled in the current AISnitch config.
951
+ * 📖 Each adapter is started independently — one failure does not prevent
952
+ * the others from starting.
951
953
  */
952
954
  startAll(config: AISnitchConfig): Promise<void>;
953
955
  /**
954
956
  * Stops every adapter in reverse registration order.
957
+ * 📖 Each adapter is stopped independently — one failure does not prevent
958
+ * the others from being stopped.
955
959
  */
956
960
  stopAll(): Promise<void>;
957
961
  }
@@ -1615,8 +1619,15 @@ declare class Pipeline {
1615
1619
  * Registers a future adapter-specific hook handler for one tool.
1616
1620
  */
1617
1621
  registerHookHandler(tool: ToolName, handler: HookHandler): void;
1622
+ /**
1623
+ * 📖 Rolls back any components that were successfully started before a
1624
+ * failure occurred, preventing orphaned servers or leaking resources.
1625
+ */
1626
+ private rollbackPartialStart;
1618
1627
  /**
1619
1628
  * Publishes an event after best-effort context enrichment.
1629
+ * 📖 If enrichment fails, the original event is published un-enriched
1630
+ * rather than being dropped entirely.
1620
1631
  */
1621
1632
  publishEvent(event: AISnitchEvent, context?: ProcessContext): Promise<boolean>;
1622
1633
  /**
@@ -1629,6 +1640,7 @@ declare class Pipeline {
1629
1640
  getEventBus(): EventBus;
1630
1641
  private getHealthSnapshot;
1631
1642
  private handleHook;
1643
+ private handleHookInner;
1632
1644
  private isPlainRecord;
1633
1645
  }
1634
1646
 
package/dist/index.js CHANGED
@@ -346,15 +346,24 @@ var BaseAdapter = class {
346
346
  cwd: data.cwd ?? context.cwd
347
347
  }
348
348
  });
349
- const published = await this.publishEventImplementation(event, {
350
- cwd: context.cwd,
351
- env: context.env,
352
- hookPayload: context.hookPayload,
353
- pid: context.pid,
354
- sessionId,
355
- source: context.source,
356
- transcriptPath: context.transcriptPath
357
- });
349
+ let published;
350
+ try {
351
+ published = await this.publishEventImplementation(event, {
352
+ cwd: context.cwd,
353
+ env: context.env,
354
+ hookPayload: context.hookPayload,
355
+ pid: context.pid,
356
+ sessionId,
357
+ source: context.source,
358
+ transcriptPath: context.transcriptPath
359
+ });
360
+ } catch (error) {
361
+ logger.error(
362
+ { error, eventType: type, adapter: this.name, sessionId },
363
+ "\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
364
+ );
365
+ published = false;
366
+ }
358
367
  if (published) {
359
368
  this.eventsEmitted += 1;
360
369
  }
@@ -5482,22 +5491,40 @@ var AdapterRegistry = class {
5482
5491
  }
5483
5492
  /**
5484
5493
  * Starts every adapter enabled in the current AISnitch config.
5494
+ * 📖 Each adapter is started independently — one failure does not prevent
5495
+ * the others from starting.
5485
5496
  */
5486
5497
  async startAll(config) {
5487
5498
  for (const adapter of this.list()) {
5488
5499
  if (config.adapters[adapter.name]?.enabled !== true) {
5489
5500
  continue;
5490
5501
  }
5491
- await adapter.start();
5502
+ try {
5503
+ await adapter.start();
5504
+ } catch (error) {
5505
+ logger.error(
5506
+ { error, adapter: adapter.name },
5507
+ `\u{1F4D6} Failed to start adapter "${adapter.name}" \u2014 skipping`
5508
+ );
5509
+ }
5492
5510
  }
5493
5511
  }
5494
5512
  /**
5495
5513
  * Stops every adapter in reverse registration order.
5514
+ * 📖 Each adapter is stopped independently — one failure does not prevent
5515
+ * the others from being stopped.
5496
5516
  */
5497
5517
  async stopAll() {
5498
5518
  const adapters = this.list().reverse();
5499
5519
  for (const adapter of adapters) {
5500
- await adapter.stop();
5520
+ try {
5521
+ await adapter.stop();
5522
+ } catch (error) {
5523
+ logger.warn(
5524
+ { error, adapter: adapter.name },
5525
+ `\u{1F4D6} Error stopping adapter "${adapter.name}" \u2014 continuing`
5526
+ );
5527
+ }
5501
5528
  }
5502
5529
  }
5503
5530
  };
@@ -6460,8 +6487,16 @@ var EventBus = class {
6460
6487
  },
6461
6488
  "Published event"
6462
6489
  );
6463
- this.emitter.emit("event", parsedEvent.data);
6464
- this.emitter.emit(`event:${parsedEvent.data.type}`, parsedEvent.data);
6490
+ try {
6491
+ this.emitter.emit("event", parsedEvent.data);
6492
+ } catch (error) {
6493
+ logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus global subscriber");
6494
+ }
6495
+ try {
6496
+ this.emitter.emit(`event:${parsedEvent.data.type}`, parsedEvent.data);
6497
+ } catch (error) {
6498
+ logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus typed subscriber");
6499
+ }
6465
6500
  return true;
6466
6501
  }
6467
6502
  /**
@@ -6742,6 +6777,7 @@ var WSServer = class {
6742
6777
  });
6743
6778
  socket.on("error", (error) => {
6744
6779
  logger.warn({ error }, "WebSocket consumer error");
6780
+ this.consumers.delete(socket);
6745
6781
  });
6746
6782
  const welcomeMessage = {
6747
6783
  type: "welcome",
@@ -6898,10 +6934,17 @@ var HTTPReceiver = class {
6898
6934
  }
6899
6935
  async handleRequest(request, response, options) {
6900
6936
  this.requestCount += 1;
6901
- const requestUrl = new URL(
6902
- request.url ?? "/",
6903
- `http://${this.host}:${this.port ?? options.port}`
6904
- );
6937
+ let requestUrl;
6938
+ try {
6939
+ requestUrl = new URL(
6940
+ request.url ?? "/",
6941
+ `http://${this.host}:${this.port ?? options.port}`
6942
+ );
6943
+ } catch {
6944
+ this.invalidRequestCount += 1;
6945
+ this.sendJson(response, 400, { error: "malformed request url" });
6946
+ return;
6947
+ }
6905
6948
  if (request.method === "GET" && requestUrl.pathname === "/health") {
6906
6949
  this.sendJson(response, 200, options.getHealthSnapshot());
6907
6950
  return;
@@ -7246,26 +7289,32 @@ var Pipeline = class {
7246
7289
  await adapter.handleHook(payload);
7247
7290
  });
7248
7291
  }
7292
+ try {
7293
+ this.wsPort = await this.wsServer.start({
7294
+ port: resolvedWsPort,
7295
+ eventBus: this.eventBus,
7296
+ activeTools
7297
+ });
7298
+ this.httpPort = await this.httpReceiver.start({
7299
+ port: resolvedHttpPort,
7300
+ onHook: async (tool, payload) => {
7301
+ await this.handleHook(tool, payload);
7302
+ },
7303
+ getHealthSnapshot: () => this.getHealthSnapshot()
7304
+ });
7305
+ this.socketPath = await this.udsServer.start({
7306
+ socketPath,
7307
+ onEvent: async (event) => {
7308
+ await this.publishEvent(event);
7309
+ }
7310
+ });
7311
+ await this.adapterRegistry.startAll(config);
7312
+ } catch (error) {
7313
+ logger.error({ error }, "\u{1F4D6} Pipeline start failed \u2014 rolling back already-started components");
7314
+ await this.rollbackPartialStart();
7315
+ throw error;
7316
+ }
7249
7317
  this.startedAt = Date.now();
7250
- this.wsPort = await this.wsServer.start({
7251
- port: resolvedWsPort,
7252
- eventBus: this.eventBus,
7253
- activeTools
7254
- });
7255
- this.httpPort = await this.httpReceiver.start({
7256
- port: resolvedHttpPort,
7257
- onHook: async (tool, payload) => {
7258
- await this.handleHook(tool, payload);
7259
- },
7260
- getHealthSnapshot: () => this.getHealthSnapshot()
7261
- });
7262
- this.socketPath = await this.udsServer.start({
7263
- socketPath,
7264
- onEvent: async (event) => {
7265
- await this.publishEvent(event);
7266
- }
7267
- });
7268
- await this.adapterRegistry.startAll(config);
7269
7318
  logger.info(this.getStatus(), "Core pipeline started");
7270
7319
  return this.getStatus();
7271
7320
  }
@@ -7273,10 +7322,25 @@ var Pipeline = class {
7273
7322
  * Stops every pipeline component in reverse dependency order.
7274
7323
  */
7275
7324
  async stop() {
7276
- await this.adapterRegistry?.stopAll();
7277
- await this.httpReceiver.stop();
7278
- await this.udsServer.stop();
7279
- await this.wsServer.stop();
7325
+ const stopSafely = async (label, fn) => {
7326
+ try {
7327
+ await fn();
7328
+ } catch (error) {
7329
+ logger.warn({ error }, `\u{1F4D6} Error while stopping ${label} \u2014 continuing shutdown`);
7330
+ }
7331
+ };
7332
+ await stopSafely("adapter registry", async () => {
7333
+ await this.adapterRegistry?.stopAll();
7334
+ });
7335
+ await stopSafely("HTTP receiver", async () => {
7336
+ await this.httpReceiver.stop();
7337
+ });
7338
+ await stopSafely("UDS server", async () => {
7339
+ await this.udsServer.stop();
7340
+ });
7341
+ await stopSafely("WebSocket server", async () => {
7342
+ await this.wsServer.stop();
7343
+ });
7280
7344
  this.eventBus.unsubscribeAll();
7281
7345
  this.adapterRegistry = null;
7282
7346
  this.enabledTools.clear();
@@ -7293,11 +7357,50 @@ var Pipeline = class {
7293
7357
  registerHookHandler(tool, handler) {
7294
7358
  this.hookHandlers.set(tool, handler);
7295
7359
  }
7360
+ /**
7361
+ * 📖 Rolls back any components that were successfully started before a
7362
+ * failure occurred, preventing orphaned servers or leaking resources.
7363
+ */
7364
+ async rollbackPartialStart() {
7365
+ const stopSafe = async (label, fn) => {
7366
+ try {
7367
+ await fn();
7368
+ } catch (error) {
7369
+ logger.warn({ error }, `\u{1F4D6} Error rolling back ${label}`);
7370
+ }
7371
+ };
7372
+ await stopSafe("adapter registry", async () => {
7373
+ await this.adapterRegistry?.stopAll();
7374
+ });
7375
+ await stopSafe("UDS server", async () => {
7376
+ await this.udsServer.stop();
7377
+ });
7378
+ await stopSafe("HTTP receiver", async () => {
7379
+ await this.httpReceiver.stop();
7380
+ });
7381
+ await stopSafe("WebSocket server", async () => {
7382
+ await this.wsServer.stop();
7383
+ });
7384
+ this.adapterRegistry = null;
7385
+ this.enabledTools.clear();
7386
+ this.hookHandlers.clear();
7387
+ }
7296
7388
  /**
7297
7389
  * Publishes an event after best-effort context enrichment.
7390
+ * 📖 If enrichment fails, the original event is published un-enriched
7391
+ * rather than being dropped entirely.
7298
7392
  */
7299
7393
  async publishEvent(event, context = {}) {
7300
- const enrichedEvent = await this.contextDetector.enrich(event, context);
7394
+ let enrichedEvent;
7395
+ try {
7396
+ enrichedEvent = await this.contextDetector.enrich(event, context);
7397
+ } catch (error) {
7398
+ logger.warn(
7399
+ { error, eventId: event.id },
7400
+ "\u{1F4D6} Context enrichment failed \u2014 publishing un-enriched event"
7401
+ );
7402
+ enrichedEvent = event;
7403
+ }
7301
7404
  return this.eventBus.publish(enrichedEvent);
7302
7405
  }
7303
7406
  /**
@@ -7333,6 +7436,16 @@ var Pipeline = class {
7333
7436
  };
7334
7437
  }
7335
7438
  async handleHook(tool, payload) {
7439
+ try {
7440
+ await this.handleHookInner(tool, payload);
7441
+ } catch (error) {
7442
+ logger.error(
7443
+ { error, tool },
7444
+ "\u{1F4D6} Unhandled error in hook handler \u2014 swallowing to prevent daemon crash"
7445
+ );
7446
+ }
7447
+ }
7448
+ async handleHookInner(tool, payload) {
7336
7449
  if (!this.enabledTools.has(tool)) {
7337
7450
  logger.debug({ tool }, "Ignoring hook for disabled tool");
7338
7451
  return;
@@ -8508,18 +8621,22 @@ function parseSocketPayload(data) {
8508
8621
  return parsedEvent.success ? parsedEvent.data : null;
8509
8622
  }
8510
8623
  function parseUnknownPayload(data) {
8511
- if (typeof data === "string") {
8512
- return JSON.parse(data);
8513
- }
8514
- if (Array.isArray(data)) {
8515
- return JSON.parse(Buffer.concat(data).toString("utf8"));
8516
- }
8517
- if (data instanceof ArrayBuffer) {
8518
- return JSON.parse(
8519
- Buffer.from(new Uint8Array(data)).toString("utf8")
8520
- );
8624
+ try {
8625
+ if (typeof data === "string") {
8626
+ return JSON.parse(data);
8627
+ }
8628
+ if (Array.isArray(data)) {
8629
+ return JSON.parse(Buffer.concat(data).toString("utf8"));
8630
+ }
8631
+ if (data instanceof ArrayBuffer) {
8632
+ return JSON.parse(
8633
+ Buffer.from(new Uint8Array(data)).toString("utf8")
8634
+ );
8635
+ }
8636
+ return JSON.parse(Buffer.from(data).toString("utf8"));
8637
+ } catch {
8638
+ return null;
8521
8639
  }
8522
- return JSON.parse(Buffer.from(data).toString("utf8"));
8523
8640
  }
8524
8641
 
8525
8642
  // src/tui/hooks/useKeyBinds.ts
@@ -9247,16 +9364,20 @@ function ManagedDaemonApp({
9247
9364
  }
9248
9365
  function parseSocketPayload2(data) {
9249
9366
  let parsedPayload;
9250
- if (typeof data === "string") {
9251
- parsedPayload = JSON.parse(data);
9252
- } else if (Array.isArray(data)) {
9253
- parsedPayload = JSON.parse(Buffer.concat(data).toString("utf8"));
9254
- } else if (data instanceof ArrayBuffer) {
9255
- parsedPayload = JSON.parse(
9256
- Buffer.from(new Uint8Array(data)).toString("utf8")
9257
- );
9258
- } else {
9259
- parsedPayload = JSON.parse(Buffer.from(data).toString("utf8"));
9367
+ try {
9368
+ if (typeof data === "string") {
9369
+ parsedPayload = JSON.parse(data);
9370
+ } else if (Array.isArray(data)) {
9371
+ parsedPayload = JSON.parse(Buffer.concat(data).toString("utf8"));
9372
+ } else if (data instanceof ArrayBuffer) {
9373
+ parsedPayload = JSON.parse(
9374
+ Buffer.from(new Uint8Array(data)).toString("utf8")
9375
+ );
9376
+ } else {
9377
+ parsedPayload = JSON.parse(Buffer.from(data).toString("utf8"));
9378
+ }
9379
+ } catch {
9380
+ return null;
9260
9381
  }
9261
9382
  if (typeof parsedPayload === "object" && parsedPayload !== null && "type" in parsedPayload && parsedPayload.type === "welcome") {
9262
9383
  return null;
@@ -9327,18 +9448,22 @@ async function attachWebSocketMonitor(url, output) {
9327
9448
  };
9328
9449
  }
9329
9450
  function parseSocketMessage(data) {
9330
- if (typeof data === "string") {
9331
- return JSON.parse(data);
9332
- }
9333
- if (Array.isArray(data)) {
9334
- return JSON.parse(Buffer.concat(data).toString("utf8"));
9335
- }
9336
- if (data instanceof ArrayBuffer) {
9337
- return JSON.parse(
9338
- Buffer.from(new Uint8Array(data)).toString("utf8")
9339
- );
9451
+ try {
9452
+ if (typeof data === "string") {
9453
+ return JSON.parse(data);
9454
+ }
9455
+ if (Array.isArray(data)) {
9456
+ return JSON.parse(Buffer.concat(data).toString("utf8"));
9457
+ }
9458
+ if (data instanceof ArrayBuffer) {
9459
+ return JSON.parse(
9460
+ Buffer.from(new Uint8Array(data)).toString("utf8")
9461
+ );
9462
+ }
9463
+ return JSON.parse(Buffer.from(data).toString("utf8"));
9464
+ } catch {
9465
+ return null;
9340
9466
  }
9341
- return JSON.parse(Buffer.from(data).toString("utf8"));
9342
9467
  }
9343
9468
  function isWelcomeMessage(payload) {
9344
9469
  if (typeof payload !== "object" || payload === null) {