aisnitch 0.2.3 → 0.2.5
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/README.md +312 -669
- package/dist/cli/index.cjs +219 -71
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +219 -71
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +207 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +207 -73
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -3253,8 +3253,16 @@ var EventBus = class {
|
|
|
3253
3253
|
},
|
|
3254
3254
|
"Published event"
|
|
3255
3255
|
);
|
|
3256
|
-
|
|
3257
|
-
|
|
3256
|
+
try {
|
|
3257
|
+
this.emitter.emit("event", parsedEvent.data);
|
|
3258
|
+
} catch (error) {
|
|
3259
|
+
logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus global subscriber");
|
|
3260
|
+
}
|
|
3261
|
+
try {
|
|
3262
|
+
this.emitter.emit(`event:${parsedEvent.data.type}`, parsedEvent.data);
|
|
3263
|
+
} catch (error) {
|
|
3264
|
+
logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus typed subscriber");
|
|
3265
|
+
}
|
|
3258
3266
|
return true;
|
|
3259
3267
|
}
|
|
3260
3268
|
/**
|
|
@@ -3520,6 +3528,7 @@ var WSServer = class {
|
|
|
3520
3528
|
});
|
|
3521
3529
|
socket.on("error", (error) => {
|
|
3522
3530
|
logger.warn({ error }, "WebSocket consumer error");
|
|
3531
|
+
this.consumers.delete(socket);
|
|
3523
3532
|
});
|
|
3524
3533
|
const welcomeMessage = {
|
|
3525
3534
|
type: "welcome",
|
|
@@ -3674,10 +3683,17 @@ var HTTPReceiver = class {
|
|
|
3674
3683
|
}
|
|
3675
3684
|
async handleRequest(request, response, options) {
|
|
3676
3685
|
this.requestCount += 1;
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3686
|
+
let requestUrl;
|
|
3687
|
+
try {
|
|
3688
|
+
requestUrl = new URL(
|
|
3689
|
+
request.url ?? "/",
|
|
3690
|
+
`http://${this.host}:${this.port ?? options.port}`
|
|
3691
|
+
);
|
|
3692
|
+
} catch {
|
|
3693
|
+
this.invalidRequestCount += 1;
|
|
3694
|
+
this.sendJson(response, 400, { error: "malformed request url" });
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3681
3697
|
if (request.method === "GET" && requestUrl.pathname === "/health") {
|
|
3682
3698
|
this.sendJson(response, 200, options.getHealthSnapshot());
|
|
3683
3699
|
return;
|
|
@@ -4018,15 +4034,24 @@ var BaseAdapter = class {
|
|
|
4018
4034
|
cwd: data.cwd ?? context.cwd
|
|
4019
4035
|
}
|
|
4020
4036
|
});
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4037
|
+
let published;
|
|
4038
|
+
try {
|
|
4039
|
+
published = await this.publishEventImplementation(event, {
|
|
4040
|
+
cwd: context.cwd,
|
|
4041
|
+
env: context.env,
|
|
4042
|
+
hookPayload: context.hookPayload,
|
|
4043
|
+
pid: context.pid,
|
|
4044
|
+
sessionId,
|
|
4045
|
+
source: context.source,
|
|
4046
|
+
transcriptPath: context.transcriptPath
|
|
4047
|
+
});
|
|
4048
|
+
} catch (error) {
|
|
4049
|
+
logger.error(
|
|
4050
|
+
{ error, eventType: type, adapter: this.name, sessionId },
|
|
4051
|
+
"\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
|
|
4052
|
+
);
|
|
4053
|
+
published = false;
|
|
4054
|
+
}
|
|
4030
4055
|
if (published) {
|
|
4031
4056
|
this.eventsEmitted += 1;
|
|
4032
4057
|
}
|
|
@@ -5012,6 +5037,9 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
|
|
|
5012
5037
|
});
|
|
5013
5038
|
const context = {
|
|
5014
5039
|
cwd: getString(payload, "cwd"),
|
|
5040
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
5041
|
+
// from TERM_PROGRAM, ITERM_SESSION_ID, etc. — hooks don't carry env vars
|
|
5042
|
+
env: this.env ?? process.env,
|
|
5015
5043
|
hookPayload: payload,
|
|
5016
5044
|
pid: getNumber(payload, "pid"),
|
|
5017
5045
|
sessionId,
|
|
@@ -5245,6 +5273,8 @@ function extractClaudeTranscriptObservations(payload, transcriptPath) {
|
|
|
5245
5273
|
const tokensUsed = extractTokenUsage(payload);
|
|
5246
5274
|
const rawPayload = payload;
|
|
5247
5275
|
const sharedContext = {
|
|
5276
|
+
// 📖 Pass process.env so terminal detection works from transcript path too
|
|
5277
|
+
env: process.env,
|
|
5248
5278
|
hookPayload: rawPayload,
|
|
5249
5279
|
sessionId,
|
|
5250
5280
|
source: "aisnitch://adapters/claude-code/transcript",
|
|
@@ -8901,6 +8931,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8901
8931
|
});
|
|
8902
8932
|
const context = {
|
|
8903
8933
|
cwd: extractOpenCodeCwd(payload),
|
|
8934
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
8935
|
+
env: this.env ?? process.env,
|
|
8904
8936
|
hookPayload: payload,
|
|
8905
8937
|
pid: getNumber5(payload, "pid"),
|
|
8906
8938
|
sessionId,
|
|
@@ -8911,6 +8943,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8911
8943
|
cwd: context.cwd,
|
|
8912
8944
|
errorMessage: extractOpenCodeErrorMessage(payload),
|
|
8913
8945
|
errorType: extractOpenCodeErrorType(payload),
|
|
8946
|
+
// 📖 Extract model from payload — OpenCode may send it as "model" or nested in properties
|
|
8947
|
+
model: getString7(payload, "model") ?? getString7(getRecord6(payload.properties), "model"),
|
|
8914
8948
|
project: extractOpenCodeProject(payload),
|
|
8915
8949
|
raw: payload,
|
|
8916
8950
|
toolInput: extractOpenCodeToolInput(payload),
|
|
@@ -9154,22 +9188,40 @@ var AdapterRegistry = class {
|
|
|
9154
9188
|
}
|
|
9155
9189
|
/**
|
|
9156
9190
|
* Starts every adapter enabled in the current AISnitch config.
|
|
9191
|
+
* 📖 Each adapter is started independently — one failure does not prevent
|
|
9192
|
+
* the others from starting.
|
|
9157
9193
|
*/
|
|
9158
9194
|
async startAll(config) {
|
|
9159
9195
|
for (const adapter of this.list()) {
|
|
9160
9196
|
if (config.adapters[adapter.name]?.enabled !== true) {
|
|
9161
9197
|
continue;
|
|
9162
9198
|
}
|
|
9163
|
-
|
|
9199
|
+
try {
|
|
9200
|
+
await adapter.start();
|
|
9201
|
+
} catch (error) {
|
|
9202
|
+
logger.error(
|
|
9203
|
+
{ error, adapter: adapter.name },
|
|
9204
|
+
`\u{1F4D6} Failed to start adapter "${adapter.name}" \u2014 skipping`
|
|
9205
|
+
);
|
|
9206
|
+
}
|
|
9164
9207
|
}
|
|
9165
9208
|
}
|
|
9166
9209
|
/**
|
|
9167
9210
|
* Stops every adapter in reverse registration order.
|
|
9211
|
+
* 📖 Each adapter is stopped independently — one failure does not prevent
|
|
9212
|
+
* the others from being stopped.
|
|
9168
9213
|
*/
|
|
9169
9214
|
async stopAll() {
|
|
9170
9215
|
const adapters = this.list().reverse();
|
|
9171
9216
|
for (const adapter of adapters) {
|
|
9172
|
-
|
|
9217
|
+
try {
|
|
9218
|
+
await adapter.stop();
|
|
9219
|
+
} catch (error) {
|
|
9220
|
+
logger.warn(
|
|
9221
|
+
{ error, adapter: adapter.name },
|
|
9222
|
+
`\u{1F4D6} Error stopping adapter "${adapter.name}" \u2014 continuing`
|
|
9223
|
+
);
|
|
9224
|
+
}
|
|
9173
9225
|
}
|
|
9174
9226
|
}
|
|
9175
9227
|
};
|
|
@@ -9274,26 +9326,32 @@ var Pipeline = class {
|
|
|
9274
9326
|
await adapter.handleHook(payload);
|
|
9275
9327
|
});
|
|
9276
9328
|
}
|
|
9329
|
+
try {
|
|
9330
|
+
this.wsPort = await this.wsServer.start({
|
|
9331
|
+
port: resolvedWsPort,
|
|
9332
|
+
eventBus: this.eventBus,
|
|
9333
|
+
activeTools
|
|
9334
|
+
});
|
|
9335
|
+
this.httpPort = await this.httpReceiver.start({
|
|
9336
|
+
port: resolvedHttpPort,
|
|
9337
|
+
onHook: async (tool, payload) => {
|
|
9338
|
+
await this.handleHook(tool, payload);
|
|
9339
|
+
},
|
|
9340
|
+
getHealthSnapshot: () => this.getHealthSnapshot()
|
|
9341
|
+
});
|
|
9342
|
+
this.socketPath = await this.udsServer.start({
|
|
9343
|
+
socketPath,
|
|
9344
|
+
onEvent: async (event) => {
|
|
9345
|
+
await this.publishEvent(event);
|
|
9346
|
+
}
|
|
9347
|
+
});
|
|
9348
|
+
await this.adapterRegistry.startAll(config);
|
|
9349
|
+
} catch (error) {
|
|
9350
|
+
logger.error({ error }, "\u{1F4D6} Pipeline start failed \u2014 rolling back already-started components");
|
|
9351
|
+
await this.rollbackPartialStart();
|
|
9352
|
+
throw error;
|
|
9353
|
+
}
|
|
9277
9354
|
this.startedAt = Date.now();
|
|
9278
|
-
this.wsPort = await this.wsServer.start({
|
|
9279
|
-
port: resolvedWsPort,
|
|
9280
|
-
eventBus: this.eventBus,
|
|
9281
|
-
activeTools
|
|
9282
|
-
});
|
|
9283
|
-
this.httpPort = await this.httpReceiver.start({
|
|
9284
|
-
port: resolvedHttpPort,
|
|
9285
|
-
onHook: async (tool, payload) => {
|
|
9286
|
-
await this.handleHook(tool, payload);
|
|
9287
|
-
},
|
|
9288
|
-
getHealthSnapshot: () => this.getHealthSnapshot()
|
|
9289
|
-
});
|
|
9290
|
-
this.socketPath = await this.udsServer.start({
|
|
9291
|
-
socketPath,
|
|
9292
|
-
onEvent: async (event) => {
|
|
9293
|
-
await this.publishEvent(event);
|
|
9294
|
-
}
|
|
9295
|
-
});
|
|
9296
|
-
await this.adapterRegistry.startAll(config);
|
|
9297
9355
|
logger.info(this.getStatus(), "Core pipeline started");
|
|
9298
9356
|
return this.getStatus();
|
|
9299
9357
|
}
|
|
@@ -9301,10 +9359,25 @@ var Pipeline = class {
|
|
|
9301
9359
|
* Stops every pipeline component in reverse dependency order.
|
|
9302
9360
|
*/
|
|
9303
9361
|
async stop() {
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9362
|
+
const stopSafely = async (label, fn) => {
|
|
9363
|
+
try {
|
|
9364
|
+
await fn();
|
|
9365
|
+
} catch (error) {
|
|
9366
|
+
logger.warn({ error }, `\u{1F4D6} Error while stopping ${label} \u2014 continuing shutdown`);
|
|
9367
|
+
}
|
|
9368
|
+
};
|
|
9369
|
+
await stopSafely("adapter registry", async () => {
|
|
9370
|
+
await this.adapterRegistry?.stopAll();
|
|
9371
|
+
});
|
|
9372
|
+
await stopSafely("HTTP receiver", async () => {
|
|
9373
|
+
await this.httpReceiver.stop();
|
|
9374
|
+
});
|
|
9375
|
+
await stopSafely("UDS server", async () => {
|
|
9376
|
+
await this.udsServer.stop();
|
|
9377
|
+
});
|
|
9378
|
+
await stopSafely("WebSocket server", async () => {
|
|
9379
|
+
await this.wsServer.stop();
|
|
9380
|
+
});
|
|
9308
9381
|
this.eventBus.unsubscribeAll();
|
|
9309
9382
|
this.adapterRegistry = null;
|
|
9310
9383
|
this.enabledTools.clear();
|
|
@@ -9321,11 +9394,50 @@ var Pipeline = class {
|
|
|
9321
9394
|
registerHookHandler(tool, handler) {
|
|
9322
9395
|
this.hookHandlers.set(tool, handler);
|
|
9323
9396
|
}
|
|
9397
|
+
/**
|
|
9398
|
+
* 📖 Rolls back any components that were successfully started before a
|
|
9399
|
+
* failure occurred, preventing orphaned servers or leaking resources.
|
|
9400
|
+
*/
|
|
9401
|
+
async rollbackPartialStart() {
|
|
9402
|
+
const stopSafe = async (label, fn) => {
|
|
9403
|
+
try {
|
|
9404
|
+
await fn();
|
|
9405
|
+
} catch (error) {
|
|
9406
|
+
logger.warn({ error }, `\u{1F4D6} Error rolling back ${label}`);
|
|
9407
|
+
}
|
|
9408
|
+
};
|
|
9409
|
+
await stopSafe("adapter registry", async () => {
|
|
9410
|
+
await this.adapterRegistry?.stopAll();
|
|
9411
|
+
});
|
|
9412
|
+
await stopSafe("UDS server", async () => {
|
|
9413
|
+
await this.udsServer.stop();
|
|
9414
|
+
});
|
|
9415
|
+
await stopSafe("HTTP receiver", async () => {
|
|
9416
|
+
await this.httpReceiver.stop();
|
|
9417
|
+
});
|
|
9418
|
+
await stopSafe("WebSocket server", async () => {
|
|
9419
|
+
await this.wsServer.stop();
|
|
9420
|
+
});
|
|
9421
|
+
this.adapterRegistry = null;
|
|
9422
|
+
this.enabledTools.clear();
|
|
9423
|
+
this.hookHandlers.clear();
|
|
9424
|
+
}
|
|
9324
9425
|
/**
|
|
9325
9426
|
* Publishes an event after best-effort context enrichment.
|
|
9427
|
+
* 📖 If enrichment fails, the original event is published un-enriched
|
|
9428
|
+
* rather than being dropped entirely.
|
|
9326
9429
|
*/
|
|
9327
9430
|
async publishEvent(event, context = {}) {
|
|
9328
|
-
|
|
9431
|
+
let enrichedEvent;
|
|
9432
|
+
try {
|
|
9433
|
+
enrichedEvent = await this.contextDetector.enrich(event, context);
|
|
9434
|
+
} catch (error) {
|
|
9435
|
+
logger.warn(
|
|
9436
|
+
{ error, eventId: event.id },
|
|
9437
|
+
"\u{1F4D6} Context enrichment failed \u2014 publishing un-enriched event"
|
|
9438
|
+
);
|
|
9439
|
+
enrichedEvent = event;
|
|
9440
|
+
}
|
|
9329
9441
|
return this.eventBus.publish(enrichedEvent);
|
|
9330
9442
|
}
|
|
9331
9443
|
/**
|
|
@@ -9361,6 +9473,16 @@ var Pipeline = class {
|
|
|
9361
9473
|
};
|
|
9362
9474
|
}
|
|
9363
9475
|
async handleHook(tool, payload) {
|
|
9476
|
+
try {
|
|
9477
|
+
await this.handleHookInner(tool, payload);
|
|
9478
|
+
} catch (error) {
|
|
9479
|
+
logger.error(
|
|
9480
|
+
{ error, tool },
|
|
9481
|
+
"\u{1F4D6} Unhandled error in hook handler \u2014 swallowing to prevent daemon crash"
|
|
9482
|
+
);
|
|
9483
|
+
}
|
|
9484
|
+
}
|
|
9485
|
+
async handleHookInner(tool, payload) {
|
|
9364
9486
|
if (!this.enabledTools.has(tool)) {
|
|
9365
9487
|
logger.debug({ tool }, "Ignoring hook for disabled tool");
|
|
9366
9488
|
return;
|
|
@@ -10536,18 +10658,22 @@ function parseSocketPayload(data) {
|
|
|
10536
10658
|
return parsedEvent.success ? parsedEvent.data : null;
|
|
10537
10659
|
}
|
|
10538
10660
|
function parseUnknownPayload(data) {
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10661
|
+
try {
|
|
10662
|
+
if (typeof data === "string") {
|
|
10663
|
+
return JSON.parse(data);
|
|
10664
|
+
}
|
|
10665
|
+
if (Array.isArray(data)) {
|
|
10666
|
+
return JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
10667
|
+
}
|
|
10668
|
+
if (data instanceof ArrayBuffer) {
|
|
10669
|
+
return JSON.parse(
|
|
10670
|
+
Buffer.from(new Uint8Array(data)).toString("utf8")
|
|
10671
|
+
);
|
|
10672
|
+
}
|
|
10673
|
+
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
10674
|
+
} catch {
|
|
10675
|
+
return null;
|
|
10549
10676
|
}
|
|
10550
|
-
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
10551
10677
|
}
|
|
10552
10678
|
|
|
10553
10679
|
// src/tui/hooks/useKeyBinds.ts
|
|
@@ -11275,16 +11401,20 @@ function ManagedDaemonApp({
|
|
|
11275
11401
|
}
|
|
11276
11402
|
function parseSocketPayload2(data) {
|
|
11277
11403
|
let parsedPayload;
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11404
|
+
try {
|
|
11405
|
+
if (typeof data === "string") {
|
|
11406
|
+
parsedPayload = JSON.parse(data);
|
|
11407
|
+
} else if (Array.isArray(data)) {
|
|
11408
|
+
parsedPayload = JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
11409
|
+
} else if (data instanceof ArrayBuffer) {
|
|
11410
|
+
parsedPayload = JSON.parse(
|
|
11411
|
+
Buffer.from(new Uint8Array(data)).toString("utf8")
|
|
11412
|
+
);
|
|
11413
|
+
} else {
|
|
11414
|
+
parsedPayload = JSON.parse(Buffer.from(data).toString("utf8"));
|
|
11415
|
+
}
|
|
11416
|
+
} catch {
|
|
11417
|
+
return null;
|
|
11288
11418
|
}
|
|
11289
11419
|
if (typeof parsedPayload === "object" && parsedPayload !== null && "type" in parsedPayload && parsedPayload.type === "welcome") {
|
|
11290
11420
|
return null;
|
|
@@ -11931,16 +12061,20 @@ function hexToRgb(hexColor) {
|
|
|
11931
12061
|
];
|
|
11932
12062
|
}
|
|
11933
12063
|
function parseSocketMessage(data) {
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
12064
|
+
try {
|
|
12065
|
+
if (typeof data === "string") {
|
|
12066
|
+
return JSON.parse(data);
|
|
12067
|
+
}
|
|
12068
|
+
if (Array.isArray(data)) {
|
|
12069
|
+
return JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
12070
|
+
}
|
|
12071
|
+
if (data instanceof ArrayBuffer) {
|
|
12072
|
+
return JSON.parse(Buffer.from(new Uint8Array(data)).toString("utf8"));
|
|
12073
|
+
}
|
|
12074
|
+
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
12075
|
+
} catch {
|
|
12076
|
+
return null;
|
|
11942
12077
|
}
|
|
11943
|
-
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
11944
12078
|
}
|
|
11945
12079
|
function isWelcomeMessage(payload) {
|
|
11946
12080
|
if (!isRecord10(payload)) {
|
|
@@ -12288,6 +12422,20 @@ function createCliRuntime(dependencies = {}) {
|
|
|
12288
12422
|
process.once("SIGINT", () => {
|
|
12289
12423
|
void shutdown("SIGINT");
|
|
12290
12424
|
});
|
|
12425
|
+
process.once("uncaughtException", (error) => {
|
|
12426
|
+
output.stderr(
|
|
12427
|
+
`AISnitch crashed: ${error instanceof Error ? error.message : "unknown exception"}
|
|
12428
|
+
`
|
|
12429
|
+
);
|
|
12430
|
+
void shutdown("uncaughtException", 1);
|
|
12431
|
+
});
|
|
12432
|
+
process.once("unhandledRejection", (reason) => {
|
|
12433
|
+
output.stderr(
|
|
12434
|
+
`AISnitch rejected a promise: ${reason instanceof Error ? reason.message : "unknown rejection"}
|
|
12435
|
+
`
|
|
12436
|
+
);
|
|
12437
|
+
void shutdown("unhandledRejection", 1);
|
|
12438
|
+
});
|
|
12291
12439
|
await renderForegroundTui({
|
|
12292
12440
|
configuredAdapters: getEnabledAdapters(config),
|
|
12293
12441
|
eventBus: pipeline.getEventBus(),
|