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.cjs +1246 -100
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1222 -76
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +1808 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1491 -4
- package/dist/index.d.ts +1491 -4
- package/dist/index.js +1736 -122
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
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
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
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
|
|
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
|
|
6992
|
-
import { promisify as
|
|
8037
|
+
import { basename as basename10 } from "path";
|
|
8038
|
+
import { promisify as promisify13 } from "util";
|
|
6993
8039
|
import pidCwd3 from "pid-cwd";
|
|
6994
|
-
var
|
|
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 =
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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" :
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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 ||
|
|
10653
|
+
if (!record || getString12(record, "type") !== partType) {
|
|
9103
10654
|
continue;
|
|
9104
10655
|
}
|
|
9105
|
-
const value =
|
|
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 =
|
|
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 =
|
|
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
|
|
10687
|
+
function isRecord12(value) {
|
|
9137
10688
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9138
10689
|
}
|
|
9139
10690
|
function getRecord10(value) {
|
|
9140
|
-
return
|
|
10691
|
+
return isRecord12(value) ? value : void 0;
|
|
9141
10692
|
}
|
|
9142
|
-
function
|
|
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
|