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/cli/index.js CHANGED
@@ -8,7 +8,7 @@ import { Command, InvalidArgumentError } from "commander";
8
8
 
9
9
  // src/package-info.ts
10
10
  var AISNITCH_PACKAGE_NAME = "aisnitch";
11
- var AISNITCH_VERSION = "0.2.19";
11
+ var AISNITCH_VERSION = "0.2.21";
12
12
  var AISNITCH_DESCRIPTION = "Universal bridge for AI coding tool activity \u2014 capture, normalize, stream.";
13
13
 
14
14
  // src/core/events/schema.ts
@@ -49,6 +49,8 @@ var TOOL_NAMES = [
49
49
  "kiro",
50
50
  "augment-code",
51
51
  "mistral",
52
+ "zed",
53
+ "pi",
52
54
  "unknown"
53
55
  ];
54
56
  var ERROR_TYPES = [
@@ -90,47 +92,58 @@ function createUuidV7() {
90
92
  return uuidv7();
91
93
  }
92
94
  var ToolInputSchema = z.strictObject({
93
- filePath: z.string().min(1).optional(),
94
- command: z.string().min(1).optional()
95
+ filePath: z.string().min(1).max(4096).optional(),
96
+ command: z.string().min(1).max(1e4).optional()
95
97
  }).refine(
96
98
  (value) => value.filePath !== void 0 || value.command !== void 0,
97
99
  "toolInput must include filePath or command"
98
100
  );
101
+ var ThinkingContentSchema = z.string().max(1e5).describe("Raw thinking/reasoning content from the AI model");
102
+ var ToolCallNameSchema = z.string().min(1).max(100).describe("Name of the tool being invoked (e.g., Edit, Bash, Grep)");
103
+ var FinalMessageSchema = z.string().max(5e4).describe("End-of-run summary or completion message");
104
+ var ToolResultSchema = z.string().max(1e4).describe("Tool execution result or output");
105
+ var MessageContentSchema = z.string().max(1e5).describe("Raw text content from AI messages");
99
106
  var ToolNameSchema = z.enum(TOOL_NAMES);
100
107
  var AISnitchEventTypeSchema = z.enum(AISNITCH_EVENT_TYPES);
101
108
  var ErrorTypeSchema = z.enum(ERROR_TYPES);
102
109
  var CESPCategorySchema = z.enum(CESP_CATEGORIES);
103
110
  var EventDataSchema = z.strictObject({
104
111
  state: AISnitchEventTypeSchema,
105
- project: z.string().min(1).optional(),
106
- projectPath: z.string().min(1).optional(),
112
+ project: z.string().min(1).max(255).optional(),
113
+ projectPath: z.string().min(1).max(4096).optional(),
107
114
  duration: z.number().int().min(0).optional(),
108
- toolName: z.string().min(1).optional(),
115
+ toolName: z.string().min(1).max(100).optional(),
109
116
  toolInput: ToolInputSchema.optional(),
110
- activeFile: z.string().min(1).optional(),
111
- model: z.string().min(1).optional(),
117
+ activeFile: z.string().min(1).max(4096).optional(),
118
+ model: z.string().min(1).max(200).optional(),
112
119
  tokensUsed: z.number().int().min(0).optional(),
113
- errorMessage: z.string().min(1).optional(),
120
+ errorMessage: z.string().min(1).max(1e4).optional(),
114
121
  errorType: ErrorTypeSchema.optional(),
115
122
  raw: z.record(z.string(), z.unknown()).optional(),
116
- terminal: z.string().min(1).optional(),
117
- cwd: z.string().min(1).optional(),
123
+ terminal: z.string().min(1).max(100).optional(),
124
+ cwd: z.string().min(1).max(4096).optional(),
118
125
  pid: z.number().int().positive().optional(),
119
- instanceId: z.string().min(1).optional(),
126
+ instanceId: z.string().min(1).max(255).optional(),
120
127
  instanceIndex: z.number().int().min(1).optional(),
121
- instanceTotal: z.number().int().min(1).optional()
128
+ instanceTotal: z.number().int().min(1).optional(),
129
+ // New fields for enhanced content capture
130
+ thinkingContent: ThinkingContentSchema.optional(),
131
+ toolCallName: ToolCallNameSchema.optional(),
132
+ finalMessage: FinalMessageSchema.optional(),
133
+ toolResult: ToolResultSchema.optional(),
134
+ messageContent: MessageContentSchema.optional()
122
135
  });
123
136
  var AISnitchEventSchema = z.strictObject({
124
137
  specversion: z.literal("1.0"),
125
138
  id: z.string().refine(isUuidV7, "id must be a valid UUIDv7 string"),
126
- source: z.string().refine(
139
+ source: z.string().max(2e3).refine(
127
140
  isValidUriReference,
128
141
  "source must be a valid non-empty CloudEvents URI-reference"
129
142
  ),
130
143
  type: AISnitchEventTypeSchema,
131
144
  time: ISO_TIMESTAMP_SCHEMA,
132
145
  "aisnitch.tool": ToolNameSchema,
133
- "aisnitch.sessionid": z.string().min(1),
146
+ "aisnitch.sessionid": z.string().min(1).max(500),
134
147
  "aisnitch.seqnum": z.number().int().min(1),
135
148
  data: EventDataSchema
136
149
  });
@@ -2432,11 +2445,11 @@ function toConfigPathOptions(options) {
2432
2445
  // src/cli/runtime.ts
2433
2446
  import { execFile as execFileCallback14, spawn as spawnChildProcess2 } from "child_process";
2434
2447
  import { closeSync, openSync } from "fs";
2435
- import { mkdtemp, readFile as readFile14, rename, rm as rm4, writeFile as writeFile5 } from "fs/promises";
2448
+ import { mkdtemp, readFile as readFile15, rename, rm as rm4, writeFile as writeFile5 } from "fs/promises";
2436
2449
  import { createConnection as createConnection3 } from "net";
2437
2450
  import { tmpdir } from "os";
2438
- import { basename as basename11, join as join16 } from "path";
2439
- import { promisify as promisify14 } from "util";
2451
+ import { basename as basename12, join as join18 } from "path";
2452
+ import { promisify as promisify15 } from "util";
2440
2453
 
2441
2454
  // src/adapters/generic-pty.ts
2442
2455
  import { basename as basename3 } from "path";
@@ -2507,9 +2520,11 @@ var TOOL_BINARY_MAP = {
2507
2520
  "openhands": "openhands",
2508
2521
  "openclaw": "openclaw",
2509
2522
  "opencode": "opencode",
2523
+ "pi": "pi",
2510
2524
  "qwen-code": "qwen",
2511
2525
  "unknown": "unknown",
2512
- "windsurf": "windsurf"
2526
+ "windsurf": "windsurf",
2527
+ "zed": "zed"
2513
2528
  };
2514
2529
  var ContextDetector = class {
2515
2530
  cache = /* @__PURE__ */ new Map();
@@ -3318,6 +3333,346 @@ function isPidRunning(pid) {
3318
3333
  }
3319
3334
  }
3320
3335
 
3336
+ // src/core/errors.ts
3337
+ var AISnitchError = class _AISnitchError extends Error {
3338
+ /**
3339
+ * Machine-readable error code for programmatic handling.
3340
+ * Format: `SUBCATEGORY_SPECIFIC_DETAIL` (uppercase with underscores).
3341
+ */
3342
+ code;
3343
+ /**
3344
+ * Arbitrary context bag forwarded to the logger for structured debugging.
3345
+ */
3346
+ context;
3347
+ constructor(message, code, context) {
3348
+ super(message);
3349
+ this.name = "AISnitchError";
3350
+ this.code = code;
3351
+ this.context = context;
3352
+ if (Error.captureStackTrace) {
3353
+ Error.captureStackTrace(this, _AISnitchError);
3354
+ }
3355
+ }
3356
+ /**
3357
+ * Full error chain for logging: `[name] code — message`.
3358
+ */
3359
+ toString() {
3360
+ return `${this.name} [${this.code}] \u2014 ${this.message}`;
3361
+ }
3362
+ /**
3363
+ * JSON serialization friendly to pino serializers.
3364
+ */
3365
+ toJSON() {
3366
+ return {
3367
+ name: this.name,
3368
+ code: this.code,
3369
+ message: this.message,
3370
+ context: this.context,
3371
+ stack: this.stack
3372
+ };
3373
+ }
3374
+ };
3375
+ function isAISnitchError(error) {
3376
+ return error instanceof AISnitchError;
3377
+ }
3378
+ function isRetryableError(error) {
3379
+ if (!isAISnitchError(error)) {
3380
+ if (error instanceof Error) {
3381
+ const code = error.code;
3382
+ const retryableCodes = /* @__PURE__ */ new Set([
3383
+ "ECONNREFUSED",
3384
+ "ECONNRESET",
3385
+ "ETIMEDOUT",
3386
+ "ENOTFOUND",
3387
+ "EHOSTUNREACH",
3388
+ "EPIPE",
3389
+ "EPERM"
3390
+ // sometimes transient on macOS file locks
3391
+ ]);
3392
+ if (typeof code === "string" && retryableCodes.has(code)) {
3393
+ return true;
3394
+ }
3395
+ }
3396
+ return false;
3397
+ }
3398
+ const retryableCategories = /* @__PURE__ */ new Set(["TIMEOUT", "NETWORK"]);
3399
+ for (const category of retryableCategories) {
3400
+ if (error.code.startsWith(category)) {
3401
+ return true;
3402
+ }
3403
+ }
3404
+ const retryablePatterns = [
3405
+ /^ADAPTER_.*_(FILE_IO|NETWORK|PROCESS_DETECT)_ERROR$/,
3406
+ /^PIPELINE_.*_(RETRY|RECONNECT)_ERROR$/
3407
+ ];
3408
+ for (const pattern of retryablePatterns) {
3409
+ if (pattern.test(error.code)) {
3410
+ return true;
3411
+ }
3412
+ }
3413
+ return false;
3414
+ }
3415
+
3416
+ // src/core/circuit-breaker.ts
3417
+ var CircuitOpenError = class _CircuitOpenError extends AISnitchError {
3418
+ constructor(circuitId, state) {
3419
+ super(
3420
+ `Circuit "${circuitId}" is OPEN \u2014 operation rejected`,
3421
+ "CIRCUIT_OPEN",
3422
+ { circuitId, failures: state.failures, lastFailureAt: state.lastFailureAt }
3423
+ );
3424
+ this.circuitId = circuitId;
3425
+ this.state = state;
3426
+ this.name = "CircuitOpenError";
3427
+ if (Error.captureStackTrace) {
3428
+ Error.captureStackTrace(this, _CircuitOpenError);
3429
+ }
3430
+ }
3431
+ toString() {
3432
+ return `${this.name} [${this.code}] "${this.circuitId}" \u2014 failures=${this.state.failures}`;
3433
+ }
3434
+ };
3435
+ var DEFAULT_OPTIONS = {
3436
+ failureThreshold: 5,
3437
+ halfOpenAfterMs: 3e4,
3438
+ id: "unnamed",
3439
+ resetOnSuccess: true,
3440
+ shouldCountAsFailure: isRetryableError,
3441
+ windowMs: 6e4
3442
+ };
3443
+ var CircuitBreaker = class {
3444
+ failures = 0;
3445
+ lastFailureAt = null;
3446
+ state = "closed";
3447
+ halfOpenTestStartedAt = null;
3448
+ options;
3449
+ constructor(options = {}) {
3450
+ this.options = {
3451
+ ...DEFAULT_OPTIONS,
3452
+ ...options,
3453
+ // Re-spread to ensure all fields have defaults
3454
+ failureThreshold: options.failureThreshold ?? DEFAULT_OPTIONS.failureThreshold,
3455
+ halfOpenAfterMs: options.halfOpenAfterMs ?? DEFAULT_OPTIONS.halfOpenAfterMs,
3456
+ id: options.id ?? DEFAULT_OPTIONS.id,
3457
+ resetOnSuccess: options.resetOnSuccess ?? DEFAULT_OPTIONS.resetOnSuccess,
3458
+ shouldCountAsFailure: options.shouldCountAsFailure ?? DEFAULT_OPTIONS.shouldCountAsFailure,
3459
+ windowMs: options.windowMs ?? DEFAULT_OPTIONS.windowMs
3460
+ };
3461
+ }
3462
+ /**
3463
+ * Executes an async operation through the circuit breaker.
3464
+ *
3465
+ * - If the circuit is CLOSED → runs `fn` and updates state based on result
3466
+ * - If the circuit is HALF-OPEN → runs `fn` once to test recovery
3467
+ * - If the circuit is OPEN → throws `CircuitOpenError` immediately (no call)
3468
+ *
3469
+ * @param fn - The async operation to protect
3470
+ * @returns The result of `fn` if successful
3471
+ * @throws CircuitOpenError if the circuit is OPEN
3472
+ * @throws The error from `fn` if it throws (and `shouldCountAsFailure` returns true)
3473
+ */
3474
+ async execute(fn) {
3475
+ switch (this.state) {
3476
+ case "closed":
3477
+ return this.executeClosed(fn);
3478
+ case "half-open":
3479
+ return this.executeHalfOpen(fn);
3480
+ case "open":
3481
+ if (this.shouldTransitionToHalfOpen()) {
3482
+ this.transitionToHalfOpen();
3483
+ return this.executeHalfOpen(fn);
3484
+ }
3485
+ throw new CircuitOpenError(this.options.id, this.getState());
3486
+ }
3487
+ }
3488
+ /**
3489
+ * Returns the current observable circuit state.
3490
+ */
3491
+ getState() {
3492
+ return {
3493
+ failures: this.failures,
3494
+ lastFailureAt: this.lastFailureAt,
3495
+ state: this.state
3496
+ };
3497
+ }
3498
+ /**
3499
+ * Forces the circuit to CLOSED (resets failure count and state).
3500
+ * Useful for manual recovery after a known-fix or after a maintenance window.
3501
+ */
3502
+ reset() {
3503
+ this.failures = 0;
3504
+ this.lastFailureAt = null;
3505
+ this.state = "closed";
3506
+ this.halfOpenTestStartedAt = null;
3507
+ logger.debug({ circuitId: this.options.id }, "Circuit breaker manually reset");
3508
+ }
3509
+ /**
3510
+ * Pre-warms the circuit by performing one test call in HALF-OPEN state.
3511
+ * If the circuit is already HALF-OPEN, this does nothing.
3512
+ * If the circuit is CLOSED, this does nothing.
3513
+ */
3514
+ async preWarm(fn) {
3515
+ if (this.state !== "open") {
3516
+ return;
3517
+ }
3518
+ this.transitionToHalfOpen();
3519
+ try {
3520
+ await fn();
3521
+ this.transitionToClosed();
3522
+ } catch {
3523
+ this.transitionToOpen();
3524
+ }
3525
+ }
3526
+ // ─────────────────────────────────────────────────────────────────────────
3527
+ // Private methods
3528
+ // ─────────────────────────────────────────────────────────────────────────
3529
+ async executeClosed(fn) {
3530
+ try {
3531
+ const result = await fn();
3532
+ this.onSuccess();
3533
+ return result;
3534
+ } catch (error) {
3535
+ this.onFailure(error);
3536
+ throw error;
3537
+ }
3538
+ }
3539
+ async executeHalfOpen(fn) {
3540
+ this.halfOpenTestStartedAt = Date.now();
3541
+ try {
3542
+ const result = await fn();
3543
+ this.transitionToClosed();
3544
+ return result;
3545
+ } catch (error) {
3546
+ this.transitionToOpen();
3547
+ throw error;
3548
+ }
3549
+ }
3550
+ onSuccess() {
3551
+ if (this.options.resetOnSuccess) {
3552
+ this.failures = 0;
3553
+ this.lastFailureAt = null;
3554
+ } else {
3555
+ this.failures = Math.max(0, this.failures - 1);
3556
+ if (this.failures === 0) {
3557
+ this.lastFailureAt = null;
3558
+ }
3559
+ }
3560
+ logger.debug(
3561
+ {
3562
+ circuitId: this.options.id,
3563
+ failures: this.failures
3564
+ },
3565
+ "Circuit breaker operation succeeded"
3566
+ );
3567
+ }
3568
+ onFailure(error) {
3569
+ if (!this.options.shouldCountAsFailure(error)) {
3570
+ logger.debug(
3571
+ { circuitId: this.options.id, error },
3572
+ "Circuit breaker operation failed but error is not counted as failure"
3573
+ );
3574
+ return;
3575
+ }
3576
+ this.failures += 1;
3577
+ this.lastFailureAt = Date.now();
3578
+ if (this.failures >= this.options.failureThreshold) {
3579
+ this.transitionToOpen();
3580
+ } else {
3581
+ logger.debug(
3582
+ {
3583
+ circuitId: this.options.id,
3584
+ failures: this.failures,
3585
+ threshold: this.options.failureThreshold
3586
+ },
3587
+ "Circuit breaker recorded failure"
3588
+ );
3589
+ }
3590
+ }
3591
+ transitionToOpen() {
3592
+ if (this.state === "open") {
3593
+ return;
3594
+ }
3595
+ this.state = "open";
3596
+ this.halfOpenTestStartedAt = null;
3597
+ logger.warn(
3598
+ {
3599
+ circuitId: this.options.id,
3600
+ failures: this.failures,
3601
+ windowMs: this.options.windowMs
3602
+ },
3603
+ "\u{1F534} Circuit breaker OPEN \u2014 blocking operations"
3604
+ );
3605
+ }
3606
+ transitionToHalfOpen() {
3607
+ this.state = "half-open";
3608
+ this.halfOpenTestStartedAt = Date.now();
3609
+ logger.info(
3610
+ { circuitId: this.options.id },
3611
+ "\u{1F7E1} Circuit breaker HALF-OPEN \u2014 testing recovery"
3612
+ );
3613
+ }
3614
+ transitionToClosed() {
3615
+ this.state = "closed";
3616
+ this.failures = 0;
3617
+ this.lastFailureAt = null;
3618
+ this.halfOpenTestStartedAt = null;
3619
+ logger.info(
3620
+ { circuitId: this.options.id },
3621
+ "\u{1F7E2} Circuit breaker CLOSED \u2014 recovery successful"
3622
+ );
3623
+ }
3624
+ shouldTransitionToHalfOpen() {
3625
+ if (this.lastFailureAt === null) {
3626
+ return true;
3627
+ }
3628
+ const elapsed = Date.now() - this.lastFailureAt;
3629
+ return elapsed >= this.options.halfOpenAfterMs;
3630
+ }
3631
+ };
3632
+ var SHARED_BREAKERS = Object.freeze({
3633
+ /**
3634
+ * Breaker for adapter event emission.
3635
+ * Threshold: 5 failures in 60s → open for 30s → half-open test.
3636
+ */
3637
+ adapterEmit: new CircuitBreaker({
3638
+ id: "adapter.emit",
3639
+ failureThreshold: 5,
3640
+ halfOpenAfterMs: 3e4,
3641
+ shouldCountAsFailure: isRetryableError,
3642
+ windowMs: 6e4
3643
+ }),
3644
+ /**
3645
+ * Breaker for file system operations (transcript reading, config loading).
3646
+ * More tolerant: 10 failures in 60s → open for 30s.
3647
+ */
3648
+ fileSystem: new CircuitBreaker({
3649
+ id: "filesystem",
3650
+ failureThreshold: 10,
3651
+ halfOpenAfterMs: 3e4,
3652
+ windowMs: 6e4
3653
+ }),
3654
+ /**
3655
+ * Breaker for HTTP/HTTPS requests.
3656
+ * Stricter: 3 failures in 30s → open for 15s.
3657
+ */
3658
+ httpRequest: new CircuitBreaker({
3659
+ id: "http-request",
3660
+ failureThreshold: 3,
3661
+ halfOpenAfterMs: 15e3,
3662
+ windowMs: 3e4
3663
+ }),
3664
+ /**
3665
+ * Breaker for process detection operations.
3666
+ * Most tolerant: 20 failures in 60s → open for 10s.
3667
+ */
3668
+ processDetection: new CircuitBreaker({
3669
+ id: "process-detection",
3670
+ failureThreshold: 20,
3671
+ halfOpenAfterMs: 1e4,
3672
+ windowMs: 6e4
3673
+ })
3674
+ });
3675
+
3321
3676
  // src/core/engine/event-bus.ts
3322
3677
  import { EventEmitter } from "eventemitter3";
3323
3678
  var EventBus = class {
@@ -4030,7 +4385,7 @@ var UDSServer = class {
4030
4385
  };
4031
4386
 
4032
4387
  // src/core/engine/pipeline.ts
4033
- import { join as join13 } from "path";
4388
+ import { join as join15 } from "path";
4034
4389
  import { z as z4 } from "zod";
4035
4390
 
4036
4391
  // src/adapters/aider.ts
@@ -4137,20 +4492,29 @@ var BaseAdapter = class {
4137
4492
  });
4138
4493
  let published;
4139
4494
  try {
4140
- published = await this.publishEventImplementation(event, {
4141
- cwd: context.cwd,
4142
- env: context.env,
4143
- hookPayload: context.hookPayload,
4144
- pid: context.pid,
4145
- sessionId,
4146
- source: context.source,
4147
- transcriptPath: context.transcriptPath
4495
+ published = await SHARED_BREAKERS.adapterEmit.execute(async () => {
4496
+ return await this.publishEventImplementation(event, {
4497
+ cwd: context.cwd,
4498
+ env: context.env,
4499
+ hookPayload: context.hookPayload,
4500
+ pid: context.pid,
4501
+ sessionId,
4502
+ source: context.source,
4503
+ transcriptPath: context.transcriptPath
4504
+ });
4148
4505
  });
4149
4506
  } catch (error) {
4150
- logger.error(
4151
- { error, eventType: type, adapter: this.name, sessionId },
4152
- "\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
4153
- );
4507
+ if (error instanceof Error && error.name === "CircuitOpenError") {
4508
+ logger.warn(
4509
+ { error, eventType: type, adapter: this.name },
4510
+ "\u{1F4D6} Adapter emit blocked by open circuit \u2014 event dropped"
4511
+ );
4512
+ } else {
4513
+ logger.error(
4514
+ { error, eventType: type, adapter: this.name, sessionId },
4515
+ "\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
4516
+ );
4517
+ }
4154
4518
  published = false;
4155
4519
  }
4156
4520
  if (published) {
@@ -5164,7 +5528,11 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
5164
5528
  return;
5165
5529
  }
5166
5530
  case "SessionEnd": {
5167
- await this.emitStateChange("session.end", sharedData, context);
5531
+ const finalMessage = extractFinalMessageFromPayload(payload);
5532
+ await this.emitStateChange("session.end", {
5533
+ ...sharedData,
5534
+ finalMessage
5535
+ }, context);
5168
5536
  return;
5169
5537
  }
5170
5538
  case "UserPromptSubmit":
@@ -5181,12 +5549,22 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
5181
5549
  return;
5182
5550
  }
5183
5551
  case "PreToolUse": {
5184
- await this.emitStateChange("agent.tool_call", sharedData, context);
5552
+ const toolCallName = extractToolNameFromPayload(payload);
5553
+ await this.emitStateChange("agent.tool_call", {
5554
+ ...sharedData,
5555
+ toolCallName
5556
+ }, context);
5185
5557
  return;
5186
5558
  }
5187
5559
  case "PostToolUse": {
5560
+ const toolCallName = extractToolNameFromPayload(payload);
5561
+ const toolResult = extractToolResultFromPayload(payload);
5188
5562
  const emittedType = isClaudeCodingTool(sharedData.toolName) ? "agent.coding" : "agent.tool_call";
5189
- await this.emitStateChange(emittedType, sharedData, context);
5563
+ await this.emitStateChange(emittedType, {
5564
+ ...sharedData,
5565
+ toolCallName,
5566
+ toolResult
5567
+ }, context);
5190
5568
  return;
5191
5569
  }
5192
5570
  case "PostToolUseFailure":
@@ -5388,21 +5766,50 @@ function extractClaudeTranscriptObservations(payload, transcriptPath) {
5388
5766
  };
5389
5767
  const observations = [];
5390
5768
  if (contentParts.some((part) => part.type === "thinking")) {
5769
+ const thinkingParts = contentParts.filter((part) => part.type === "thinking");
5770
+ const thinkingText = thinkingParts.map((part) => {
5771
+ const text = part.text;
5772
+ return typeof text === "string" ? text : void 0;
5773
+ }).filter((text) => text !== void 0).join("\n");
5391
5774
  observations.push({
5392
5775
  context: sharedContext,
5393
- data: sharedData,
5776
+ data: {
5777
+ ...sharedData,
5778
+ thinkingContent: thinkingText.length > 0 ? thinkingText : void 0
5779
+ },
5394
5780
  type: "agent.thinking"
5395
5781
  });
5396
5782
  }
5397
5783
  if (contentParts.some(
5398
5784
  (part) => part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0
5399
5785
  )) {
5786
+ const messageTexts = contentParts.filter((part) => part.type === "text").map((part) => part.text).filter((text) => text.trim().length > 0);
5787
+ const messageContent = messageTexts.join("\n");
5400
5788
  observations.push({
5401
5789
  context: sharedContext,
5402
- data: sharedData,
5790
+ data: {
5791
+ ...sharedData,
5792
+ messageContent: messageContent.length > 0 ? messageContent : void 0
5793
+ },
5403
5794
  type: "agent.streaming"
5404
5795
  });
5405
5796
  }
5797
+ const toolUseParts = contentParts.filter(
5798
+ (part) => part.type === "tool_use" || part.type === "toolUse"
5799
+ );
5800
+ if (toolUseParts.length > 0) {
5801
+ const toolName = getString(toolUseParts[0], "name") ?? getString(toolUseParts[0], "tool");
5802
+ if (toolName) {
5803
+ observations.push({
5804
+ context: sharedContext,
5805
+ data: {
5806
+ ...sharedData,
5807
+ toolCallName: toolName
5808
+ },
5809
+ type: "agent.tool_call"
5810
+ });
5811
+ }
5812
+ }
5406
5813
  return observations;
5407
5814
  }
5408
5815
  function extractClaudeContentParts(payload) {
@@ -5525,6 +5932,51 @@ function getString(payload, key) {
5525
5932
  const value = payload[key];
5526
5933
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
5527
5934
  }
5935
+ function extractToolNameFromPayload(payload) {
5936
+ const directToolName = getString(payload, "tool_name") ?? getString(payload, "toolName");
5937
+ if (directToolName) {
5938
+ return directToolName;
5939
+ }
5940
+ const toolUse = getRecord(payload.tool_use) ?? getRecord(payload.toolUse);
5941
+ if (toolUse) {
5942
+ return getString(toolUse, "name") ?? getString(toolUse, "tool");
5943
+ }
5944
+ const toolInput = getRecord(payload.tool_input) ?? getRecord(payload.toolInput);
5945
+ if (toolInput) {
5946
+ return getString(toolInput, "tool_name") ?? getString(toolInput, "type");
5947
+ }
5948
+ return void 0;
5949
+ }
5950
+ function extractToolResultFromPayload(payload) {
5951
+ const directResult = getString(payload, "result") ?? getString(payload, "output");
5952
+ if (directResult) {
5953
+ return directResult;
5954
+ }
5955
+ const toolResult = getRecord(payload.tool_result) ?? getRecord(payload.toolResult);
5956
+ if (toolResult) {
5957
+ return getString(toolResult, "content") ?? getString(toolResult, "output");
5958
+ }
5959
+ const errorField = getString(payload, "error") ?? getString(payload, "error_message");
5960
+ if (errorField) {
5961
+ return errorField;
5962
+ }
5963
+ return void 0;
5964
+ }
5965
+ function extractFinalMessageFromPayload(payload) {
5966
+ const directMessage = getString(payload, "final_message") ?? getString(payload, "finalMessage") ?? getString(payload, "summary") ?? getString(payload, "completion_message");
5967
+ if (directMessage) {
5968
+ return directMessage;
5969
+ }
5970
+ const result = getString(payload, "result") ?? getString(payload, "output") ?? getString(payload, "message");
5971
+ if (result) {
5972
+ return result;
5973
+ }
5974
+ const stats = getRecord(payload.stats);
5975
+ if (stats) {
5976
+ return getString(stats, "summary") ?? getString(stats, "completion_summary");
5977
+ }
5978
+ return void 0;
5979
+ }
5528
5980
 
5529
5981
  // src/adapters/copilot-cli.ts
5530
5982
  import { execFile as execFileCallback5 } from "child_process";
@@ -10498,7 +10950,11 @@ var OpenCodeAdapter = class extends BaseAdapter {
10498
10950
  return;
10499
10951
  }
10500
10952
  case "session.deleted": {
10501
- await this.emitStateChange("session.end", sharedData, context);
10953
+ const finalMessage = extractOpenCodeFinalMessage(payload);
10954
+ await this.emitStateChange("session.end", {
10955
+ ...sharedData,
10956
+ finalMessage
10957
+ }, context);
10502
10958
  return;
10503
10959
  }
10504
10960
  case "session.error": {
@@ -10523,12 +10979,22 @@ var OpenCodeAdapter = class extends BaseAdapter {
10523
10979
  return;
10524
10980
  }
10525
10981
  case "tool.execute.before": {
10526
- await this.emitStateChange("agent.tool_call", sharedData, context);
10982
+ const toolCallName = extractOpenCodeToolName(payload);
10983
+ await this.emitStateChange("agent.tool_call", {
10984
+ ...sharedData,
10985
+ toolCallName
10986
+ }, context);
10527
10987
  return;
10528
10988
  }
10529
10989
  case "tool.execute.after": {
10990
+ const toolCallName = extractOpenCodeToolName(payload);
10991
+ const toolResult = extractOpenCodeToolResult(payload);
10530
10992
  const emittedType = isOpenCodeCodingTool(sharedData.toolName) ? "agent.coding" : "agent.tool_call";
10531
- await this.emitStateChange(emittedType, sharedData, context);
10993
+ await this.emitStateChange(emittedType, {
10994
+ ...sharedData,
10995
+ toolCallName,
10996
+ toolResult
10997
+ }, context);
10532
10998
  return;
10533
10999
  }
10534
11000
  default: {
@@ -10695,6 +11161,535 @@ function getString10(payload, key) {
10695
11161
  const value = payload[key];
10696
11162
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
10697
11163
  }
11164
+ function extractOpenCodeFinalMessage(payload) {
11165
+ const directMessage = getString10(payload, "final_message") ?? getString10(payload, "finalMessage") ?? getString10(payload, "summary") ?? getString10(payload, "completion_message");
11166
+ if (directMessage) {
11167
+ return directMessage;
11168
+ }
11169
+ const result = getString10(payload, "result") ?? getString10(payload, "output") ?? getString10(getRecord9(payload.properties), "result");
11170
+ if (result) {
11171
+ return result;
11172
+ }
11173
+ return void 0;
11174
+ }
11175
+ function extractOpenCodeToolResult(payload) {
11176
+ const directResult = getString10(payload, "result") ?? getString10(payload, "output") ?? getString10(payload, "toolResult");
11177
+ if (directResult) {
11178
+ return directResult;
11179
+ }
11180
+ const toolResult = getRecord9(payload.tool_result) ?? getRecord9(payload.toolResult);
11181
+ if (toolResult) {
11182
+ return getString10(toolResult, "content") ?? getString10(toolResult, "output");
11183
+ }
11184
+ const props = getRecord9(payload.properties);
11185
+ if (props) {
11186
+ const nestedResult = getRecord9(props.tool_result) ?? getRecord9(props.toolResult);
11187
+ if (nestedResult) {
11188
+ return getString10(nestedResult, "content") ?? getString10(nestedResult, "output");
11189
+ }
11190
+ }
11191
+ return void 0;
11192
+ }
11193
+
11194
+ // src/adapters/pi.ts
11195
+ import { execFile as execFile14 } from "child_process";
11196
+ import { join as join13 } from "path";
11197
+ import { promisify as promisify14 } from "util";
11198
+ var execFileAsync = promisify14(execFile14);
11199
+ var PiAdapter = class extends BaseAdapter {
11200
+ displayName = "Pi (MiniMax)";
11201
+ name = "pi";
11202
+ strategies = [
11203
+ "process-detect",
11204
+ "api-client",
11205
+ "log-watch"
11206
+ ];
11207
+ apiPort = 7890;
11208
+ logPath;
11209
+ poller = null;
11210
+ activePiSessions = /* @__PURE__ */ new Map();
11211
+ lastCheckedTime = 0;
11212
+ constructor(options) {
11213
+ super(options);
11214
+ this.logPath = join13(
11215
+ options.homeDirectory ?? process.env.HOME ?? "",
11216
+ ".pi",
11217
+ "agent.log"
11218
+ );
11219
+ }
11220
+ start() {
11221
+ if (this.getStatus().running) {
11222
+ return Promise.resolve();
11223
+ }
11224
+ this.setRunning(true);
11225
+ this.startPolling();
11226
+ logger.info({ adapter: this.name }, "Pi adapter started");
11227
+ return Promise.resolve();
11228
+ }
11229
+ stop() {
11230
+ if (this.poller !== null) {
11231
+ clearInterval(this.poller);
11232
+ this.poller = null;
11233
+ }
11234
+ this.setRunning(false);
11235
+ logger.info({ adapter: this.name }, "Pi adapter stopped");
11236
+ return Promise.resolve();
11237
+ }
11238
+ async handleHook(payload) {
11239
+ const normalized = this.parseNormalizedHookPayload(payload);
11240
+ if (normalized === null) {
11241
+ return;
11242
+ }
11243
+ const context = {
11244
+ cwd: normalized.cwd,
11245
+ pid: normalized.pid,
11246
+ sessionId: normalized.sessionId,
11247
+ source: "pi-hook"
11248
+ };
11249
+ const eventType = this.mapEventType(normalized.type ?? "");
11250
+ const eventData = this.buildEventData(eventType, normalized);
11251
+ await this.emit(eventType, eventData, context);
11252
+ }
11253
+ startPolling() {
11254
+ this.poller = setInterval(() => {
11255
+ void this.pollPiActivity();
11256
+ }, 2e3);
11257
+ }
11258
+ async pollPiActivity() {
11259
+ const running = await this.detectPiInstance();
11260
+ if (!running) {
11261
+ for (const [sessionId, activity] of this.activePiSessions) {
11262
+ if (activity.state !== "idle") {
11263
+ activity.state = "idle";
11264
+ await this.emitIdle(sessionId);
11265
+ }
11266
+ }
11267
+ return;
11268
+ }
11269
+ try {
11270
+ const response = await fetch(
11271
+ `http://127.0.0.1:${this.apiPort}/api/status`,
11272
+ {
11273
+ signal: AbortSignal.timeout(500)
11274
+ }
11275
+ );
11276
+ if (response.ok) {
11277
+ const data = await response.json();
11278
+ await this.processPiApiResponse(data);
11279
+ }
11280
+ } catch {
11281
+ await this.checkMiniMaxApi();
11282
+ }
11283
+ }
11284
+ async detectPiInstance() {
11285
+ try {
11286
+ const result = await execFileAsync("pgrep", ["-l", "pi|minimax"]);
11287
+ if (result.stdout.includes("pi") || result.stdout.includes("minimax")) {
11288
+ return true;
11289
+ }
11290
+ } catch {
11291
+ }
11292
+ try {
11293
+ const response = await fetch(
11294
+ `http://127.0.0.1:${this.apiPort}/health`,
11295
+ {
11296
+ signal: AbortSignal.timeout(200)
11297
+ }
11298
+ );
11299
+ if (response.ok) {
11300
+ return true;
11301
+ }
11302
+ } catch {
11303
+ }
11304
+ return false;
11305
+ }
11306
+ async checkMiniMaxApi() {
11307
+ try {
11308
+ const response = await fetch("http://127.0.0.1:3000/api/agent/status", {
11309
+ signal: AbortSignal.timeout(500)
11310
+ });
11311
+ if (response.ok) {
11312
+ const data = await response.json();
11313
+ await this.processPiApiResponse(data);
11314
+ }
11315
+ } catch {
11316
+ }
11317
+ }
11318
+ async processPiApiResponse(data) {
11319
+ const rawSession = data.sessionId ?? data.project ?? "default";
11320
+ const sessionId = `pi:${rawSession.replace(/[^a-zA-Z0-9-_]/g, "-")}`;
11321
+ let activity = this.activePiSessions.get(sessionId);
11322
+ if (!activity) {
11323
+ activity = { sessionId, state: "idle" };
11324
+ this.activePiSessions.set(sessionId, activity);
11325
+ await this.emitSessionStart(sessionId, data);
11326
+ }
11327
+ const rawState = data.state ?? "idle";
11328
+ const state = rawState;
11329
+ if (state !== activity.state) {
11330
+ switch (state) {
11331
+ case "thinking": {
11332
+ const rawThinking = data.thinking;
11333
+ if (rawThinking) {
11334
+ await this.emitThinking(sessionId, rawThinking);
11335
+ }
11336
+ break;
11337
+ }
11338
+ case "tool": {
11339
+ const rawFilePath = data.filePath;
11340
+ const rawCommand = data.command;
11341
+ const rawToolName = data.toolName ?? "unknown";
11342
+ await this.emitToolCall(
11343
+ sessionId,
11344
+ {
11345
+ filePath: rawFilePath ?? "",
11346
+ command: rawCommand ?? ""
11347
+ },
11348
+ rawToolName
11349
+ );
11350
+ break;
11351
+ }
11352
+ case "output": {
11353
+ const rawOutput = data.output;
11354
+ if (rawOutput) {
11355
+ await this.emitOutput(sessionId, rawOutput);
11356
+ }
11357
+ break;
11358
+ }
11359
+ case "error": {
11360
+ const rawError = data.error ?? "Unknown error";
11361
+ await this.emitError(sessionId, rawError);
11362
+ break;
11363
+ }
11364
+ case "idle":
11365
+ await this.emitIdle(sessionId);
11366
+ break;
11367
+ }
11368
+ activity.state = state;
11369
+ }
11370
+ }
11371
+ async emitSessionStart(sessionId, data) {
11372
+ const rawProject = data.project ?? "pi-project";
11373
+ const rawModel = data.model ?? "minimax/moonshot";
11374
+ const eventData = {
11375
+ state: "session.start",
11376
+ project: rawProject,
11377
+ model: rawModel,
11378
+ raw: data
11379
+ };
11380
+ await this.emit("session.start", eventData, { sessionId });
11381
+ }
11382
+ async emitThinking(sessionId, content) {
11383
+ if (!content) return;
11384
+ const eventData = {
11385
+ state: "agent.thinking",
11386
+ thinkingContent: content
11387
+ };
11388
+ await this.emit("agent.thinking", eventData, { sessionId });
11389
+ }
11390
+ async emitToolCall(sessionId, toolInput, toolName) {
11391
+ const eventData = {
11392
+ state: "agent.tool_call",
11393
+ toolCallName: toolName,
11394
+ toolInput,
11395
+ activeFile: toolInput.filePath
11396
+ };
11397
+ await this.emit("agent.tool_call", eventData, { sessionId });
11398
+ }
11399
+ async emitOutput(sessionId, content) {
11400
+ if (!content) return;
11401
+ const eventData = {
11402
+ state: "agent.streaming",
11403
+ messageContent: content
11404
+ };
11405
+ await this.emit("agent.streaming", eventData, { sessionId });
11406
+ }
11407
+ async emitError(sessionId, errorMessage) {
11408
+ const eventData = {
11409
+ state: "agent.error",
11410
+ errorMessage,
11411
+ errorType: "api_error"
11412
+ };
11413
+ await this.emit("agent.error", eventData, { sessionId });
11414
+ }
11415
+ async emitIdle(sessionId) {
11416
+ const eventData = {
11417
+ state: "agent.idle"
11418
+ };
11419
+ await this.emit("agent.idle", eventData, { sessionId });
11420
+ }
11421
+ mapEventType(type) {
11422
+ const mapping = {
11423
+ "session.start": "session.start",
11424
+ "session.end": "session.end",
11425
+ "task.start": "task.start",
11426
+ "task.complete": "task.complete",
11427
+ thinking: "agent.thinking",
11428
+ tool: "agent.tool_call",
11429
+ coding: "agent.coding",
11430
+ output: "agent.streaming",
11431
+ message: "agent.streaming",
11432
+ ask: "agent.asking_user",
11433
+ error: "agent.error",
11434
+ idle: "agent.idle",
11435
+ compact: "agent.compact"
11436
+ };
11437
+ return mapping[type] ?? "agent.streaming";
11438
+ }
11439
+ buildEventData(eventType, payload) {
11440
+ const data = payload.data ?? {};
11441
+ return {
11442
+ state: eventType,
11443
+ project: data.project,
11444
+ activeFile: data.activeFile,
11445
+ model: data.model,
11446
+ toolInput: data.toolInput,
11447
+ toolCallName: data.toolCallName,
11448
+ thinkingContent: data.thinkingContent,
11449
+ messageContent: data.messageContent,
11450
+ finalMessage: data.finalMessage,
11451
+ toolResult: data.toolResult,
11452
+ errorMessage: data.errorMessage,
11453
+ errorType: data.errorType,
11454
+ raw: data.raw
11455
+ };
11456
+ }
11457
+ };
11458
+
11459
+ // src/adapters/zed.ts
11460
+ import { readFile as readFile12 } from "fs/promises";
11461
+ import { basename as basename11, join as join14 } from "path";
11462
+ var ZedAdapter = class extends BaseAdapter {
11463
+ displayName = "Zed AI";
11464
+ name = "zed";
11465
+ strategies = [
11466
+ "process-detect",
11467
+ "api-client"
11468
+ ];
11469
+ logPaths;
11470
+ apiPort = 9876;
11471
+ pollIntervalMs;
11472
+ poller = null;
11473
+ lastEventTime = 0;
11474
+ activeZedSessions = /* @__PURE__ */ new Map();
11475
+ constructor(options) {
11476
+ super(options);
11477
+ this.pollIntervalMs = 2e3;
11478
+ this.logPaths = [
11479
+ join14(options.homeDirectory ?? process.env.HOME ?? "", ".config", "zed", "logs", "agent.log"),
11480
+ "/tmp/zed-agent.log"
11481
+ ];
11482
+ }
11483
+ start() {
11484
+ if (this.getStatus().running) {
11485
+ return Promise.resolve();
11486
+ }
11487
+ this.setRunning(true);
11488
+ this.startPolling();
11489
+ logger.info({ adapter: this.name }, "Zed adapter started");
11490
+ return Promise.resolve();
11491
+ }
11492
+ stop() {
11493
+ if (this.poller !== null) {
11494
+ clearInterval(this.poller);
11495
+ this.poller = null;
11496
+ }
11497
+ this.setRunning(false);
11498
+ logger.info({ adapter: this.name }, "Zed adapter stopped");
11499
+ return Promise.resolve();
11500
+ }
11501
+ async handleHook(payload) {
11502
+ const normalized = this.parseNormalizedHookPayload(payload);
11503
+ if (normalized === null) {
11504
+ return;
11505
+ }
11506
+ const context = {
11507
+ cwd: normalized.cwd,
11508
+ pid: normalized.pid,
11509
+ sessionId: normalized.sessionId,
11510
+ source: "zed-hook"
11511
+ };
11512
+ const eventType = this.mapEventType(normalized.type ?? "");
11513
+ const eventData = this.buildEventData(eventType, normalized);
11514
+ await this.emit(eventType, eventData, context);
11515
+ }
11516
+ startPolling() {
11517
+ this.poller = setInterval(() => {
11518
+ void this.pollZedStatus();
11519
+ }, this.pollIntervalMs);
11520
+ }
11521
+ async pollZedStatus() {
11522
+ try {
11523
+ const response = await fetch(`http://127.0.0.1:${this.apiPort}/api/agent/status`, {
11524
+ signal: AbortSignal.timeout(1e3)
11525
+ });
11526
+ if (response.ok) {
11527
+ const data = await response.json();
11528
+ if (data.sessionId && typeof data.sessionId === "string") {
11529
+ const sessionId = `zed:${data.sessionId}`;
11530
+ if (!this.activeZedSessions.has(sessionId)) {
11531
+ this.activeZedSessions.set(sessionId, sessionId);
11532
+ await this.emitSessionStart(sessionId, data);
11533
+ }
11534
+ if (data.state === "thinking" && this.lastEventTime < Date.now() - 5e3) {
11535
+ const rawThinking = data.thinking;
11536
+ if (rawThinking) {
11537
+ await this.emitThinking(sessionId, rawThinking);
11538
+ }
11539
+ } else if (data.state === "tool" && data.toolName) {
11540
+ const rawFilePath = data.filePath;
11541
+ const rawCommand = data.command;
11542
+ const rawToolName = data.toolName ?? "unknown";
11543
+ await this.emitToolCall(
11544
+ sessionId,
11545
+ {
11546
+ filePath: rawFilePath ?? "",
11547
+ command: rawCommand ?? ""
11548
+ },
11549
+ rawToolName
11550
+ );
11551
+ } else if (data.state === "idle") {
11552
+ await this.emitIdle(sessionId);
11553
+ }
11554
+ }
11555
+ }
11556
+ } catch {
11557
+ await this.checkLogFiles();
11558
+ }
11559
+ }
11560
+ async checkLogFiles() {
11561
+ for (const logPath of this.logPaths) {
11562
+ try {
11563
+ const content = await readFile12(logPath, "utf8");
11564
+ await this.parseLogContent(content);
11565
+ } catch {
11566
+ }
11567
+ }
11568
+ }
11569
+ async parseLogContent(content) {
11570
+ const lines = content.split("\n").filter((line) => line.trim());
11571
+ for (const line of lines) {
11572
+ if (this.lastEventTime > 0 && this.lastEventTime >= Date.now() - 2e3) {
11573
+ continue;
11574
+ }
11575
+ const event = this.extractZedEventFromLog(line);
11576
+ if (event) {
11577
+ await this.handleHook(event);
11578
+ this.lastEventTime = Date.now();
11579
+ }
11580
+ }
11581
+ }
11582
+ extractZedEventFromLog(line) {
11583
+ try {
11584
+ const parsed = JSON.parse(line);
11585
+ if (!parsed.type || typeof parsed.type !== "string") {
11586
+ return null;
11587
+ }
11588
+ const rawSessionId = parsed.sessionId;
11589
+ const rawWorkspace = parsed.workspace;
11590
+ const sessionId = rawSessionId ? rawSessionId : rawWorkspace ? `zed:${basename11(rawWorkspace)}` : `zed:${Date.now()}`;
11591
+ const rawCwd = parsed.cwd ?? rawWorkspace;
11592
+ return {
11593
+ type: parsed.type,
11594
+ sessionId,
11595
+ cwd: rawCwd,
11596
+ data: {
11597
+ project: parsed.project ?? rawWorkspace ? basename11(String(rawWorkspace)) : void 0,
11598
+ model: parsed.model,
11599
+ state: parsed.type,
11600
+ thinkingContent: parsed.thinking,
11601
+ toolCallName: parsed.toolName,
11602
+ toolInput: parsed.toolInput,
11603
+ messageContent: parsed.message ?? parsed.output,
11604
+ errorMessage: parsed.error,
11605
+ raw: parsed
11606
+ }
11607
+ };
11608
+ } catch {
11609
+ if (line.includes("[zed:agent]")) {
11610
+ const cleaned = line.replace(/^\[.*?\] \[.*?\] /, "");
11611
+ if (cleaned.includes("Thinking:")) {
11612
+ return { type: "thinking", thinkingContent: cleaned.replace("Thinking:", "").trim() };
11613
+ }
11614
+ if (cleaned.includes("Executing tool:")) {
11615
+ return { type: "tool", toolCallName: cleaned.replace("Executing tool:", "").trim() };
11616
+ }
11617
+ if (cleaned.includes("Error:")) {
11618
+ return { type: "error", errorMessage: cleaned.replace("Error:", "").trim() };
11619
+ }
11620
+ }
11621
+ return null;
11622
+ }
11623
+ }
11624
+ async emitSessionStart(sessionId, _data) {
11625
+ const eventData = {
11626
+ state: "session.start",
11627
+ project: sessionId.split(":")[1] ?? "unknown"
11628
+ };
11629
+ await this.emit("session.start", eventData, { sessionId });
11630
+ }
11631
+ async emitThinking(sessionId, content) {
11632
+ if (!content) return;
11633
+ const eventData = {
11634
+ state: "agent.thinking",
11635
+ thinkingContent: content
11636
+ };
11637
+ await this.emit("agent.thinking", eventData, { sessionId });
11638
+ this.lastEventTime = Date.now();
11639
+ }
11640
+ async emitToolCall(sessionId, toolInput, toolName) {
11641
+ const eventData = {
11642
+ state: "agent.tool_call",
11643
+ toolCallName: toolName,
11644
+ toolInput,
11645
+ activeFile: toolInput.filePath
11646
+ };
11647
+ await this.emit("agent.tool_call", eventData, { sessionId });
11648
+ this.lastEventTime = Date.now();
11649
+ }
11650
+ async emitIdle(sessionId) {
11651
+ const eventData = {
11652
+ state: "agent.idle"
11653
+ };
11654
+ await this.emit("agent.idle", eventData, { sessionId });
11655
+ }
11656
+ mapEventType(type) {
11657
+ const mapping = {
11658
+ "session.start": "session.start",
11659
+ "session.end": "session.end",
11660
+ "task.start": "task.start",
11661
+ "task.complete": "task.complete",
11662
+ thinking: "agent.thinking",
11663
+ tool: "agent.tool_call",
11664
+ coding: "agent.coding",
11665
+ output: "agent.streaming",
11666
+ message: "agent.streaming",
11667
+ ask: "agent.asking_user",
11668
+ error: "agent.error",
11669
+ idle: "agent.idle",
11670
+ compact: "agent.compact"
11671
+ };
11672
+ return mapping[type] ?? "agent.streaming";
11673
+ }
11674
+ buildEventData(eventType, payload) {
11675
+ const data = payload.data ?? {};
11676
+ return {
11677
+ state: eventType,
11678
+ project: data.project,
11679
+ activeFile: data.activeFile,
11680
+ model: data.model,
11681
+ toolInput: data.toolInput,
11682
+ toolCallName: data.toolCallName,
11683
+ thinkingContent: data.thinkingContent,
11684
+ messageContent: data.messageContent,
11685
+ finalMessage: data.finalMessage,
11686
+ toolResult: data.toolResult,
11687
+ errorMessage: data.errorMessage,
11688
+ errorType: data.errorType,
11689
+ raw: data.raw
11690
+ };
11691
+ }
11692
+ };
10698
11693
 
10699
11694
  // src/adapters/registry.ts
10700
11695
  var AdapterRegistry = class {
@@ -10779,7 +11774,9 @@ function createDefaultAdapters(options) {
10779
11774
  new KiloAdapter(options),
10780
11775
  new CodexAdapter(options),
10781
11776
  new OpenClawAdapter(options),
10782
- new OpenCodeAdapter(options)
11777
+ new OpenCodeAdapter(options),
11778
+ new PiAdapter(options),
11779
+ new ZedAdapter(options)
10783
11780
  ];
10784
11781
  }
10785
11782
 
@@ -10800,7 +11797,7 @@ function getSocketPath(aisnitchHomePath) {
10800
11797
  if (process.platform === "win32") {
10801
11798
  return "\\\\.\\pipe\\aisnitch.sock";
10802
11799
  }
10803
- return join13(aisnitchHomePath, "aisnitch.sock");
11800
+ return join15(aisnitchHomePath, "aisnitch.sock");
10804
11801
  }
10805
11802
  var Pipeline = class {
10806
11803
  eventBus = new EventBus();
@@ -11005,6 +12002,30 @@ var Pipeline = class {
11005
12002
  getEventBus() {
11006
12003
  return this.eventBus;
11007
12004
  }
12005
+ /**
12006
+ * Returns the adapter registry for graceful shutdown coordination.
12007
+ */
12008
+ getAdapterRegistry() {
12009
+ return this.adapterRegistry ?? void 0;
12010
+ }
12011
+ /**
12012
+ * Returns the HTTP receiver for graceful shutdown coordination.
12013
+ */
12014
+ getHttpReceiver() {
12015
+ return this.httpReceiver;
12016
+ }
12017
+ /**
12018
+ * Returns the UDS server for graceful shutdown coordination.
12019
+ */
12020
+ getUdsServer() {
12021
+ return this.udsServer;
12022
+ }
12023
+ /**
12024
+ * Returns the WebSocket server for graceful shutdown coordination.
12025
+ */
12026
+ getWsServer() {
12027
+ return this.wsServer;
12028
+ }
11008
12029
  getHealthSnapshot() {
11009
12030
  const status = this.getStatus();
11010
12031
  return {
@@ -11085,6 +12106,116 @@ var Pipeline = class {
11085
12106
  }
11086
12107
  };
11087
12108
 
12109
+ // src/core/timeout.ts
12110
+ var DEFAULT_TIMEOUTS = Object.freeze({
12111
+ /**
12112
+ * File read/write operations (JSONL transcripts, config files).
12113
+ * Default: 5 seconds
12114
+ */
12115
+ fileOperation: 5e3,
12116
+ /**
12117
+ * HTTP requests to health endpoint or external APIs.
12118
+ * Default: 30 seconds
12119
+ */
12120
+ httpRequest: 3e4,
12121
+ /**
12122
+ * Process detection commands (`pgrep`, `ps aux`).
12123
+ * Default: 3 seconds
12124
+ */
12125
+ processDetection: 3e3,
12126
+ /**
12127
+ * Adapter startup (file watchers, hook bridges, pollers).
12128
+ * Default: 10 seconds
12129
+ */
12130
+ adapterStartup: 1e4,
12131
+ /**
12132
+ * Adapter shutdown (graceful cleanup, watcher close).
12133
+ * Default: 5 seconds — after this, resources are force-closed
12134
+ */
12135
+ adapterShutdown: 5e3,
12136
+ /**
12137
+ * Daemon graceful shutdown (stop all components in order).
12138
+ * Default: 30 seconds
12139
+ */
12140
+ daemonShutdown: 3e4,
12141
+ /**
12142
+ * WebSocket connection establishment.
12143
+ * Default: 10 seconds
12144
+ */
12145
+ wsConnection: 1e4,
12146
+ /**
12147
+ * Overall pipeline start (all components).
12148
+ * Default: 15 seconds
12149
+ */
12150
+ pipelineStartup: 15e3
12151
+ });
12152
+
12153
+ // src/core/graceful-shutdown.ts
12154
+ async function withShutdownTimeout(fn, timeoutMs, component) {
12155
+ if (timeoutMs <= 0) {
12156
+ await fn();
12157
+ return;
12158
+ }
12159
+ const timeoutPromise = new Promise((resolve2) => {
12160
+ setTimeout(() => {
12161
+ resolve2("timed_out");
12162
+ }, timeoutMs).unref();
12163
+ });
12164
+ const result = await Promise.race([
12165
+ fn().then(() => "completed"),
12166
+ timeoutPromise
12167
+ ]);
12168
+ if (result === "timed_out") {
12169
+ logger.warn(
12170
+ { component, timeoutMs },
12171
+ `Graceful shutdown exceeded ${timeoutMs}ms timeout \u2014 forcing through`
12172
+ );
12173
+ }
12174
+ }
12175
+ async function shutdownInOrder(components, timeouts, label) {
12176
+ const getTimeout = (key) => {
12177
+ return timeouts[key] ?? DEFAULT_TIMEOUTS.daemonShutdown;
12178
+ };
12179
+ const stopSafely = async (key, fn) => {
12180
+ const timeoutMs = getTimeout(key);
12181
+ try {
12182
+ await withShutdownTimeout(fn, timeoutMs, `${label}.${key}`);
12183
+ } catch (error) {
12184
+ logger.warn(
12185
+ { error, key, label },
12186
+ `Error during shutdown of ${key} \u2014 continuing with remaining components`
12187
+ );
12188
+ }
12189
+ };
12190
+ if (components.cleanupFns) {
12191
+ for (const cleanupFn of components.cleanupFns) {
12192
+ try {
12193
+ await withShutdownTimeout(
12194
+ async () => {
12195
+ const result = cleanupFn();
12196
+ if (result instanceof Promise) {
12197
+ await result;
12198
+ }
12199
+ },
12200
+ 1e3,
12201
+ `${label}.cleanup`
12202
+ );
12203
+ } catch (error) {
12204
+ logger.warn({ error, label }, "Cleanup function failed");
12205
+ }
12206
+ }
12207
+ }
12208
+ if (components.eventBus) {
12209
+ components.eventBus.unsubscribeAll();
12210
+ }
12211
+ await stopSafely("wsServer", () => components.wsServer.stop());
12212
+ await stopSafely("udsServer", () => components.udsServer.stop());
12213
+ await stopSafely("httpReceiver", () => components.httpReceiver.stop());
12214
+ if (components.adapterRegistry) {
12215
+ await stopSafely("adapterRegistry", () => components.adapterRegistry.stopAll());
12216
+ }
12217
+ }
12218
+
11088
12219
  // src/tui/index.tsx
11089
12220
  import { render } from "ink";
11090
12221
  import { withFullScreen } from "fullscreen-ink";
@@ -11288,9 +12419,11 @@ var TOOL_COLORS = {
11288
12419
  "openhands": "#facc15",
11289
12420
  "openclaw": "#ef4444",
11290
12421
  "opencode": "#10b981",
12422
+ "pi": "#1db954",
11291
12423
  "qwen-code": "#22c55e",
11292
12424
  "unknown": "#94a3b8",
11293
- "windsurf": "#a855f7"
12425
+ "windsurf": "#a855f7",
12426
+ "zed": "#e85d04"
11294
12427
  };
11295
12428
  var EVENT_COLORS = {
11296
12429
  "agent.asking_user": "#ef4444",
@@ -13044,10 +14177,10 @@ async function renderManagedTui(options) {
13044
14177
 
13045
14178
  // src/cli/pid.ts
13046
14179
  import { constants as constants3 } from "fs";
13047
- import { mkdir as mkdir3, readFile as readFile12, rm as rm3, stat as stat10, writeFile as writeFile3, access as access3 } from "fs/promises";
14180
+ import { mkdir as mkdir3, readFile as readFile13, rm as rm3, stat as stat10, writeFile as writeFile3, access as access3 } from "fs/promises";
13048
14181
  import { createConnection as createConnection2 } from "net";
13049
14182
  import { homedir as homedir4 } from "os";
13050
- import { dirname as dirname7, join as join14 } from "path";
14183
+ import { dirname as dirname7, join as join16 } from "path";
13051
14184
  import { z as z5 } from "zod";
13052
14185
  var DaemonStateSchema = z5.strictObject({
13053
14186
  pid: z5.number().int().positive(),
@@ -13062,7 +14195,7 @@ function getDefaultSocketPath(options) {
13062
14195
  if (process.platform === "win32") {
13063
14196
  return "\\\\.\\pipe\\aisnitch.sock";
13064
14197
  }
13065
- return join14(getAISnitchHomePath(options), "aisnitch.sock");
14198
+ return join16(getAISnitchHomePath(options), "aisnitch.sock");
13066
14199
  }
13067
14200
  async function cleanupSocketPathIfStale(socketPath) {
13068
14201
  if (process.platform === "win32") {
@@ -13097,16 +14230,16 @@ async function cleanupSocketPathIfStale(socketPath) {
13097
14230
  return true;
13098
14231
  }
13099
14232
  function getPidFilePath(options = {}) {
13100
- return join14(getAISnitchHomePath(options), "aisnitch.pid");
14233
+ return join16(getAISnitchHomePath(options), "aisnitch.pid");
13101
14234
  }
13102
14235
  function getDaemonStatePath(options = {}) {
13103
- return join14(getAISnitchHomePath(options), "daemon-state.json");
14236
+ return join16(getAISnitchHomePath(options), "daemon-state.json");
13104
14237
  }
13105
14238
  function getDaemonLogPath(options = {}) {
13106
- return join14(getAISnitchHomePath(options), "daemon.log");
14239
+ return join16(getAISnitchHomePath(options), "daemon.log");
13107
14240
  }
13108
14241
  function getLaunchAgentPath(options = {}) {
13109
- return join14(
14242
+ return join16(
13110
14243
  options.launchAgentHomeDirectory ?? homedir4(),
13111
14244
  "Library",
13112
14245
  "LaunchAgents",
@@ -13122,7 +14255,7 @@ async function writePid(pid, options = {}) {
13122
14255
  }
13123
14256
  async function readPid(options = {}) {
13124
14257
  try {
13125
- const rawPid = await readFile12(getPidFilePath(options), "utf8");
14258
+ const rawPid = await readFile13(getPidFilePath(options), "utf8");
13126
14259
  const parsedPid = Number.parseInt(rawPid.trim(), 10);
13127
14260
  if (!Number.isInteger(parsedPid) || parsedPid <= 0) {
13128
14261
  throw new Error("Invalid PID file contents.");
@@ -13152,7 +14285,7 @@ async function writeDaemonState(state, options = {}) {
13152
14285
  }
13153
14286
  async function readDaemonState(options = {}) {
13154
14287
  try {
13155
- const rawJson = await readFile12(getDaemonStatePath(options), "utf8");
14288
+ const rawJson = await readFile13(getDaemonStatePath(options), "utf8");
13156
14289
  const parsedJson = JSON.parse(rawJson);
13157
14290
  return DaemonStateSchema.parse(parsedJson);
13158
14291
  } catch (error) {
@@ -13220,9 +14353,9 @@ function getEffectiveCliConfigPath(options = {}) {
13220
14353
 
13221
14354
  // src/cli/auto-update.ts
13222
14355
  import { spawn as spawnChildProcess } from "child_process";
13223
- import { mkdir as mkdir4, readFile as readFile13, writeFile as writeFile4 } from "fs/promises";
14356
+ import { mkdir as mkdir4, readFile as readFile14, writeFile as writeFile4 } from "fs/promises";
13224
14357
  import { realpath } from "fs/promises";
13225
- import { join as join15 } from "path";
14358
+ import { join as join17 } from "path";
13226
14359
  var AUTO_UPDATE_STATE_FILE = "auto-update.json";
13227
14360
  var AUTO_UPDATE_LOG_FILE = "auto-update.log";
13228
14361
  function createAutoUpdateController(dependencies = {}) {
@@ -13237,7 +14370,7 @@ function createAutoUpdateController(dependencies = {}) {
13237
14370
  const args = resolveUpdateArgs(options.manager);
13238
14371
  const aisnitchHomePath = getAISnitchHomePath(pathOptions);
13239
14372
  await mkdir4(aisnitchHomePath, { recursive: true });
13240
- const logFilePath = join15(aisnitchHomePath, AUTO_UPDATE_LOG_FILE);
14373
+ const logFilePath = join17(aisnitchHomePath, AUTO_UPDATE_LOG_FILE);
13241
14374
  const startedAt = now().toISOString();
13242
14375
  await writeAutoUpdateState(
13243
14376
  {
@@ -13425,7 +14558,7 @@ function resolveUpdateArgs(manager) {
13425
14558
  async function readAutoUpdateState(options) {
13426
14559
  const statePath = getAutoUpdateStatePath(options);
13427
14560
  try {
13428
- const rawJson = await readFile13(statePath, "utf8");
14561
+ const rawJson = await readFile14(statePath, "utf8");
13429
14562
  return JSON.parse(rawJson);
13430
14563
  } catch (error) {
13431
14564
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -13445,7 +14578,7 @@ async function writeAutoUpdateState(state, options) {
13445
14578
  );
13446
14579
  }
13447
14580
  function getAutoUpdateStatePath(options) {
13448
- return join15(getAISnitchHomePath(options), AUTO_UPDATE_STATE_FILE);
14581
+ return join17(getAISnitchHomePath(options), AUTO_UPDATE_STATE_FILE);
13449
14582
  }
13450
14583
  function toPathOptions(options) {
13451
14584
  return {
@@ -13636,7 +14769,7 @@ function isRecord13(value) {
13636
14769
  }
13637
14770
 
13638
14771
  // src/cli/runtime.ts
13639
- var execFile14 = promisify14(execFileCallback14);
14772
+ var execFile15 = promisify15(execFileCallback14);
13640
14773
  var DAEMON_READY_TIMEOUT_MS = 4e3;
13641
14774
  var DAEMON_READY_POLL_INTERVAL_MS = 100;
13642
14775
  var DAEMON_STOP_TIMEOUT_MS = 4e3;
@@ -13652,7 +14785,7 @@ function createCliRuntime(dependencies = {}) {
13652
14785
  spawn: spawnImplementation
13653
14786
  });
13654
14787
  const execFileImplementation = dependencies.execFile ?? (async (file, args) => {
13655
- return await execFile14(file, [...args], {
14788
+ return await execFile15(file, [...args], {
13656
14789
  encoding: "utf8"
13657
14790
  });
13658
14791
  });
@@ -13827,7 +14960,7 @@ function createCliRuntime(dependencies = {}) {
13827
14960
  }
13828
14961
  async function readDaemonStartupFailure(pathOptions) {
13829
14962
  try {
13830
- const daemonLog = await readFile14(getDaemonLogPath(pathOptions), "utf8");
14963
+ const daemonLog = await readFile15(getDaemonLogPath(pathOptions), "utf8");
13831
14964
  const logLines = daemonLog.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0);
13832
14965
  const lastLine = logLines.at(-1);
13833
14966
  if (!lastLine) {
@@ -13925,22 +15058,35 @@ function createCliRuntime(dependencies = {}) {
13925
15058
  return;
13926
15059
  }
13927
15060
  shuttingDown = true;
13928
- try {
13929
- await pipeline.stop();
13930
- } finally {
13931
- if (daemonMode) {
13932
- const daemonPathOptions = toPathOptions2(options);
15061
+ const shutdownTimeouts = {
15062
+ adapterRegistry: DEFAULT_TIMEOUTS.adapterShutdown,
15063
+ httpReceiver: DEFAULT_TIMEOUTS.httpRequest,
15064
+ udsServer: DEFAULT_TIMEOUTS.fileOperation,
15065
+ wsServer: DEFAULT_TIMEOUTS.wsConnection,
15066
+ cleanupFns: 1e3
15067
+ };
15068
+ const components = {
15069
+ adapterRegistry: pipeline.getAdapterRegistry(),
15070
+ httpReceiver: pipeline.getHttpReceiver(),
15071
+ udsServer: pipeline.getUdsServer(),
15072
+ wsServer: pipeline.getWsServer(),
15073
+ eventBus: pipeline.getEventBus(),
15074
+ cleanupFns: daemonMode ? [async () => {
13933
15075
  await Promise.all([
13934
- removePid(daemonPathOptions),
13935
- removeDaemonState(daemonPathOptions)
15076
+ removePid(toPathOptions2(options)),
15077
+ removeDaemonState(toPathOptions2(options))
13936
15078
  ]);
13937
- }
13938
- }
13939
- if (!daemonMode) {
13940
- output.stdout(`AISnitch stopped after ${signal}.
15079
+ }] : []
15080
+ };
15081
+ try {
15082
+ await shutdownInOrder(components, shutdownTimeouts, "pipeline");
15083
+ } finally {
15084
+ if (!daemonMode) {
15085
+ output.stdout(`AISnitch stopped after ${signal}.
13941
15086
  `);
15087
+ }
15088
+ process.exit(exitCode);
13942
15089
  }
13943
- process.exit(exitCode);
13944
15090
  };
13945
15091
  if (daemonMode) {
13946
15092
  process.once("SIGTERM", () => {
@@ -14197,7 +15343,7 @@ function createCliRuntime(dependencies = {}) {
14197
15343
  }
14198
15344
  const { config } = await loadEffectiveConfig(options);
14199
15345
  setLoggerLevel(getForegroundSafeLogLevel(config.logLevel, false));
14200
- const ephemeralHomeDirectory = await mkdtemp(join16(tmpdir(), "aisnitch-mock-"));
15346
+ const ephemeralHomeDirectory = await mkdtemp(join18(tmpdir(), "aisnitch-mock-"));
14201
15347
  const ephemeralPipeline = new Pipeline();
14202
15348
  const status2 = await ephemeralPipeline.start({
14203
15349
  config: {
@@ -14281,7 +15427,7 @@ function createCliRuntime(dependencies = {}) {
14281
15427
  } else {
14282
15428
  const { config } = await loadEffectiveConfig(options);
14283
15429
  setLoggerLevel(getForegroundSafeLogLevel(config.logLevel, false));
14284
- ephemeralHomeDirectory = await mkdtemp(join16(tmpdir(), "aisnitch-wrap-"));
15430
+ ephemeralHomeDirectory = await mkdtemp(join18(tmpdir(), "aisnitch-wrap-"));
14285
15431
  ephemeralPipeline = new Pipeline();
14286
15432
  await ephemeralPipeline.start({
14287
15433
  config: {
@@ -14356,7 +15502,7 @@ function createCliRuntime(dependencies = {}) {
14356
15502
  },
14357
15503
  pid: process.ppid > 1 ? process.ppid : void 0,
14358
15504
  source: "aisnitch://adapters/aider/notifications-command",
14359
- transcriptPath: join16(process.cwd(), ".aider.chat.history.md"),
15505
+ transcriptPath: join18(process.cwd(), ".aider.chat.history.md"),
14360
15506
  type: "agent.idle"
14361
15507
  }),
14362
15508
  headers: {
@@ -14557,7 +15703,7 @@ function joinSocketPath(pathOptions) {
14557
15703
  }
14558
15704
  function resolveCliEntryPath() {
14559
15705
  const cliEntryPath = process.argv[1];
14560
- if (!cliEntryPath || basename11(cliEntryPath).length === 0) {
15706
+ if (!cliEntryPath || basename12(cliEntryPath).length === 0) {
14561
15707
  throw new Error("Unable to resolve the AISnitch CLI entry path.");
14562
15708
  }
14563
15709
  return cliEntryPath;