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/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.
|
|
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
|
|
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
|
|
2439
|
-
import { promisify as
|
|
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
|
|
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
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
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
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
14233
|
+
return join16(getAISnitchHomePath(options), "aisnitch.pid");
|
|
13101
14234
|
}
|
|
13102
14235
|
function getDaemonStatePath(options = {}) {
|
|
13103
|
-
return
|
|
14236
|
+
return join16(getAISnitchHomePath(options), "daemon-state.json");
|
|
13104
14237
|
}
|
|
13105
14238
|
function getDaemonLogPath(options = {}) {
|
|
13106
|
-
return
|
|
14239
|
+
return join16(getAISnitchHomePath(options), "daemon.log");
|
|
13107
14240
|
}
|
|
13108
14241
|
function getLaunchAgentPath(options = {}) {
|
|
13109
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
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(
|
|
13935
|
-
removeDaemonState(
|
|
15076
|
+
removePid(toPathOptions2(options)),
|
|
15077
|
+
removeDaemonState(toPathOptions2(options))
|
|
13936
15078
|
]);
|
|
13937
|
-
}
|
|
13938
|
-
}
|
|
13939
|
-
|
|
13940
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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 ||
|
|
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;
|