aisnitch 0.2.19 → 0.2.21

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.js CHANGED
@@ -119,6 +119,399 @@ function sanitizeToken(value) {
119
119
  import { homedir } from "os";
120
120
  import { z as z2 } from "zod";
121
121
 
122
+ // src/core/errors.ts
123
+ var AISnitchError = class _AISnitchError extends Error {
124
+ /**
125
+ * Machine-readable error code for programmatic handling.
126
+ * Format: `SUBCATEGORY_SPECIFIC_DETAIL` (uppercase with underscores).
127
+ */
128
+ code;
129
+ /**
130
+ * Arbitrary context bag forwarded to the logger for structured debugging.
131
+ */
132
+ context;
133
+ constructor(message, code, context) {
134
+ super(message);
135
+ this.name = "AISnitchError";
136
+ this.code = code;
137
+ this.context = context;
138
+ if (Error.captureStackTrace) {
139
+ Error.captureStackTrace(this, _AISnitchError);
140
+ }
141
+ }
142
+ /**
143
+ * Full error chain for logging: `[name] code — message`.
144
+ */
145
+ toString() {
146
+ return `${this.name} [${this.code}] \u2014 ${this.message}`;
147
+ }
148
+ /**
149
+ * JSON serialization friendly to pino serializers.
150
+ */
151
+ toJSON() {
152
+ return {
153
+ name: this.name,
154
+ code: this.code,
155
+ message: this.message,
156
+ context: this.context,
157
+ stack: this.stack
158
+ };
159
+ }
160
+ };
161
+ var AdapterError = class _AdapterError extends AISnitchError {
162
+ constructor(message, code, context) {
163
+ super(message, code, context);
164
+ this.name = "AdapterError";
165
+ if (Error.captureStackTrace) {
166
+ Error.captureStackTrace(this, _AdapterError);
167
+ }
168
+ }
169
+ /**
170
+ * Factory for adapter-specific errors with auto-generated codes.
171
+ */
172
+ static withAutoCode(message, tool, context) {
173
+ const sanitizedTool = tool.replace(/[^a-z0-9]/gi, "_").toUpperCase();
174
+ const code = `ADAPTER_${sanitizedTool}_ERROR`;
175
+ return new _AdapterError(message, code, { tool, ...context });
176
+ }
177
+ };
178
+ var PipelineError = class _PipelineError extends AISnitchError {
179
+ constructor(message, code, context) {
180
+ super(message, code, context);
181
+ this.name = "PipelineError";
182
+ if (Error.captureStackTrace) {
183
+ Error.captureStackTrace(this, _PipelineError);
184
+ }
185
+ }
186
+ };
187
+ var ValidationError = class _ValidationError extends AISnitchError {
188
+ constructor(message, code, context) {
189
+ super(message, code, context);
190
+ this.name = "ValidationError";
191
+ if (Error.captureStackTrace) {
192
+ Error.captureStackTrace(this, _ValidationError);
193
+ }
194
+ }
195
+ };
196
+ var NetworkError = class _NetworkError extends AISnitchError {
197
+ constructor(message, code, context) {
198
+ super(message, code, context);
199
+ this.name = "NetworkError";
200
+ if (Error.captureStackTrace) {
201
+ Error.captureStackTrace(this, _NetworkError);
202
+ }
203
+ }
204
+ };
205
+ var TimeoutError = class _TimeoutError extends AISnitchError {
206
+ constructor(message, code, context) {
207
+ super(message, code, context);
208
+ this.name = "TimeoutError";
209
+ if (Error.captureStackTrace) {
210
+ Error.captureStackTrace(this, _TimeoutError);
211
+ }
212
+ }
213
+ };
214
+ function isAISnitchError(error) {
215
+ return error instanceof AISnitchError;
216
+ }
217
+ function isRetryableError(error) {
218
+ if (!isAISnitchError(error)) {
219
+ if (error instanceof Error) {
220
+ const code = error.code;
221
+ const retryableCodes = /* @__PURE__ */ new Set([
222
+ "ECONNREFUSED",
223
+ "ECONNRESET",
224
+ "ETIMEDOUT",
225
+ "ENOTFOUND",
226
+ "EHOSTUNREACH",
227
+ "EPIPE",
228
+ "EPERM"
229
+ // sometimes transient on macOS file locks
230
+ ]);
231
+ if (typeof code === "string" && retryableCodes.has(code)) {
232
+ return true;
233
+ }
234
+ }
235
+ return false;
236
+ }
237
+ const retryableCategories = /* @__PURE__ */ new Set(["TIMEOUT", "NETWORK"]);
238
+ for (const category of retryableCategories) {
239
+ if (error.code.startsWith(category)) {
240
+ return true;
241
+ }
242
+ }
243
+ const retryablePatterns = [
244
+ /^ADAPTER_.*_(FILE_IO|NETWORK|PROCESS_DETECT)_ERROR$/,
245
+ /^PIPELINE_.*_(RETRY|RECONNECT)_ERROR$/
246
+ ];
247
+ for (const pattern of retryablePatterns) {
248
+ if (pattern.test(error.code)) {
249
+ return true;
250
+ }
251
+ }
252
+ return false;
253
+ }
254
+
255
+ // src/core/circuit-breaker.ts
256
+ var CircuitOpenError = class _CircuitOpenError extends AISnitchError {
257
+ constructor(circuitId, state) {
258
+ super(
259
+ `Circuit "${circuitId}" is OPEN \u2014 operation rejected`,
260
+ "CIRCUIT_OPEN",
261
+ { circuitId, failures: state.failures, lastFailureAt: state.lastFailureAt }
262
+ );
263
+ this.circuitId = circuitId;
264
+ this.state = state;
265
+ this.name = "CircuitOpenError";
266
+ if (Error.captureStackTrace) {
267
+ Error.captureStackTrace(this, _CircuitOpenError);
268
+ }
269
+ }
270
+ toString() {
271
+ return `${this.name} [${this.code}] "${this.circuitId}" \u2014 failures=${this.state.failures}`;
272
+ }
273
+ };
274
+ var DEFAULT_OPTIONS = {
275
+ failureThreshold: 5,
276
+ halfOpenAfterMs: 3e4,
277
+ id: "unnamed",
278
+ resetOnSuccess: true,
279
+ shouldCountAsFailure: isRetryableError,
280
+ windowMs: 6e4
281
+ };
282
+ var CircuitBreaker = class {
283
+ failures = 0;
284
+ lastFailureAt = null;
285
+ state = "closed";
286
+ halfOpenTestStartedAt = null;
287
+ options;
288
+ constructor(options = {}) {
289
+ this.options = {
290
+ ...DEFAULT_OPTIONS,
291
+ ...options,
292
+ // Re-spread to ensure all fields have defaults
293
+ failureThreshold: options.failureThreshold ?? DEFAULT_OPTIONS.failureThreshold,
294
+ halfOpenAfterMs: options.halfOpenAfterMs ?? DEFAULT_OPTIONS.halfOpenAfterMs,
295
+ id: options.id ?? DEFAULT_OPTIONS.id,
296
+ resetOnSuccess: options.resetOnSuccess ?? DEFAULT_OPTIONS.resetOnSuccess,
297
+ shouldCountAsFailure: options.shouldCountAsFailure ?? DEFAULT_OPTIONS.shouldCountAsFailure,
298
+ windowMs: options.windowMs ?? DEFAULT_OPTIONS.windowMs
299
+ };
300
+ }
301
+ /**
302
+ * Executes an async operation through the circuit breaker.
303
+ *
304
+ * - If the circuit is CLOSED → runs `fn` and updates state based on result
305
+ * - If the circuit is HALF-OPEN → runs `fn` once to test recovery
306
+ * - If the circuit is OPEN → throws `CircuitOpenError` immediately (no call)
307
+ *
308
+ * @param fn - The async operation to protect
309
+ * @returns The result of `fn` if successful
310
+ * @throws CircuitOpenError if the circuit is OPEN
311
+ * @throws The error from `fn` if it throws (and `shouldCountAsFailure` returns true)
312
+ */
313
+ async execute(fn) {
314
+ switch (this.state) {
315
+ case "closed":
316
+ return this.executeClosed(fn);
317
+ case "half-open":
318
+ return this.executeHalfOpen(fn);
319
+ case "open":
320
+ if (this.shouldTransitionToHalfOpen()) {
321
+ this.transitionToHalfOpen();
322
+ return this.executeHalfOpen(fn);
323
+ }
324
+ throw new CircuitOpenError(this.options.id, this.getState());
325
+ }
326
+ }
327
+ /**
328
+ * Returns the current observable circuit state.
329
+ */
330
+ getState() {
331
+ return {
332
+ failures: this.failures,
333
+ lastFailureAt: this.lastFailureAt,
334
+ state: this.state
335
+ };
336
+ }
337
+ /**
338
+ * Forces the circuit to CLOSED (resets failure count and state).
339
+ * Useful for manual recovery after a known-fix or after a maintenance window.
340
+ */
341
+ reset() {
342
+ this.failures = 0;
343
+ this.lastFailureAt = null;
344
+ this.state = "closed";
345
+ this.halfOpenTestStartedAt = null;
346
+ logger.debug({ circuitId: this.options.id }, "Circuit breaker manually reset");
347
+ }
348
+ /**
349
+ * Pre-warms the circuit by performing one test call in HALF-OPEN state.
350
+ * If the circuit is already HALF-OPEN, this does nothing.
351
+ * If the circuit is CLOSED, this does nothing.
352
+ */
353
+ async preWarm(fn) {
354
+ if (this.state !== "open") {
355
+ return;
356
+ }
357
+ this.transitionToHalfOpen();
358
+ try {
359
+ await fn();
360
+ this.transitionToClosed();
361
+ } catch {
362
+ this.transitionToOpen();
363
+ }
364
+ }
365
+ // ─────────────────────────────────────────────────────────────────────────
366
+ // Private methods
367
+ // ─────────────────────────────────────────────────────────────────────────
368
+ async executeClosed(fn) {
369
+ try {
370
+ const result = await fn();
371
+ this.onSuccess();
372
+ return result;
373
+ } catch (error) {
374
+ this.onFailure(error);
375
+ throw error;
376
+ }
377
+ }
378
+ async executeHalfOpen(fn) {
379
+ this.halfOpenTestStartedAt = Date.now();
380
+ try {
381
+ const result = await fn();
382
+ this.transitionToClosed();
383
+ return result;
384
+ } catch (error) {
385
+ this.transitionToOpen();
386
+ throw error;
387
+ }
388
+ }
389
+ onSuccess() {
390
+ if (this.options.resetOnSuccess) {
391
+ this.failures = 0;
392
+ this.lastFailureAt = null;
393
+ } else {
394
+ this.failures = Math.max(0, this.failures - 1);
395
+ if (this.failures === 0) {
396
+ this.lastFailureAt = null;
397
+ }
398
+ }
399
+ logger.debug(
400
+ {
401
+ circuitId: this.options.id,
402
+ failures: this.failures
403
+ },
404
+ "Circuit breaker operation succeeded"
405
+ );
406
+ }
407
+ onFailure(error) {
408
+ if (!this.options.shouldCountAsFailure(error)) {
409
+ logger.debug(
410
+ { circuitId: this.options.id, error },
411
+ "Circuit breaker operation failed but error is not counted as failure"
412
+ );
413
+ return;
414
+ }
415
+ this.failures += 1;
416
+ this.lastFailureAt = Date.now();
417
+ if (this.failures >= this.options.failureThreshold) {
418
+ this.transitionToOpen();
419
+ } else {
420
+ logger.debug(
421
+ {
422
+ circuitId: this.options.id,
423
+ failures: this.failures,
424
+ threshold: this.options.failureThreshold
425
+ },
426
+ "Circuit breaker recorded failure"
427
+ );
428
+ }
429
+ }
430
+ transitionToOpen() {
431
+ if (this.state === "open") {
432
+ return;
433
+ }
434
+ this.state = "open";
435
+ this.halfOpenTestStartedAt = null;
436
+ logger.warn(
437
+ {
438
+ circuitId: this.options.id,
439
+ failures: this.failures,
440
+ windowMs: this.options.windowMs
441
+ },
442
+ "\u{1F534} Circuit breaker OPEN \u2014 blocking operations"
443
+ );
444
+ }
445
+ transitionToHalfOpen() {
446
+ this.state = "half-open";
447
+ this.halfOpenTestStartedAt = Date.now();
448
+ logger.info(
449
+ { circuitId: this.options.id },
450
+ "\u{1F7E1} Circuit breaker HALF-OPEN \u2014 testing recovery"
451
+ );
452
+ }
453
+ transitionToClosed() {
454
+ this.state = "closed";
455
+ this.failures = 0;
456
+ this.lastFailureAt = null;
457
+ this.halfOpenTestStartedAt = null;
458
+ logger.info(
459
+ { circuitId: this.options.id },
460
+ "\u{1F7E2} Circuit breaker CLOSED \u2014 recovery successful"
461
+ );
462
+ }
463
+ shouldTransitionToHalfOpen() {
464
+ if (this.lastFailureAt === null) {
465
+ return true;
466
+ }
467
+ const elapsed = Date.now() - this.lastFailureAt;
468
+ return elapsed >= this.options.halfOpenAfterMs;
469
+ }
470
+ };
471
+ var SHARED_BREAKERS = Object.freeze({
472
+ /**
473
+ * Breaker for adapter event emission.
474
+ * Threshold: 5 failures in 60s → open for 30s → half-open test.
475
+ */
476
+ adapterEmit: new CircuitBreaker({
477
+ id: "adapter.emit",
478
+ failureThreshold: 5,
479
+ halfOpenAfterMs: 3e4,
480
+ shouldCountAsFailure: isRetryableError,
481
+ windowMs: 6e4
482
+ }),
483
+ /**
484
+ * Breaker for file system operations (transcript reading, config loading).
485
+ * More tolerant: 10 failures in 60s → open for 30s.
486
+ */
487
+ fileSystem: new CircuitBreaker({
488
+ id: "filesystem",
489
+ failureThreshold: 10,
490
+ halfOpenAfterMs: 3e4,
491
+ windowMs: 6e4
492
+ }),
493
+ /**
494
+ * Breaker for HTTP/HTTPS requests.
495
+ * Stricter: 3 failures in 30s → open for 15s.
496
+ */
497
+ httpRequest: new CircuitBreaker({
498
+ id: "http-request",
499
+ failureThreshold: 3,
500
+ halfOpenAfterMs: 15e3,
501
+ windowMs: 3e4
502
+ }),
503
+ /**
504
+ * Breaker for process detection operations.
505
+ * Most tolerant: 20 failures in 60s → open for 10s.
506
+ */
507
+ processDetection: new CircuitBreaker({
508
+ id: "process-detection",
509
+ failureThreshold: 20,
510
+ halfOpenAfterMs: 1e4,
511
+ windowMs: 6e4
512
+ })
513
+ });
514
+
122
515
  // src/core/events/schema.ts
123
516
  import { validate as isUuid, v7 as uuidv7, version as uuidVersion } from "uuid";
124
517
  import { z } from "zod";
@@ -157,6 +550,8 @@ var TOOL_NAMES = [
157
550
  "kiro",
158
551
  "augment-code",
159
552
  "mistral",
553
+ "zed",
554
+ "pi",
160
555
  "unknown"
161
556
  ];
162
557
  var ERROR_TYPES = [
@@ -198,47 +593,58 @@ function createUuidV7() {
198
593
  return uuidv7();
199
594
  }
200
595
  var ToolInputSchema = z.strictObject({
201
- filePath: z.string().min(1).optional(),
202
- command: z.string().min(1).optional()
596
+ filePath: z.string().min(1).max(4096).optional(),
597
+ command: z.string().min(1).max(1e4).optional()
203
598
  }).refine(
204
599
  (value) => value.filePath !== void 0 || value.command !== void 0,
205
600
  "toolInput must include filePath or command"
206
601
  );
602
+ var ThinkingContentSchema = z.string().max(1e5).describe("Raw thinking/reasoning content from the AI model");
603
+ var ToolCallNameSchema = z.string().min(1).max(100).describe("Name of the tool being invoked (e.g., Edit, Bash, Grep)");
604
+ var FinalMessageSchema = z.string().max(5e4).describe("End-of-run summary or completion message");
605
+ var ToolResultSchema = z.string().max(1e4).describe("Tool execution result or output");
606
+ var MessageContentSchema = z.string().max(1e5).describe("Raw text content from AI messages");
207
607
  var ToolNameSchema = z.enum(TOOL_NAMES);
208
608
  var AISnitchEventTypeSchema = z.enum(AISNITCH_EVENT_TYPES);
209
609
  var ErrorTypeSchema = z.enum(ERROR_TYPES);
210
610
  var CESPCategorySchema = z.enum(CESP_CATEGORIES);
211
611
  var EventDataSchema = z.strictObject({
212
612
  state: AISnitchEventTypeSchema,
213
- project: z.string().min(1).optional(),
214
- projectPath: z.string().min(1).optional(),
613
+ project: z.string().min(1).max(255).optional(),
614
+ projectPath: z.string().min(1).max(4096).optional(),
215
615
  duration: z.number().int().min(0).optional(),
216
- toolName: z.string().min(1).optional(),
616
+ toolName: z.string().min(1).max(100).optional(),
217
617
  toolInput: ToolInputSchema.optional(),
218
- activeFile: z.string().min(1).optional(),
219
- model: z.string().min(1).optional(),
618
+ activeFile: z.string().min(1).max(4096).optional(),
619
+ model: z.string().min(1).max(200).optional(),
220
620
  tokensUsed: z.number().int().min(0).optional(),
221
- errorMessage: z.string().min(1).optional(),
621
+ errorMessage: z.string().min(1).max(1e4).optional(),
222
622
  errorType: ErrorTypeSchema.optional(),
223
623
  raw: z.record(z.string(), z.unknown()).optional(),
224
- terminal: z.string().min(1).optional(),
225
- cwd: z.string().min(1).optional(),
624
+ terminal: z.string().min(1).max(100).optional(),
625
+ cwd: z.string().min(1).max(4096).optional(),
226
626
  pid: z.number().int().positive().optional(),
227
- instanceId: z.string().min(1).optional(),
627
+ instanceId: z.string().min(1).max(255).optional(),
228
628
  instanceIndex: z.number().int().min(1).optional(),
229
- instanceTotal: z.number().int().min(1).optional()
629
+ instanceTotal: z.number().int().min(1).optional(),
630
+ // New fields for enhanced content capture
631
+ thinkingContent: ThinkingContentSchema.optional(),
632
+ toolCallName: ToolCallNameSchema.optional(),
633
+ finalMessage: FinalMessageSchema.optional(),
634
+ toolResult: ToolResultSchema.optional(),
635
+ messageContent: MessageContentSchema.optional()
230
636
  });
231
637
  var AISnitchEventSchema = z.strictObject({
232
638
  specversion: z.literal("1.0"),
233
639
  id: z.string().refine(isUuidV7, "id must be a valid UUIDv7 string"),
234
- source: z.string().refine(
640
+ source: z.string().max(2e3).refine(
235
641
  isValidUriReference,
236
642
  "source must be a valid non-empty CloudEvents URI-reference"
237
643
  ),
238
644
  type: AISnitchEventTypeSchema,
239
645
  time: ISO_TIMESTAMP_SCHEMA,
240
646
  "aisnitch.tool": ToolNameSchema,
241
- "aisnitch.sessionid": z.string().min(1),
647
+ "aisnitch.sessionid": z.string().min(1).max(500),
242
648
  "aisnitch.seqnum": z.number().int().min(1),
243
649
  data: EventDataSchema
244
650
  });
@@ -352,20 +758,29 @@ var BaseAdapter = class {
352
758
  });
353
759
  let published;
354
760
  try {
355
- published = await this.publishEventImplementation(event, {
356
- cwd: context.cwd,
357
- env: context.env,
358
- hookPayload: context.hookPayload,
359
- pid: context.pid,
360
- sessionId,
361
- source: context.source,
362
- transcriptPath: context.transcriptPath
761
+ published = await SHARED_BREAKERS.adapterEmit.execute(async () => {
762
+ return await this.publishEventImplementation(event, {
763
+ cwd: context.cwd,
764
+ env: context.env,
765
+ hookPayload: context.hookPayload,
766
+ pid: context.pid,
767
+ sessionId,
768
+ source: context.source,
769
+ transcriptPath: context.transcriptPath
770
+ });
363
771
  });
364
772
  } catch (error) {
365
- logger.error(
366
- { error, eventType: type, adapter: this.name, sessionId },
367
- "\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
368
- );
773
+ if (error instanceof Error && error.name === "CircuitOpenError") {
774
+ logger.warn(
775
+ { error, eventType: type, adapter: this.name },
776
+ "\u{1F4D6} Adapter emit blocked by open circuit \u2014 event dropped"
777
+ );
778
+ } else {
779
+ logger.error(
780
+ { error, eventType: type, adapter: this.name, sessionId },
781
+ "\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
782
+ );
783
+ }
369
784
  published = false;
370
785
  }
371
786
  if (published) {
@@ -1379,7 +1794,11 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
1379
1794
  return;
1380
1795
  }
1381
1796
  case "SessionEnd": {
1382
- await this.emitStateChange("session.end", sharedData, context);
1797
+ const finalMessage = extractFinalMessageFromPayload(payload);
1798
+ await this.emitStateChange("session.end", {
1799
+ ...sharedData,
1800
+ finalMessage
1801
+ }, context);
1383
1802
  return;
1384
1803
  }
1385
1804
  case "UserPromptSubmit":
@@ -1396,12 +1815,22 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
1396
1815
  return;
1397
1816
  }
1398
1817
  case "PreToolUse": {
1399
- await this.emitStateChange("agent.tool_call", sharedData, context);
1818
+ const toolCallName = extractToolNameFromPayload(payload);
1819
+ await this.emitStateChange("agent.tool_call", {
1820
+ ...sharedData,
1821
+ toolCallName
1822
+ }, context);
1400
1823
  return;
1401
1824
  }
1402
1825
  case "PostToolUse": {
1826
+ const toolCallName = extractToolNameFromPayload(payload);
1827
+ const toolResult = extractToolResultFromPayload(payload);
1403
1828
  const emittedType = isClaudeCodingTool(sharedData.toolName) ? "agent.coding" : "agent.tool_call";
1404
- await this.emitStateChange(emittedType, sharedData, context);
1829
+ await this.emitStateChange(emittedType, {
1830
+ ...sharedData,
1831
+ toolCallName,
1832
+ toolResult
1833
+ }, context);
1405
1834
  return;
1406
1835
  }
1407
1836
  case "PostToolUseFailure":
@@ -1603,21 +2032,50 @@ function extractClaudeTranscriptObservations(payload, transcriptPath) {
1603
2032
  };
1604
2033
  const observations = [];
1605
2034
  if (contentParts.some((part) => part.type === "thinking")) {
2035
+ const thinkingParts = contentParts.filter((part) => part.type === "thinking");
2036
+ const thinkingText = thinkingParts.map((part) => {
2037
+ const text = part.text;
2038
+ return typeof text === "string" ? text : void 0;
2039
+ }).filter((text) => text !== void 0).join("\n");
1606
2040
  observations.push({
1607
2041
  context: sharedContext,
1608
- data: sharedData,
2042
+ data: {
2043
+ ...sharedData,
2044
+ thinkingContent: thinkingText.length > 0 ? thinkingText : void 0
2045
+ },
1609
2046
  type: "agent.thinking"
1610
2047
  });
1611
2048
  }
1612
2049
  if (contentParts.some(
1613
2050
  (part) => part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0
1614
2051
  )) {
2052
+ const messageTexts = contentParts.filter((part) => part.type === "text").map((part) => part.text).filter((text) => text.trim().length > 0);
2053
+ const messageContent = messageTexts.join("\n");
1615
2054
  observations.push({
1616
2055
  context: sharedContext,
1617
- data: sharedData,
2056
+ data: {
2057
+ ...sharedData,
2058
+ messageContent: messageContent.length > 0 ? messageContent : void 0
2059
+ },
1618
2060
  type: "agent.streaming"
1619
2061
  });
1620
2062
  }
2063
+ const toolUseParts = contentParts.filter(
2064
+ (part) => part.type === "tool_use" || part.type === "toolUse"
2065
+ );
2066
+ if (toolUseParts.length > 0) {
2067
+ const toolName = getString(toolUseParts[0], "name") ?? getString(toolUseParts[0], "tool");
2068
+ if (toolName) {
2069
+ observations.push({
2070
+ context: sharedContext,
2071
+ data: {
2072
+ ...sharedData,
2073
+ toolCallName: toolName
2074
+ },
2075
+ type: "agent.tool_call"
2076
+ });
2077
+ }
2078
+ }
1621
2079
  return observations;
1622
2080
  }
1623
2081
  function extractClaudeContentParts(payload) {
@@ -1740,6 +2198,51 @@ function getString(payload, key) {
1740
2198
  const value = payload[key];
1741
2199
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1742
2200
  }
2201
+ function extractToolNameFromPayload(payload) {
2202
+ const directToolName = getString(payload, "tool_name") ?? getString(payload, "toolName");
2203
+ if (directToolName) {
2204
+ return directToolName;
2205
+ }
2206
+ const toolUse = getRecord(payload.tool_use) ?? getRecord(payload.toolUse);
2207
+ if (toolUse) {
2208
+ return getString(toolUse, "name") ?? getString(toolUse, "tool");
2209
+ }
2210
+ const toolInput = getRecord(payload.tool_input) ?? getRecord(payload.toolInput);
2211
+ if (toolInput) {
2212
+ return getString(toolInput, "tool_name") ?? getString(toolInput, "type");
2213
+ }
2214
+ return void 0;
2215
+ }
2216
+ function extractToolResultFromPayload(payload) {
2217
+ const directResult = getString(payload, "result") ?? getString(payload, "output");
2218
+ if (directResult) {
2219
+ return directResult;
2220
+ }
2221
+ const toolResult = getRecord(payload.tool_result) ?? getRecord(payload.toolResult);
2222
+ if (toolResult) {
2223
+ return getString(toolResult, "content") ?? getString(toolResult, "output");
2224
+ }
2225
+ const errorField = getString(payload, "error") ?? getString(payload, "error_message");
2226
+ if (errorField) {
2227
+ return errorField;
2228
+ }
2229
+ return void 0;
2230
+ }
2231
+ function extractFinalMessageFromPayload(payload) {
2232
+ const directMessage = getString(payload, "final_message") ?? getString(payload, "finalMessage") ?? getString(payload, "summary") ?? getString(payload, "completion_message");
2233
+ if (directMessage) {
2234
+ return directMessage;
2235
+ }
2236
+ const result = getString(payload, "result") ?? getString(payload, "output") ?? getString(payload, "message");
2237
+ if (result) {
2238
+ return result;
2239
+ }
2240
+ const stats = getRecord(payload.stats);
2241
+ if (stats) {
2242
+ return getString(stats, "summary") ?? getString(stats, "completion_summary");
2243
+ }
2244
+ return void 0;
2245
+ }
1743
2246
 
1744
2247
  // src/adapters/copilot-cli.ts
1745
2248
  import { execFile as execFileCallback3 } from "child_process";
@@ -6713,7 +7216,11 @@ var OpenCodeAdapter = class extends BaseAdapter {
6713
7216
  return;
6714
7217
  }
6715
7218
  case "session.deleted": {
6716
- await this.emitStateChange("session.end", sharedData, context);
7219
+ const finalMessage = extractOpenCodeFinalMessage(payload);
7220
+ await this.emitStateChange("session.end", {
7221
+ ...sharedData,
7222
+ finalMessage
7223
+ }, context);
6717
7224
  return;
6718
7225
  }
6719
7226
  case "session.error": {
@@ -6738,12 +7245,22 @@ var OpenCodeAdapter = class extends BaseAdapter {
6738
7245
  return;
6739
7246
  }
6740
7247
  case "tool.execute.before": {
6741
- await this.emitStateChange("agent.tool_call", sharedData, context);
7248
+ const toolCallName = extractOpenCodeToolName(payload);
7249
+ await this.emitStateChange("agent.tool_call", {
7250
+ ...sharedData,
7251
+ toolCallName
7252
+ }, context);
6742
7253
  return;
6743
7254
  }
6744
7255
  case "tool.execute.after": {
7256
+ const toolCallName = extractOpenCodeToolName(payload);
7257
+ const toolResult = extractOpenCodeToolResult(payload);
6745
7258
  const emittedType = isOpenCodeCodingTool(sharedData.toolName) ? "agent.coding" : "agent.tool_call";
6746
- await this.emitStateChange(emittedType, sharedData, context);
7259
+ await this.emitStateChange(emittedType, {
7260
+ ...sharedData,
7261
+ toolCallName,
7262
+ toolResult
7263
+ }, context);
6747
7264
  return;
6748
7265
  }
6749
7266
  default: {
@@ -6910,68 +7427,597 @@ function getString10(payload, key) {
6910
7427
  const value = payload[key];
6911
7428
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
6912
7429
  }
6913
-
6914
- // src/adapters/registry.ts
6915
- var AdapterRegistry = class {
6916
- adapters = /* @__PURE__ */ new Map();
6917
- /**
6918
- * Registers one built-in or community adapter instance.
6919
- */
6920
- register(adapter) {
6921
- if (this.adapters.has(adapter.name)) {
6922
- throw new Error(`Adapter "${adapter.name}" is already registered.`);
6923
- }
6924
- this.adapters.set(adapter.name, adapter);
7430
+ function extractOpenCodeFinalMessage(payload) {
7431
+ const directMessage = getString10(payload, "final_message") ?? getString10(payload, "finalMessage") ?? getString10(payload, "summary") ?? getString10(payload, "completion_message");
7432
+ if (directMessage) {
7433
+ return directMessage;
6925
7434
  }
6926
- /**
6927
- * Returns one adapter instance by its tool name.
6928
- */
6929
- get(toolName) {
6930
- return this.adapters.get(toolName);
7435
+ const result = getString10(payload, "result") ?? getString10(payload, "output") ?? getString10(getRecord9(payload.properties), "result");
7436
+ if (result) {
7437
+ return result;
6931
7438
  }
6932
- /**
6933
- * Lists every registered adapter.
6934
- */
6935
- list() {
6936
- return [...this.adapters.values()];
7439
+ return void 0;
7440
+ }
7441
+ function extractOpenCodeToolResult(payload) {
7442
+ const directResult = getString10(payload, "result") ?? getString10(payload, "output") ?? getString10(payload, "toolResult");
7443
+ if (directResult) {
7444
+ return directResult;
6937
7445
  }
6938
- /**
6939
- * Returns one status snapshot per registered adapter.
6940
- */
6941
- getStatus() {
6942
- return this.list().map((adapter) => adapter.getStatus());
7446
+ const toolResult = getRecord9(payload.tool_result) ?? getRecord9(payload.toolResult);
7447
+ if (toolResult) {
7448
+ return getString10(toolResult, "content") ?? getString10(toolResult, "output");
6943
7449
  }
6944
- /**
6945
- * Starts every adapter enabled in the current AISnitch config.
6946
- * 📖 Each adapter is started independently — one failure does not prevent
6947
- * the others from starting.
6948
- */
6949
- async startAll(config) {
6950
- for (const adapter of this.list()) {
6951
- if (config.adapters[adapter.name]?.enabled !== true) {
6952
- continue;
6953
- }
6954
- try {
6955
- await adapter.start();
6956
- } catch (error) {
6957
- logger.error(
6958
- { error, adapter: adapter.name },
6959
- `\u{1F4D6} Failed to start adapter "${adapter.name}" \u2014 skipping`
6960
- );
6961
- }
7450
+ const props = getRecord9(payload.properties);
7451
+ if (props) {
7452
+ const nestedResult = getRecord9(props.tool_result) ?? getRecord9(props.toolResult);
7453
+ if (nestedResult) {
7454
+ return getString10(nestedResult, "content") ?? getString10(nestedResult, "output");
6962
7455
  }
6963
7456
  }
6964
- /**
6965
- * Stops every adapter in reverse registration order.
6966
- * 📖 Each adapter is stopped independently — one failure does not prevent
6967
- * the others from being stopped.
6968
- */
6969
- async stopAll() {
6970
- const adapters = this.list().reverse();
6971
- for (const adapter of adapters) {
6972
- try {
6973
- await adapter.stop();
6974
- } catch (error) {
7457
+ return void 0;
7458
+ }
7459
+
7460
+ // src/adapters/pi.ts
7461
+ import { execFile as execFile12 } from "child_process";
7462
+ import { join as join11 } from "path";
7463
+ import { promisify as promisify12 } from "util";
7464
+ var execFileAsync = promisify12(execFile12);
7465
+ var PiAdapter = class extends BaseAdapter {
7466
+ displayName = "Pi (MiniMax)";
7467
+ name = "pi";
7468
+ strategies = [
7469
+ "process-detect",
7470
+ "api-client",
7471
+ "log-watch"
7472
+ ];
7473
+ apiPort = 7890;
7474
+ logPath;
7475
+ poller = null;
7476
+ activePiSessions = /* @__PURE__ */ new Map();
7477
+ lastCheckedTime = 0;
7478
+ constructor(options) {
7479
+ super(options);
7480
+ this.logPath = join11(
7481
+ options.homeDirectory ?? process.env.HOME ?? "",
7482
+ ".pi",
7483
+ "agent.log"
7484
+ );
7485
+ }
7486
+ start() {
7487
+ if (this.getStatus().running) {
7488
+ return Promise.resolve();
7489
+ }
7490
+ this.setRunning(true);
7491
+ this.startPolling();
7492
+ logger.info({ adapter: this.name }, "Pi adapter started");
7493
+ return Promise.resolve();
7494
+ }
7495
+ stop() {
7496
+ if (this.poller !== null) {
7497
+ clearInterval(this.poller);
7498
+ this.poller = null;
7499
+ }
7500
+ this.setRunning(false);
7501
+ logger.info({ adapter: this.name }, "Pi adapter stopped");
7502
+ return Promise.resolve();
7503
+ }
7504
+ async handleHook(payload) {
7505
+ const normalized = this.parseNormalizedHookPayload(payload);
7506
+ if (normalized === null) {
7507
+ return;
7508
+ }
7509
+ const context = {
7510
+ cwd: normalized.cwd,
7511
+ pid: normalized.pid,
7512
+ sessionId: normalized.sessionId,
7513
+ source: "pi-hook"
7514
+ };
7515
+ const eventType = this.mapEventType(normalized.type ?? "");
7516
+ const eventData = this.buildEventData(eventType, normalized);
7517
+ await this.emit(eventType, eventData, context);
7518
+ }
7519
+ startPolling() {
7520
+ this.poller = setInterval(() => {
7521
+ void this.pollPiActivity();
7522
+ }, 2e3);
7523
+ }
7524
+ async pollPiActivity() {
7525
+ const running = await this.detectPiInstance();
7526
+ if (!running) {
7527
+ for (const [sessionId, activity] of this.activePiSessions) {
7528
+ if (activity.state !== "idle") {
7529
+ activity.state = "idle";
7530
+ await this.emitIdle(sessionId);
7531
+ }
7532
+ }
7533
+ return;
7534
+ }
7535
+ try {
7536
+ const response = await fetch(
7537
+ `http://127.0.0.1:${this.apiPort}/api/status`,
7538
+ {
7539
+ signal: AbortSignal.timeout(500)
7540
+ }
7541
+ );
7542
+ if (response.ok) {
7543
+ const data = await response.json();
7544
+ await this.processPiApiResponse(data);
7545
+ }
7546
+ } catch {
7547
+ await this.checkMiniMaxApi();
7548
+ }
7549
+ }
7550
+ async detectPiInstance() {
7551
+ try {
7552
+ const result = await execFileAsync("pgrep", ["-l", "pi|minimax"]);
7553
+ if (result.stdout.includes("pi") || result.stdout.includes("minimax")) {
7554
+ return true;
7555
+ }
7556
+ } catch {
7557
+ }
7558
+ try {
7559
+ const response = await fetch(
7560
+ `http://127.0.0.1:${this.apiPort}/health`,
7561
+ {
7562
+ signal: AbortSignal.timeout(200)
7563
+ }
7564
+ );
7565
+ if (response.ok) {
7566
+ return true;
7567
+ }
7568
+ } catch {
7569
+ }
7570
+ return false;
7571
+ }
7572
+ async checkMiniMaxApi() {
7573
+ try {
7574
+ const response = await fetch("http://127.0.0.1:3000/api/agent/status", {
7575
+ signal: AbortSignal.timeout(500)
7576
+ });
7577
+ if (response.ok) {
7578
+ const data = await response.json();
7579
+ await this.processPiApiResponse(data);
7580
+ }
7581
+ } catch {
7582
+ }
7583
+ }
7584
+ async processPiApiResponse(data) {
7585
+ const rawSession = data.sessionId ?? data.project ?? "default";
7586
+ const sessionId = `pi:${rawSession.replace(/[^a-zA-Z0-9-_]/g, "-")}`;
7587
+ let activity = this.activePiSessions.get(sessionId);
7588
+ if (!activity) {
7589
+ activity = { sessionId, state: "idle" };
7590
+ this.activePiSessions.set(sessionId, activity);
7591
+ await this.emitSessionStart(sessionId, data);
7592
+ }
7593
+ const rawState = data.state ?? "idle";
7594
+ const state = rawState;
7595
+ if (state !== activity.state) {
7596
+ switch (state) {
7597
+ case "thinking": {
7598
+ const rawThinking = data.thinking;
7599
+ if (rawThinking) {
7600
+ await this.emitThinking(sessionId, rawThinking);
7601
+ }
7602
+ break;
7603
+ }
7604
+ case "tool": {
7605
+ const rawFilePath = data.filePath;
7606
+ const rawCommand = data.command;
7607
+ const rawToolName = data.toolName ?? "unknown";
7608
+ await this.emitToolCall(
7609
+ sessionId,
7610
+ {
7611
+ filePath: rawFilePath ?? "",
7612
+ command: rawCommand ?? ""
7613
+ },
7614
+ rawToolName
7615
+ );
7616
+ break;
7617
+ }
7618
+ case "output": {
7619
+ const rawOutput = data.output;
7620
+ if (rawOutput) {
7621
+ await this.emitOutput(sessionId, rawOutput);
7622
+ }
7623
+ break;
7624
+ }
7625
+ case "error": {
7626
+ const rawError = data.error ?? "Unknown error";
7627
+ await this.emitError(sessionId, rawError);
7628
+ break;
7629
+ }
7630
+ case "idle":
7631
+ await this.emitIdle(sessionId);
7632
+ break;
7633
+ }
7634
+ activity.state = state;
7635
+ }
7636
+ }
7637
+ async emitSessionStart(sessionId, data) {
7638
+ const rawProject = data.project ?? "pi-project";
7639
+ const rawModel = data.model ?? "minimax/moonshot";
7640
+ const eventData = {
7641
+ state: "session.start",
7642
+ project: rawProject,
7643
+ model: rawModel,
7644
+ raw: data
7645
+ };
7646
+ await this.emit("session.start", eventData, { sessionId });
7647
+ }
7648
+ async emitThinking(sessionId, content) {
7649
+ if (!content) return;
7650
+ const eventData = {
7651
+ state: "agent.thinking",
7652
+ thinkingContent: content
7653
+ };
7654
+ await this.emit("agent.thinking", eventData, { sessionId });
7655
+ }
7656
+ async emitToolCall(sessionId, toolInput, toolName) {
7657
+ const eventData = {
7658
+ state: "agent.tool_call",
7659
+ toolCallName: toolName,
7660
+ toolInput,
7661
+ activeFile: toolInput.filePath
7662
+ };
7663
+ await this.emit("agent.tool_call", eventData, { sessionId });
7664
+ }
7665
+ async emitOutput(sessionId, content) {
7666
+ if (!content) return;
7667
+ const eventData = {
7668
+ state: "agent.streaming",
7669
+ messageContent: content
7670
+ };
7671
+ await this.emit("agent.streaming", eventData, { sessionId });
7672
+ }
7673
+ async emitError(sessionId, errorMessage) {
7674
+ const eventData = {
7675
+ state: "agent.error",
7676
+ errorMessage,
7677
+ errorType: "api_error"
7678
+ };
7679
+ await this.emit("agent.error", eventData, { sessionId });
7680
+ }
7681
+ async emitIdle(sessionId) {
7682
+ const eventData = {
7683
+ state: "agent.idle"
7684
+ };
7685
+ await this.emit("agent.idle", eventData, { sessionId });
7686
+ }
7687
+ mapEventType(type) {
7688
+ const mapping = {
7689
+ "session.start": "session.start",
7690
+ "session.end": "session.end",
7691
+ "task.start": "task.start",
7692
+ "task.complete": "task.complete",
7693
+ thinking: "agent.thinking",
7694
+ tool: "agent.tool_call",
7695
+ coding: "agent.coding",
7696
+ output: "agent.streaming",
7697
+ message: "agent.streaming",
7698
+ ask: "agent.asking_user",
7699
+ error: "agent.error",
7700
+ idle: "agent.idle",
7701
+ compact: "agent.compact"
7702
+ };
7703
+ return mapping[type] ?? "agent.streaming";
7704
+ }
7705
+ buildEventData(eventType, payload) {
7706
+ const data = payload.data ?? {};
7707
+ return {
7708
+ state: eventType,
7709
+ project: data.project,
7710
+ activeFile: data.activeFile,
7711
+ model: data.model,
7712
+ toolInput: data.toolInput,
7713
+ toolCallName: data.toolCallName,
7714
+ thinkingContent: data.thinkingContent,
7715
+ messageContent: data.messageContent,
7716
+ finalMessage: data.finalMessage,
7717
+ toolResult: data.toolResult,
7718
+ errorMessage: data.errorMessage,
7719
+ errorType: data.errorType,
7720
+ raw: data.raw
7721
+ };
7722
+ }
7723
+ };
7724
+
7725
+ // src/adapters/zed.ts
7726
+ import { readFile as readFile10 } from "fs/promises";
7727
+ import { basename as basename9, join as join12 } from "path";
7728
+ var ZedAdapter = class extends BaseAdapter {
7729
+ displayName = "Zed AI";
7730
+ name = "zed";
7731
+ strategies = [
7732
+ "process-detect",
7733
+ "api-client"
7734
+ ];
7735
+ logPaths;
7736
+ apiPort = 9876;
7737
+ pollIntervalMs;
7738
+ poller = null;
7739
+ lastEventTime = 0;
7740
+ activeZedSessions = /* @__PURE__ */ new Map();
7741
+ constructor(options) {
7742
+ super(options);
7743
+ this.pollIntervalMs = 2e3;
7744
+ this.logPaths = [
7745
+ join12(options.homeDirectory ?? process.env.HOME ?? "", ".config", "zed", "logs", "agent.log"),
7746
+ "/tmp/zed-agent.log"
7747
+ ];
7748
+ }
7749
+ start() {
7750
+ if (this.getStatus().running) {
7751
+ return Promise.resolve();
7752
+ }
7753
+ this.setRunning(true);
7754
+ this.startPolling();
7755
+ logger.info({ adapter: this.name }, "Zed adapter started");
7756
+ return Promise.resolve();
7757
+ }
7758
+ stop() {
7759
+ if (this.poller !== null) {
7760
+ clearInterval(this.poller);
7761
+ this.poller = null;
7762
+ }
7763
+ this.setRunning(false);
7764
+ logger.info({ adapter: this.name }, "Zed adapter stopped");
7765
+ return Promise.resolve();
7766
+ }
7767
+ async handleHook(payload) {
7768
+ const normalized = this.parseNormalizedHookPayload(payload);
7769
+ if (normalized === null) {
7770
+ return;
7771
+ }
7772
+ const context = {
7773
+ cwd: normalized.cwd,
7774
+ pid: normalized.pid,
7775
+ sessionId: normalized.sessionId,
7776
+ source: "zed-hook"
7777
+ };
7778
+ const eventType = this.mapEventType(normalized.type ?? "");
7779
+ const eventData = this.buildEventData(eventType, normalized);
7780
+ await this.emit(eventType, eventData, context);
7781
+ }
7782
+ startPolling() {
7783
+ this.poller = setInterval(() => {
7784
+ void this.pollZedStatus();
7785
+ }, this.pollIntervalMs);
7786
+ }
7787
+ async pollZedStatus() {
7788
+ try {
7789
+ const response = await fetch(`http://127.0.0.1:${this.apiPort}/api/agent/status`, {
7790
+ signal: AbortSignal.timeout(1e3)
7791
+ });
7792
+ if (response.ok) {
7793
+ const data = await response.json();
7794
+ if (data.sessionId && typeof data.sessionId === "string") {
7795
+ const sessionId = `zed:${data.sessionId}`;
7796
+ if (!this.activeZedSessions.has(sessionId)) {
7797
+ this.activeZedSessions.set(sessionId, sessionId);
7798
+ await this.emitSessionStart(sessionId, data);
7799
+ }
7800
+ if (data.state === "thinking" && this.lastEventTime < Date.now() - 5e3) {
7801
+ const rawThinking = data.thinking;
7802
+ if (rawThinking) {
7803
+ await this.emitThinking(sessionId, rawThinking);
7804
+ }
7805
+ } else if (data.state === "tool" && data.toolName) {
7806
+ const rawFilePath = data.filePath;
7807
+ const rawCommand = data.command;
7808
+ const rawToolName = data.toolName ?? "unknown";
7809
+ await this.emitToolCall(
7810
+ sessionId,
7811
+ {
7812
+ filePath: rawFilePath ?? "",
7813
+ command: rawCommand ?? ""
7814
+ },
7815
+ rawToolName
7816
+ );
7817
+ } else if (data.state === "idle") {
7818
+ await this.emitIdle(sessionId);
7819
+ }
7820
+ }
7821
+ }
7822
+ } catch {
7823
+ await this.checkLogFiles();
7824
+ }
7825
+ }
7826
+ async checkLogFiles() {
7827
+ for (const logPath of this.logPaths) {
7828
+ try {
7829
+ const content = await readFile10(logPath, "utf8");
7830
+ await this.parseLogContent(content);
7831
+ } catch {
7832
+ }
7833
+ }
7834
+ }
7835
+ async parseLogContent(content) {
7836
+ const lines = content.split("\n").filter((line) => line.trim());
7837
+ for (const line of lines) {
7838
+ if (this.lastEventTime > 0 && this.lastEventTime >= Date.now() - 2e3) {
7839
+ continue;
7840
+ }
7841
+ const event = this.extractZedEventFromLog(line);
7842
+ if (event) {
7843
+ await this.handleHook(event);
7844
+ this.lastEventTime = Date.now();
7845
+ }
7846
+ }
7847
+ }
7848
+ extractZedEventFromLog(line) {
7849
+ try {
7850
+ const parsed = JSON.parse(line);
7851
+ if (!parsed.type || typeof parsed.type !== "string") {
7852
+ return null;
7853
+ }
7854
+ const rawSessionId = parsed.sessionId;
7855
+ const rawWorkspace = parsed.workspace;
7856
+ const sessionId = rawSessionId ? rawSessionId : rawWorkspace ? `zed:${basename9(rawWorkspace)}` : `zed:${Date.now()}`;
7857
+ const rawCwd = parsed.cwd ?? rawWorkspace;
7858
+ return {
7859
+ type: parsed.type,
7860
+ sessionId,
7861
+ cwd: rawCwd,
7862
+ data: {
7863
+ project: parsed.project ?? rawWorkspace ? basename9(String(rawWorkspace)) : void 0,
7864
+ model: parsed.model,
7865
+ state: parsed.type,
7866
+ thinkingContent: parsed.thinking,
7867
+ toolCallName: parsed.toolName,
7868
+ toolInput: parsed.toolInput,
7869
+ messageContent: parsed.message ?? parsed.output,
7870
+ errorMessage: parsed.error,
7871
+ raw: parsed
7872
+ }
7873
+ };
7874
+ } catch {
7875
+ if (line.includes("[zed:agent]")) {
7876
+ const cleaned = line.replace(/^\[.*?\] \[.*?\] /, "");
7877
+ if (cleaned.includes("Thinking:")) {
7878
+ return { type: "thinking", thinkingContent: cleaned.replace("Thinking:", "").trim() };
7879
+ }
7880
+ if (cleaned.includes("Executing tool:")) {
7881
+ return { type: "tool", toolCallName: cleaned.replace("Executing tool:", "").trim() };
7882
+ }
7883
+ if (cleaned.includes("Error:")) {
7884
+ return { type: "error", errorMessage: cleaned.replace("Error:", "").trim() };
7885
+ }
7886
+ }
7887
+ return null;
7888
+ }
7889
+ }
7890
+ async emitSessionStart(sessionId, _data) {
7891
+ const eventData = {
7892
+ state: "session.start",
7893
+ project: sessionId.split(":")[1] ?? "unknown"
7894
+ };
7895
+ await this.emit("session.start", eventData, { sessionId });
7896
+ }
7897
+ async emitThinking(sessionId, content) {
7898
+ if (!content) return;
7899
+ const eventData = {
7900
+ state: "agent.thinking",
7901
+ thinkingContent: content
7902
+ };
7903
+ await this.emit("agent.thinking", eventData, { sessionId });
7904
+ this.lastEventTime = Date.now();
7905
+ }
7906
+ async emitToolCall(sessionId, toolInput, toolName) {
7907
+ const eventData = {
7908
+ state: "agent.tool_call",
7909
+ toolCallName: toolName,
7910
+ toolInput,
7911
+ activeFile: toolInput.filePath
7912
+ };
7913
+ await this.emit("agent.tool_call", eventData, { sessionId });
7914
+ this.lastEventTime = Date.now();
7915
+ }
7916
+ async emitIdle(sessionId) {
7917
+ const eventData = {
7918
+ state: "agent.idle"
7919
+ };
7920
+ await this.emit("agent.idle", eventData, { sessionId });
7921
+ }
7922
+ mapEventType(type) {
7923
+ const mapping = {
7924
+ "session.start": "session.start",
7925
+ "session.end": "session.end",
7926
+ "task.start": "task.start",
7927
+ "task.complete": "task.complete",
7928
+ thinking: "agent.thinking",
7929
+ tool: "agent.tool_call",
7930
+ coding: "agent.coding",
7931
+ output: "agent.streaming",
7932
+ message: "agent.streaming",
7933
+ ask: "agent.asking_user",
7934
+ error: "agent.error",
7935
+ idle: "agent.idle",
7936
+ compact: "agent.compact"
7937
+ };
7938
+ return mapping[type] ?? "agent.streaming";
7939
+ }
7940
+ buildEventData(eventType, payload) {
7941
+ const data = payload.data ?? {};
7942
+ return {
7943
+ state: eventType,
7944
+ project: data.project,
7945
+ activeFile: data.activeFile,
7946
+ model: data.model,
7947
+ toolInput: data.toolInput,
7948
+ toolCallName: data.toolCallName,
7949
+ thinkingContent: data.thinkingContent,
7950
+ messageContent: data.messageContent,
7951
+ finalMessage: data.finalMessage,
7952
+ toolResult: data.toolResult,
7953
+ errorMessage: data.errorMessage,
7954
+ errorType: data.errorType,
7955
+ raw: data.raw
7956
+ };
7957
+ }
7958
+ };
7959
+
7960
+ // src/adapters/registry.ts
7961
+ var AdapterRegistry = class {
7962
+ adapters = /* @__PURE__ */ new Map();
7963
+ /**
7964
+ * Registers one built-in or community adapter instance.
7965
+ */
7966
+ register(adapter) {
7967
+ if (this.adapters.has(adapter.name)) {
7968
+ throw new Error(`Adapter "${adapter.name}" is already registered.`);
7969
+ }
7970
+ this.adapters.set(adapter.name, adapter);
7971
+ }
7972
+ /**
7973
+ * Returns one adapter instance by its tool name.
7974
+ */
7975
+ get(toolName) {
7976
+ return this.adapters.get(toolName);
7977
+ }
7978
+ /**
7979
+ * Lists every registered adapter.
7980
+ */
7981
+ list() {
7982
+ return [...this.adapters.values()];
7983
+ }
7984
+ /**
7985
+ * Returns one status snapshot per registered adapter.
7986
+ */
7987
+ getStatus() {
7988
+ return this.list().map((adapter) => adapter.getStatus());
7989
+ }
7990
+ /**
7991
+ * Starts every adapter enabled in the current AISnitch config.
7992
+ * 📖 Each adapter is started independently — one failure does not prevent
7993
+ * the others from starting.
7994
+ */
7995
+ async startAll(config) {
7996
+ for (const adapter of this.list()) {
7997
+ if (config.adapters[adapter.name]?.enabled !== true) {
7998
+ continue;
7999
+ }
8000
+ try {
8001
+ await adapter.start();
8002
+ } catch (error) {
8003
+ logger.error(
8004
+ { error, adapter: adapter.name },
8005
+ `\u{1F4D6} Failed to start adapter "${adapter.name}" \u2014 skipping`
8006
+ );
8007
+ }
8008
+ }
8009
+ }
8010
+ /**
8011
+ * Stops every adapter in reverse registration order.
8012
+ * 📖 Each adapter is stopped independently — one failure does not prevent
8013
+ * the others from being stopped.
8014
+ */
8015
+ async stopAll() {
8016
+ const adapters = this.list().reverse();
8017
+ for (const adapter of adapters) {
8018
+ try {
8019
+ await adapter.stop();
8020
+ } catch (error) {
6975
8021
  logger.warn(
6976
8022
  { error, adapter: adapter.name },
6977
8023
  `\u{1F4D6} Error stopping adapter "${adapter.name}" \u2014 continuing`
@@ -6982,16 +8028,16 @@ var AdapterRegistry = class {
6982
8028
  };
6983
8029
 
6984
8030
  // src/adapters/generic-pty.ts
6985
- import { basename as basename10 } from "path";
8031
+ import { basename as basename11 } from "path";
6986
8032
  import { spawn as spawnPty } from "@lydell/node-pty";
6987
8033
  import stripAnsi from "strip-ansi";
6988
8034
 
6989
8035
  // src/core/engine/context-detector.ts
6990
8036
  import { execFile as execFileCallback12 } from "child_process";
6991
- import { basename as basename9 } from "path";
6992
- import { promisify as promisify12 } from "util";
8037
+ import { basename as basename10 } from "path";
8038
+ import { promisify as promisify13 } from "util";
6993
8039
  import pidCwd3 from "pid-cwd";
6994
- var execFile12 = promisify12(execFileCallback12);
8040
+ var execFile13 = promisify13(execFileCallback12);
6995
8041
  var TERM_PROGRAM_MAP = {
6996
8042
  Apple_Terminal: "Terminal.app",
6997
8043
  Hyper: "Hyper",
@@ -7034,9 +8080,11 @@ var TOOL_BINARY_MAP = {
7034
8080
  "openhands": "openhands",
7035
8081
  "openclaw": "openclaw",
7036
8082
  "opencode": "opencode",
8083
+ "pi": "pi",
7037
8084
  "qwen-code": "qwen",
7038
8085
  "unknown": "unknown",
7039
- "windsurf": "windsurf"
8086
+ "windsurf": "windsurf",
8087
+ "zed": "zed"
7040
8088
  };
7041
8089
  var ContextDetector = class {
7042
8090
  cache = /* @__PURE__ */ new Map();
@@ -7102,7 +8150,7 @@ var ContextDetector = class {
7102
8150
  return "unknown";
7103
8151
  }
7104
8152
  const nextPid = Number.parseInt(parentPidToken, 10);
7105
- const normalizedProcessName = basename9(commandText).replace(/\.app$/u, "");
8153
+ const normalizedProcessName = basename10(commandText).replace(/\.app$/u, "");
7106
8154
  const mappedTerminal = PROCESS_NAME_MAP[normalizedProcessName] ?? PROCESS_NAME_MAP[commandText.trim()];
7107
8155
  if (mappedTerminal) {
7108
8156
  return mappedTerminal;
@@ -7286,7 +8334,7 @@ var ContextDetector = class {
7286
8334
  return cwd ?? void 0;
7287
8335
  }
7288
8336
  async defaultExecCommand(command, args, options = {}) {
7289
- const result = await execFile12(command, [...args], {
8337
+ const result = await execFile13(command, [...args], {
7290
8338
  encoding: "utf8",
7291
8339
  timeout: options.timeoutMs ?? this.commandTimeoutMs,
7292
8340
  maxBuffer: 1024 * 1024
@@ -7366,7 +8414,7 @@ var GenericPTYSession = class {
7366
8414
  {
7367
8415
  cwd: this.cwd,
7368
8416
  pid: pty.pid,
7369
- project: basename10(this.cwd) || this.cwd,
8417
+ project: basename11(this.cwd) || this.cwd,
7370
8418
  projectPath: this.cwd,
7371
8419
  raw: {
7372
8420
  args: this.args,
@@ -7377,7 +8425,7 @@ var GenericPTYSession = class {
7377
8425
  toolInput: {
7378
8426
  command: this.commandLine
7379
8427
  },
7380
- toolName: basename10(this.command) || this.command
8428
+ toolName: basename11(this.command) || this.command
7381
8429
  }
7382
8430
  );
7383
8431
  await this.emitEvent(
@@ -7387,7 +8435,7 @@ var GenericPTYSession = class {
7387
8435
  {
7388
8436
  cwd: this.cwd,
7389
8437
  pid: pty.pid,
7390
- project: basename10(this.cwd) || this.cwd,
8438
+ project: basename11(this.cwd) || this.cwd,
7391
8439
  projectPath: this.cwd,
7392
8440
  raw: {
7393
8441
  args: this.args,
@@ -7537,7 +8585,7 @@ var GenericPTYSession = class {
7537
8585
  await this.emitEvent(pty.pid, sessionId, observation.type, {
7538
8586
  cwd: this.cwd,
7539
8587
  pid: pty.pid,
7540
- project: basename10(this.cwd) || this.cwd,
8588
+ project: basename11(this.cwd) || this.cwd,
7541
8589
  projectPath: this.cwd,
7542
8590
  terminal,
7543
8591
  ...observation.data
@@ -7647,7 +8695,7 @@ function analyzeTerminalOutputChunk(input) {
7647
8695
  } : {
7648
8696
  command: input.commandLine
7649
8697
  },
7650
- toolName: activeFile ? "file-edit" : basename10(input.commandLine) || "shell"
8698
+ toolName: activeFile ? "file-edit" : basename11(input.commandLine) || "shell"
7651
8699
  },
7652
8700
  fingerprint: createPtyFingerprint("agent.coding", normalizedText, activeFile),
7653
8701
  type: "agent.coding"
@@ -7677,7 +8725,7 @@ function normalizePtyEnvironment(env) {
7677
8725
  );
7678
8726
  }
7679
8727
  function inferWrappedToolName(command, args) {
7680
- const commandBaseName = basename10(command).toLowerCase();
8728
+ const commandBaseName = basename11(command).toLowerCase();
7681
8729
  const fullCommandLine = [commandBaseName, ...args].join(" ").toLowerCase();
7682
8730
  const toolMatchers = [
7683
8731
  ["aider", /\baider\b/u],
@@ -7763,7 +8811,9 @@ function createDefaultAdapters(options) {
7763
8811
  new KiloAdapter(options),
7764
8812
  new CodexAdapter(options),
7765
8813
  new OpenClawAdapter(options),
7766
- new OpenCodeAdapter(options)
8814
+ new OpenCodeAdapter(options),
8815
+ new PiAdapter(options),
8816
+ new ZedAdapter(options)
7767
8817
  ];
7768
8818
  }
7769
8819
 
@@ -7811,10 +8861,10 @@ var DEFAULT_CONFIG = {
7811
8861
  };
7812
8862
 
7813
8863
  // src/core/config/loader.ts
7814
- import { mkdir, readFile as readFile10, writeFile } from "fs/promises";
8864
+ import { mkdir, readFile as readFile11, writeFile } from "fs/promises";
7815
8865
  import { createServer } from "net";
7816
8866
  import { homedir as homedir2 } from "os";
7817
- import { dirname as dirname5, join as join11, resolve } from "path";
8867
+ import { dirname as dirname5, join as join13, resolve } from "path";
7818
8868
  function getAISnitchHomePath(options = {}) {
7819
8869
  if (options.configPath && options.configPath.trim().length > 0) {
7820
8870
  return dirname5(resolve(options.configPath));
@@ -7823,13 +8873,13 @@ function getAISnitchHomePath(options = {}) {
7823
8873
  if (configuredHome && configuredHome.trim().length > 0) {
7824
8874
  return resolve(configuredHome);
7825
8875
  }
7826
- return join11(options.homeDirectory ?? homedir2(), ".aisnitch");
8876
+ return join13(options.homeDirectory ?? homedir2(), ".aisnitch");
7827
8877
  }
7828
8878
  function getConfigPath(options = {}) {
7829
8879
  if (options.configPath && options.configPath.trim().length > 0) {
7830
8880
  return resolve(options.configPath);
7831
8881
  }
7832
- return join11(getAISnitchHomePath(options), "config.json");
8882
+ return join13(getAISnitchHomePath(options), "config.json");
7833
8883
  }
7834
8884
  async function ensureConfigDir(options = {}) {
7835
8885
  const directoryPath = getAISnitchHomePath(options);
@@ -7839,7 +8889,7 @@ async function ensureConfigDir(options = {}) {
7839
8889
  async function loadConfig(options = {}) {
7840
8890
  const configPath = getConfigPath(options);
7841
8891
  try {
7842
- const rawConfig = await readFile10(configPath, "utf8");
8892
+ const rawConfig = await readFile11(configPath, "utf8");
7843
8893
  const parsedJson = JSON.parse(rawConfig);
7844
8894
  return ConfigSchema.parse(parsedJson);
7845
8895
  } catch (error) {
@@ -8102,7 +9152,7 @@ import { WebSocket, WebSocketServer } from "ws";
8102
9152
 
8103
9153
  // src/package-info.ts
8104
9154
  var AISNITCH_PACKAGE_NAME = "aisnitch";
8105
- var AISNITCH_VERSION = "0.2.19";
9155
+ var AISNITCH_VERSION = "0.2.21";
8106
9156
  var AISNITCH_DESCRIPTION = "Universal bridge for AI coding tool activity \u2014 capture, normalize, stream.";
8107
9157
  function getPackageScaffoldInfo() {
8108
9158
  return {
@@ -8639,7 +9689,7 @@ var UDSServer = class {
8639
9689
  };
8640
9690
 
8641
9691
  // src/core/engine/pipeline.ts
8642
- import { join as join12 } from "path";
9692
+ import { join as join14 } from "path";
8643
9693
  import { z as z4 } from "zod";
8644
9694
 
8645
9695
  // src/core/events/cesp.ts
@@ -8679,7 +9729,7 @@ function getSocketPath(aisnitchHomePath) {
8679
9729
  if (process.platform === "win32") {
8680
9730
  return "\\\\.\\pipe\\aisnitch.sock";
8681
9731
  }
8682
- return join12(aisnitchHomePath, "aisnitch.sock");
9732
+ return join14(aisnitchHomePath, "aisnitch.sock");
8683
9733
  }
8684
9734
  var Pipeline = class {
8685
9735
  eventBus = new EventBus();
@@ -8884,6 +9934,30 @@ var Pipeline = class {
8884
9934
  getEventBus() {
8885
9935
  return this.eventBus;
8886
9936
  }
9937
+ /**
9938
+ * Returns the adapter registry for graceful shutdown coordination.
9939
+ */
9940
+ getAdapterRegistry() {
9941
+ return this.adapterRegistry ?? void 0;
9942
+ }
9943
+ /**
9944
+ * Returns the HTTP receiver for graceful shutdown coordination.
9945
+ */
9946
+ getHttpReceiver() {
9947
+ return this.httpReceiver;
9948
+ }
9949
+ /**
9950
+ * Returns the UDS server for graceful shutdown coordination.
9951
+ */
9952
+ getUdsServer() {
9953
+ return this.udsServer;
9954
+ }
9955
+ /**
9956
+ * Returns the WebSocket server for graceful shutdown coordination.
9957
+ */
9958
+ getWsServer() {
9959
+ return this.wsServer;
9960
+ }
8887
9961
  getHealthSnapshot() {
8888
9962
  const status = this.getStatus();
8889
9963
  return {
@@ -8964,6 +10038,483 @@ var Pipeline = class {
8964
10038
  }
8965
10039
  };
8966
10040
 
10041
+ // src/core/timeout.ts
10042
+ var DEFAULT_TIMEOUTS = Object.freeze({
10043
+ /**
10044
+ * File read/write operations (JSONL transcripts, config files).
10045
+ * Default: 5 seconds
10046
+ */
10047
+ fileOperation: 5e3,
10048
+ /**
10049
+ * HTTP requests to health endpoint or external APIs.
10050
+ * Default: 30 seconds
10051
+ */
10052
+ httpRequest: 3e4,
10053
+ /**
10054
+ * Process detection commands (`pgrep`, `ps aux`).
10055
+ * Default: 3 seconds
10056
+ */
10057
+ processDetection: 3e3,
10058
+ /**
10059
+ * Adapter startup (file watchers, hook bridges, pollers).
10060
+ * Default: 10 seconds
10061
+ */
10062
+ adapterStartup: 1e4,
10063
+ /**
10064
+ * Adapter shutdown (graceful cleanup, watcher close).
10065
+ * Default: 5 seconds — after this, resources are force-closed
10066
+ */
10067
+ adapterShutdown: 5e3,
10068
+ /**
10069
+ * Daemon graceful shutdown (stop all components in order).
10070
+ * Default: 30 seconds
10071
+ */
10072
+ daemonShutdown: 3e4,
10073
+ /**
10074
+ * WebSocket connection establishment.
10075
+ * Default: 10 seconds
10076
+ */
10077
+ wsConnection: 1e4,
10078
+ /**
10079
+ * Overall pipeline start (all components).
10080
+ * Default: 15 seconds
10081
+ */
10082
+ pipelineStartup: 15e3
10083
+ });
10084
+ function withTimeout(promise, timeoutMs, context) {
10085
+ if (timeoutMs <= 0) {
10086
+ throw new TimeoutError(
10087
+ `Invalid timeout value: ${timeoutMs}ms (must be > 0)`,
10088
+ "TIMEOUT_INVALID_VALUE",
10089
+ { context, timeoutMs }
10090
+ );
10091
+ }
10092
+ return Promise.race([
10093
+ promise,
10094
+ new Promise((_, reject) => {
10095
+ const timeoutId = setTimeout(() => {
10096
+ reject(
10097
+ new TimeoutError(
10098
+ `Operation exceeded ${timeoutMs}ms deadline`,
10099
+ "TIMEOUT_EXCEEDED",
10100
+ { context, timeoutMs }
10101
+ )
10102
+ );
10103
+ }, timeoutMs);
10104
+ timeoutId.unref();
10105
+ })
10106
+ ]);
10107
+ }
10108
+ async function timeoutWarning(promise, timeoutMs, context) {
10109
+ try {
10110
+ return await withTimeout(promise, timeoutMs, context);
10111
+ } catch (error) {
10112
+ if (error instanceof TimeoutError) {
10113
+ logger.warn(
10114
+ { context: error.context, timeoutMs: error.context },
10115
+ `Best-effort operation timed out after ${timeoutMs}ms`
10116
+ );
10117
+ return await promise;
10118
+ }
10119
+ throw error;
10120
+ }
10121
+ }
10122
+ function getTimeout(name) {
10123
+ return DEFAULT_TIMEOUTS[name];
10124
+ }
10125
+ function isTimeoutError(error) {
10126
+ return error instanceof TimeoutError;
10127
+ }
10128
+
10129
+ // src/core/graceful-shutdown.ts
10130
+ async function withShutdownTimeout(fn, timeoutMs, component) {
10131
+ if (timeoutMs <= 0) {
10132
+ await fn();
10133
+ return;
10134
+ }
10135
+ const timeoutPromise = new Promise((resolve2) => {
10136
+ setTimeout(() => {
10137
+ resolve2("timed_out");
10138
+ }, timeoutMs).unref();
10139
+ });
10140
+ const result = await Promise.race([
10141
+ fn().then(() => "completed"),
10142
+ timeoutPromise
10143
+ ]);
10144
+ if (result === "timed_out") {
10145
+ logger.warn(
10146
+ { component, timeoutMs },
10147
+ `Graceful shutdown exceeded ${timeoutMs}ms timeout \u2014 forcing through`
10148
+ );
10149
+ }
10150
+ }
10151
+ async function shutdownInOrder(components, timeouts, label) {
10152
+ const getTimeout2 = (key) => {
10153
+ return timeouts[key] ?? DEFAULT_TIMEOUTS.daemonShutdown;
10154
+ };
10155
+ const stopSafely = async (key, fn) => {
10156
+ const timeoutMs = getTimeout2(key);
10157
+ try {
10158
+ await withShutdownTimeout(fn, timeoutMs, `${label}.${key}`);
10159
+ } catch (error) {
10160
+ logger.warn(
10161
+ { error, key, label },
10162
+ `Error during shutdown of ${key} \u2014 continuing with remaining components`
10163
+ );
10164
+ }
10165
+ };
10166
+ if (components.cleanupFns) {
10167
+ for (const cleanupFn of components.cleanupFns) {
10168
+ try {
10169
+ await withShutdownTimeout(
10170
+ async () => {
10171
+ const result = cleanupFn();
10172
+ if (result instanceof Promise) {
10173
+ await result;
10174
+ }
10175
+ },
10176
+ 1e3,
10177
+ `${label}.cleanup`
10178
+ );
10179
+ } catch (error) {
10180
+ logger.warn({ error, label }, "Cleanup function failed");
10181
+ }
10182
+ }
10183
+ }
10184
+ if (components.eventBus) {
10185
+ components.eventBus.unsubscribeAll();
10186
+ }
10187
+ await stopSafely("wsServer", () => components.wsServer.stop());
10188
+ await stopSafely("udsServer", () => components.udsServer.stop());
10189
+ await stopSafely("httpReceiver", () => components.httpReceiver.stop());
10190
+ if (components.adapterRegistry) {
10191
+ await stopSafely("adapterRegistry", () => components.adapterRegistry.stopAll());
10192
+ }
10193
+ }
10194
+ var GracefulShutdownManager = class {
10195
+ /**
10196
+ * Creates a new shutdown manager.
10197
+ *
10198
+ * @param options - Configuration options
10199
+ * @param options.onShutdown - Async function called when shutdown is triggered
10200
+ * @param options.exitCode - Exit code to use (default: 0 for graceful, 1 for errors)
10201
+ * @param options.exitDelayMs - Delay before `process.exit()` (default: 100ms for flush)
10202
+ */
10203
+ constructor(options) {
10204
+ this.options = options;
10205
+ }
10206
+ shuttingDown = false;
10207
+ pendingHandlers = /* @__PURE__ */ new Set();
10208
+ /**
10209
+ * Synchronous handler function suitable for `process.on()`.
10210
+ *
10211
+ * Multiple calls are safe — only the first call executes `onShutdown`.
10212
+ * Subsequent calls queue to the internal pending set and run after the
10213
+ * first shutdown completes.
10214
+ */
10215
+ get handler() {
10216
+ return (signal) => {
10217
+ if (!this.shuttingDown) {
10218
+ this.shuttingDown = true;
10219
+ void this.runShutdown(signal);
10220
+ } else {
10221
+ this.pendingHandlers.add(() => {
10222
+ void this.runShutdown(signal);
10223
+ });
10224
+ }
10225
+ };
10226
+ }
10227
+ /**
10228
+ * Manually triggers shutdown from async code (e.g., TUI quit button).
10229
+ *
10230
+ * @param signal - Signal name (for logging)
10231
+ */
10232
+ shutdown(signal = "manual") {
10233
+ this.handler(signal);
10234
+ }
10235
+ /**
10236
+ * Returns whether shutdown is currently in progress.
10237
+ */
10238
+ isShuttingDown() {
10239
+ return this.shuttingDown;
10240
+ }
10241
+ async runShutdown(signal) {
10242
+ const exitCode = this.options.exitCode ?? (signal === "uncaughtException" || signal === "unhandledRejection" ? 1 : 0);
10243
+ const exitDelayMs = this.options.exitDelayMs ?? 100;
10244
+ try {
10245
+ await this.options.onShutdown(signal);
10246
+ } catch (error) {
10247
+ logger.error(
10248
+ { error, signal },
10249
+ "Error during graceful shutdown \u2014 forcing exit"
10250
+ );
10251
+ } finally {
10252
+ await new Promise((resolve2) => {
10253
+ setTimeout(resolve2, exitDelayMs).unref();
10254
+ });
10255
+ for (const pendingHandler of this.pendingHandlers) {
10256
+ pendingHandler();
10257
+ }
10258
+ process.exit(exitCode);
10259
+ }
10260
+ }
10261
+ };
10262
+ async function withOverallShutdownTimeout(shutdownPromise, timeoutMs, label) {
10263
+ try {
10264
+ await withTimeout(shutdownPromise, timeoutMs, `${label}-overall-shutdown`);
10265
+ } catch (error) {
10266
+ if (isTimeoutError(error)) {
10267
+ logger.error(
10268
+ { timeoutMs, label },
10269
+ `Overall shutdown timeout exceeded \u2014 forcing process exit`
10270
+ );
10271
+ }
10272
+ process.exit(1);
10273
+ }
10274
+ }
10275
+
10276
+ // src/core/result.ts
10277
+ function isOk(result) {
10278
+ return result.success === true;
10279
+ }
10280
+ function isErr(result) {
10281
+ return result.success === false;
10282
+ }
10283
+ function ok(value) {
10284
+ return Object.freeze({ success: true, value });
10285
+ }
10286
+ function err(error) {
10287
+ return Object.freeze({ success: false, error });
10288
+ }
10289
+ function mapOk(result, fn) {
10290
+ if (!result.success) {
10291
+ return result;
10292
+ }
10293
+ return ok(fn(result.value));
10294
+ }
10295
+ function mapErr(result, fn) {
10296
+ if (result.success) {
10297
+ return result;
10298
+ }
10299
+ return err(fn(result.error));
10300
+ }
10301
+ async function flatMap(result, fn) {
10302
+ if (!result.success) {
10303
+ return result;
10304
+ }
10305
+ const mapped = await fn(result.value);
10306
+ if (!mapped.success) {
10307
+ return mapped;
10308
+ }
10309
+ return ok(mapped.value);
10310
+ }
10311
+ async function fromPromise(promise, mapError) {
10312
+ try {
10313
+ const value = await promise;
10314
+ return ok(value);
10315
+ } catch (reason) {
10316
+ return err(mapError(reason));
10317
+ }
10318
+ }
10319
+ function fromSync(fn, mapError) {
10320
+ try {
10321
+ return ok(fn());
10322
+ } catch (reason) {
10323
+ return err(mapError(reason));
10324
+ }
10325
+ }
10326
+
10327
+ // src/core/retry.ts
10328
+ var DefaultRetryOptions = {
10329
+ attempts: 3,
10330
+ backoff: 2,
10331
+ context: "unknown",
10332
+ delayMs: 500,
10333
+ jitter: true,
10334
+ maxTotalDelayMs: 3e4,
10335
+ shouldRetry: isRetryableError
10336
+ };
10337
+ function sleep(ms) {
10338
+ return new Promise((resolve2) => {
10339
+ setTimeout(resolve2, ms).unref();
10340
+ });
10341
+ }
10342
+ function computeDelay(attempt, baseDelayMs, backoff, jitter) {
10343
+ const exponentialDelay = baseDelayMs * Math.pow(backoff, attempt - 1);
10344
+ if (!jitter) {
10345
+ return exponentialDelay;
10346
+ }
10347
+ const jitterFactor = 0.75 + Math.random() * 0.5;
10348
+ return Math.round(exponentialDelay * jitterFactor);
10349
+ }
10350
+ async function withRetry(fn, options) {
10351
+ const {
10352
+ attempts = DefaultRetryOptions.attempts,
10353
+ backoff = DefaultRetryOptions.backoff,
10354
+ delayMs = DefaultRetryOptions.delayMs,
10355
+ maxTotalDelayMs = DefaultRetryOptions.maxTotalDelayMs,
10356
+ jitter = DefaultRetryOptions.jitter,
10357
+ shouldRetry = DefaultRetryOptions.shouldRetry
10358
+ } = options;
10359
+ let lastError;
10360
+ let totalDelayMs = 0;
10361
+ const shouldRetryWithDefault = shouldRetry ?? DefaultRetryOptions.shouldRetry;
10362
+ for (let attempt = 1; attempt <= attempts; attempt++) {
10363
+ try {
10364
+ return await fn();
10365
+ } catch (error) {
10366
+ lastError = error;
10367
+ const retryable = shouldRetryWithDefault(error);
10368
+ const attemptsRemaining = attempt < attempts;
10369
+ if (!retryable || !attemptsRemaining) {
10370
+ if (!retryable) {
10371
+ logger.debug(
10372
+ { attempt, context: options.context, error },
10373
+ "Non-retryable error \u2014 giving up immediately"
10374
+ );
10375
+ } else {
10376
+ logger.error(
10377
+ { attempt, attempts, context: options.context, error },
10378
+ "All retry attempts exhausted"
10379
+ );
10380
+ }
10381
+ throw error;
10382
+ }
10383
+ const delay = computeDelay(attempt, delayMs, backoff, jitter ?? false);
10384
+ totalDelayMs += delay;
10385
+ if (totalDelayMs > maxTotalDelayMs) {
10386
+ logger.warn(
10387
+ { attempt, delay, totalDelayMs, maxTotalDelayMs, context: options.context },
10388
+ "Retry max total delay exceeded \u2014 giving up"
10389
+ );
10390
+ throw lastError;
10391
+ }
10392
+ logger.debug(
10393
+ { attempt, attempts, delay, nextDelayMs: delay, context: options.context },
10394
+ `Operation failed \u2014 retrying in ${delay}ms`
10395
+ );
10396
+ await sleep(delay);
10397
+ }
10398
+ }
10399
+ throw lastError;
10400
+ }
10401
+ function fireAndForgetRetry(fn, options) {
10402
+ void withRetry(fn, {
10403
+ ...options,
10404
+ attempts: options.attempts ?? 2
10405
+ }).catch((error) => {
10406
+ logger.warn(
10407
+ { error, context: options.context },
10408
+ "Fire-and-forget retry also failed \u2014 giving up silently"
10409
+ );
10410
+ });
10411
+ }
10412
+ function withRetryOn(fn, options) {
10413
+ return ((...args) => withRetry(() => fn(...args), options));
10414
+ }
10415
+
10416
+ // src/core/safety.ts
10417
+ var MAX_PORT = 65535;
10418
+ var MIN_PORT = 1;
10419
+ var MAX_PATH_LENGTH = 4096;
10420
+ var MAX_GENERIC_STRING_LENGTH = 1e4;
10421
+ var MAX_LABEL_LENGTH = 255;
10422
+ function getString11(record, key) {
10423
+ const value = record[key];
10424
+ if (typeof value !== "string") {
10425
+ return void 0;
10426
+ }
10427
+ const trimmed = value.trim();
10428
+ return trimmed.length > 0 ? trimmed : void 0;
10429
+ }
10430
+ function getStringWithMaxLength(record, key, maxLength) {
10431
+ const value = getString11(record, key);
10432
+ if (value === void 0) {
10433
+ return void 0;
10434
+ }
10435
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
10436
+ }
10437
+ function getNumber9(record, key) {
10438
+ const value = record[key];
10439
+ if (typeof value !== "number" || !Number.isFinite(value)) {
10440
+ return void 0;
10441
+ }
10442
+ return value;
10443
+ }
10444
+ function getSafeInteger(record, key, options = {}) {
10445
+ const value = getNumber9(record, key);
10446
+ if (value === void 0) {
10447
+ return void 0;
10448
+ }
10449
+ if (!Number.isInteger(value)) {
10450
+ return void 0;
10451
+ }
10452
+ if (options.min !== void 0 && value < options.min) {
10453
+ return void 0;
10454
+ }
10455
+ if (options.max !== void 0 && value > options.max) {
10456
+ return void 0;
10457
+ }
10458
+ return value;
10459
+ }
10460
+ function getPositiveNumber(record, key) {
10461
+ const value = getNumber9(record, key);
10462
+ return value !== void 0 && value > 0 ? value : void 0;
10463
+ }
10464
+ function getBoolean2(record, key) {
10465
+ const value = record[key];
10466
+ if (typeof value === "boolean") {
10467
+ return value;
10468
+ }
10469
+ if (typeof value === "string") {
10470
+ const lower = value.toLowerCase();
10471
+ if (lower === "true" || lower === "1") {
10472
+ return true;
10473
+ }
10474
+ if (lower === "false" || lower === "0") {
10475
+ return false;
10476
+ }
10477
+ }
10478
+ return void 0;
10479
+ }
10480
+ function getArray(record, key) {
10481
+ const value = record[key];
10482
+ return Array.isArray(value) ? value : void 0;
10483
+ }
10484
+ function getObject(record, key) {
10485
+ const value = record[key];
10486
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10487
+ return void 0;
10488
+ }
10489
+ return value;
10490
+ }
10491
+ function isValidPort(port) {
10492
+ return port !== void 0 && Number.isInteger(port) && port >= MIN_PORT && port <= MAX_PORT;
10493
+ }
10494
+ function isValidPathLength(path) {
10495
+ return path !== void 0 && path.length > 0 && path.length <= MAX_PATH_LENGTH;
10496
+ }
10497
+ function isValidStringLength(value, maxLength) {
10498
+ return value !== void 0 && value.length <= maxLength;
10499
+ }
10500
+ function isRecord11(value) {
10501
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10502
+ return false;
10503
+ }
10504
+ const proto = Object.prototype.toString.call(value);
10505
+ return proto === "[object Object]";
10506
+ }
10507
+ function isNotNull(value) {
10508
+ return value != null;
10509
+ }
10510
+ function getPort(record, key) {
10511
+ const value = getSafeInteger(record, key, { min: MIN_PORT, max: MAX_PORT });
10512
+ return value;
10513
+ }
10514
+ function getSeqnum(record, key) {
10515
+ return getSafeInteger(record, key, { min: 1 });
10516
+ }
10517
+
8967
10518
  // src/tui/index.tsx
8968
10519
  import { render } from "ink";
8969
10520
  import { withFullScreen } from "fullscreen-ink";
@@ -9025,7 +10576,7 @@ function getEventDetailSegments(event) {
9025
10576
  break;
9026
10577
  case "agent.asking_user":
9027
10578
  segments.push(
9028
- getString11(raw, "notification_type") ?? getString11(raw, "notificationType") ?? getString11(raw, "type")
10579
+ getString12(raw, "notification_type") ?? getString12(raw, "notificationType") ?? getString12(raw, "type")
9029
10580
  );
9030
10581
  segments.push(event.data.errorMessage ?? extractLooseString3(raw, [
9031
10582
  "message",
@@ -9099,10 +10650,10 @@ function extractContentPart(raw, partType, valueKey) {
9099
10650
  }
9100
10651
  for (const part of content) {
9101
10652
  const record = getRecord10(part);
9102
- if (!record || getString11(record, "type") !== partType) {
10653
+ if (!record || getString12(record, "type") !== partType) {
9103
10654
  continue;
9104
10655
  }
9105
- const value = getString11(record, valueKey);
10656
+ const value = getString12(record, valueKey);
9106
10657
  if (value) {
9107
10658
  return value;
9108
10659
  }
@@ -9114,12 +10665,12 @@ function extractLooseString3(raw, keys) {
9114
10665
  return void 0;
9115
10666
  }
9116
10667
  for (const key of keys) {
9117
- const directValue = getString11(raw, key);
10668
+ const directValue = getString12(raw, key);
9118
10669
  if (directValue) {
9119
10670
  return directValue;
9120
10671
  }
9121
10672
  const nestedRecord = getRecord10(raw[key]);
9122
- const nestedValue = getString11(nestedRecord, "text") ?? getString11(nestedRecord, "message") ?? getString11(nestedRecord, "content");
10673
+ const nestedValue = getString12(nestedRecord, "text") ?? getString12(nestedRecord, "message") ?? getString12(nestedRecord, "content");
9123
10674
  if (nestedValue) {
9124
10675
  return nestedValue;
9125
10676
  }
@@ -9133,13 +10684,13 @@ function truncateSegment(value) {
9133
10684
  }
9134
10685
  return `${normalized.slice(0, DETAIL_SEGMENT_LIMIT - 1)}\u2026`;
9135
10686
  }
9136
- function isRecord11(value) {
10687
+ function isRecord12(value) {
9137
10688
  return typeof value === "object" && value !== null && !Array.isArray(value);
9138
10689
  }
9139
10690
  function getRecord10(value) {
9140
- return isRecord11(value) ? value : void 0;
10691
+ return isRecord12(value) ? value : void 0;
9141
10692
  }
9142
- function getString11(payload, key) {
10693
+ function getString12(payload, key) {
9143
10694
  if (!payload) {
9144
10695
  return void 0;
9145
10696
  }
@@ -9167,9 +10718,11 @@ var TOOL_COLORS = {
9167
10718
  "openhands": "#facc15",
9168
10719
  "openclaw": "#ef4444",
9169
10720
  "opencode": "#10b981",
10721
+ "pi": "#1db954",
9170
10722
  "qwen-code": "#22c55e",
9171
10723
  "unknown": "#94a3b8",
9172
- "windsurf": "#a855f7"
10724
+ "windsurf": "#a855f7",
10725
+ "zed": "#e85d04"
9173
10726
  };
9174
10727
  var EVENT_COLORS = {
9175
10728
  "agent.asking_user": "#ef4444",
@@ -11018,10 +12571,12 @@ export {
11018
12571
  AISNITCH_EVENT_TYPES,
11019
12572
  AISNITCH_PACKAGE_NAME,
11020
12573
  AISNITCH_VERSION,
12574
+ AISnitchError,
11021
12575
  AISnitchEventSchema,
11022
12576
  AISnitchEventTypeSchema,
11023
12577
  AUTO_UPDATE_MANAGERS,
11024
12578
  AdapterConfigSchema,
12579
+ AdapterError,
11025
12580
  AdapterRegistry,
11026
12581
  AiderAdapter,
11027
12582
  App,
@@ -11030,6 +12585,8 @@ export {
11030
12585
  CESPCategorySchema,
11031
12586
  CESP_CATEGORIES,
11032
12587
  CESP_MAP,
12588
+ CircuitBreaker,
12589
+ CircuitOpenError,
11033
12590
  ClaudeCodeAdapter,
11034
12591
  CodexAdapter,
11035
12592
  ConfigSchema,
@@ -11037,8 +12594,10 @@ export {
11037
12594
  CopilotCLIAdapter,
11038
12595
  CursorAdapter,
11039
12596
  DEFAULT_CONFIG,
12597
+ DEFAULT_TIMEOUTS,
11040
12598
  DEFAULT_TUI_FILTERS,
11041
12599
  DEFAULT_VISIBLE_EVENT_COUNT,
12600
+ DefaultRetryOptions,
11042
12601
  DevinAdapter,
11043
12602
  ERROR_TYPES,
11044
12603
  EVENT_COLORS,
@@ -11050,33 +12609,51 @@ export {
11050
12609
  EventLine,
11051
12610
  EventStream,
11052
12611
  FilterBar,
12612
+ FinalMessageSchema,
11053
12613
  GeminiCLIAdapter,
11054
12614
  GenericPTYSession,
11055
12615
  GlobalBadge,
11056
12616
  GooseAdapter,
12617
+ GracefulShutdownManager,
11057
12618
  HTTPReceiver,
11058
12619
  Header,
11059
12620
  HelpOverlay,
11060
12621
  KiloAdapter,
11061
12622
  LOG_LEVELS,
12623
+ MAX_GENERIC_STRING_LENGTH,
12624
+ MAX_LABEL_LENGTH,
12625
+ MAX_PATH_LENGTH,
12626
+ MAX_PORT,
12627
+ MIN_PORT,
11062
12628
  ManagedDaemonApp,
12629
+ MessageContentSchema,
12630
+ NetworkError,
11063
12631
  OpenClawAdapter,
11064
12632
  OpenCodeAdapter,
11065
12633
  Panel,
11066
12634
  PanelStack,
12635
+ PiAdapter,
11067
12636
  Pipeline,
12637
+ PipelineError,
11068
12638
  RingBuffer,
11069
12639
  SESSION_STALE_AFTER_MS,
12640
+ SHARED_BREAKERS,
11070
12641
  SessionPanel,
11071
12642
  StatusBar,
11072
12643
  TOOL_COLORS,
11073
12644
  TOOL_NAMES,
11074
12645
  TUI_THEME,
11075
12646
  TUI_VIEW_MODES,
12647
+ ThinkingContentSchema,
12648
+ TimeoutError,
12649
+ ToolCallNameSchema,
11076
12650
  ToolInputSchema,
11077
12651
  ToolNameSchema,
12652
+ ToolResultSchema,
11078
12653
  UDSServer,
12654
+ ValidationError,
11079
12655
  WSServer,
12656
+ ZedAdapter,
11080
12657
  analyzeTerminalOutputChunk,
11081
12658
  appendEventToStream,
11082
12659
  applyEventFilters,
@@ -11090,6 +12667,9 @@ export {
11090
12667
  deriveGlobalActivityStatus,
11091
12668
  deriveSessions,
11092
12669
  ensureConfigDir,
12670
+ err,
12671
+ fireAndForgetRetry,
12672
+ flatMap,
11093
12673
  formatEventDetail,
11094
12674
  formatEventLine,
11095
12675
  formatEventTime,
@@ -11097,16 +12677,42 @@ export {
11097
12677
  formatSessionLabelFromEvent,
11098
12678
  formatSessionShortId,
11099
12679
  formatWelcomeLine,
12680
+ fromPromise,
12681
+ fromSync,
11100
12682
  getAISnitchHomePath,
12683
+ getArray,
12684
+ getBoolean2 as getBoolean,
11101
12685
  getCESPCategory,
11102
12686
  getConfigPath,
12687
+ getNumber9 as getNumber,
12688
+ getObject,
11103
12689
  getPackageScaffoldInfo,
11104
12690
  getPendingFrozenEventCount,
12691
+ getPort,
12692
+ getPositiveNumber,
12693
+ getSafeInteger,
12694
+ getSeqnum,
11105
12695
  getSocketPath,
12696
+ getString11 as getString,
12697
+ getStringWithMaxLength,
12698
+ getTimeout,
11106
12699
  getVisibleEventWindow,
12700
+ isAISnitchError,
12701
+ isErr,
11107
12702
  isGenericSessionId,
12703
+ isNotNull,
12704
+ isOk,
12705
+ isRecord11 as isRecord,
12706
+ isRetryableError,
12707
+ isTimeoutError,
12708
+ isValidPathLength,
12709
+ isValidPort,
12710
+ isValidStringLength,
11108
12711
  loadConfig,
11109
12712
  logger,
12713
+ mapErr,
12714
+ mapOk,
12715
+ ok,
11110
12716
  parseAiderHistoryMarkdown,
11111
12717
  renderAttachedTui,
11112
12718
  renderForegroundTui,
@@ -11115,8 +12721,16 @@ export {
11115
12721
  resolveSessionId,
11116
12722
  saveConfig,
11117
12723
  setLoggerLevel,
12724
+ shutdownInOrder,
12725
+ sleep,
12726
+ timeoutWarning,
11118
12727
  useEventStream,
11119
12728
  useKeyBinds,
11120
- useSessions
12729
+ useSessions,
12730
+ withOverallShutdownTimeout,
12731
+ withRetry,
12732
+ withRetryOn,
12733
+ withShutdownTimeout,
12734
+ withTimeout
11121
12735
  };
11122
12736
  //# sourceMappingURL=index.js.map