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.js
CHANGED
|
@@ -3220,8 +3220,16 @@ var EventBus = class {
|
|
|
3220
3220
|
},
|
|
3221
3221
|
"Published event"
|
|
3222
3222
|
);
|
|
3223
|
-
|
|
3224
|
-
|
|
3223
|
+
try {
|
|
3224
|
+
this.emitter.emit("event", parsedEvent.data);
|
|
3225
|
+
} catch (error) {
|
|
3226
|
+
logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus global subscriber");
|
|
3227
|
+
}
|
|
3228
|
+
try {
|
|
3229
|
+
this.emitter.emit(`event:${parsedEvent.data.type}`, parsedEvent.data);
|
|
3230
|
+
} catch (error) {
|
|
3231
|
+
logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus typed subscriber");
|
|
3232
|
+
}
|
|
3225
3233
|
return true;
|
|
3226
3234
|
}
|
|
3227
3235
|
/**
|
|
@@ -3487,6 +3495,7 @@ var WSServer = class {
|
|
|
3487
3495
|
});
|
|
3488
3496
|
socket.on("error", (error) => {
|
|
3489
3497
|
logger.warn({ error }, "WebSocket consumer error");
|
|
3498
|
+
this.consumers.delete(socket);
|
|
3490
3499
|
});
|
|
3491
3500
|
const welcomeMessage = {
|
|
3492
3501
|
type: "welcome",
|
|
@@ -3643,10 +3652,17 @@ var HTTPReceiver = class {
|
|
|
3643
3652
|
}
|
|
3644
3653
|
async handleRequest(request, response, options) {
|
|
3645
3654
|
this.requestCount += 1;
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3655
|
+
let requestUrl;
|
|
3656
|
+
try {
|
|
3657
|
+
requestUrl = new URL(
|
|
3658
|
+
request.url ?? "/",
|
|
3659
|
+
`http://${this.host}:${this.port ?? options.port}`
|
|
3660
|
+
);
|
|
3661
|
+
} catch {
|
|
3662
|
+
this.invalidRequestCount += 1;
|
|
3663
|
+
this.sendJson(response, 400, { error: "malformed request url" });
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3650
3666
|
if (request.method === "GET" && requestUrl.pathname === "/health") {
|
|
3651
3667
|
this.sendJson(response, 200, options.getHealthSnapshot());
|
|
3652
3668
|
return;
|
|
@@ -3987,15 +4003,24 @@ var BaseAdapter = class {
|
|
|
3987
4003
|
cwd: data.cwd ?? context.cwd
|
|
3988
4004
|
}
|
|
3989
4005
|
});
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
4006
|
+
let published;
|
|
4007
|
+
try {
|
|
4008
|
+
published = await this.publishEventImplementation(event, {
|
|
4009
|
+
cwd: context.cwd,
|
|
4010
|
+
env: context.env,
|
|
4011
|
+
hookPayload: context.hookPayload,
|
|
4012
|
+
pid: context.pid,
|
|
4013
|
+
sessionId,
|
|
4014
|
+
source: context.source,
|
|
4015
|
+
transcriptPath: context.transcriptPath
|
|
4016
|
+
});
|
|
4017
|
+
} catch (error) {
|
|
4018
|
+
logger.error(
|
|
4019
|
+
{ error, eventType: type, adapter: this.name, sessionId },
|
|
4020
|
+
"\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
|
|
4021
|
+
);
|
|
4022
|
+
published = false;
|
|
4023
|
+
}
|
|
3999
4024
|
if (published) {
|
|
4000
4025
|
this.eventsEmitted += 1;
|
|
4001
4026
|
}
|
|
@@ -4981,6 +5006,9 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
|
|
|
4981
5006
|
});
|
|
4982
5007
|
const context = {
|
|
4983
5008
|
cwd: getString(payload, "cwd"),
|
|
5009
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
5010
|
+
// from TERM_PROGRAM, ITERM_SESSION_ID, etc. — hooks don't carry env vars
|
|
5011
|
+
env: this.env ?? process.env,
|
|
4984
5012
|
hookPayload: payload,
|
|
4985
5013
|
pid: getNumber(payload, "pid"),
|
|
4986
5014
|
sessionId,
|
|
@@ -5214,6 +5242,8 @@ function extractClaudeTranscriptObservations(payload, transcriptPath) {
|
|
|
5214
5242
|
const tokensUsed = extractTokenUsage(payload);
|
|
5215
5243
|
const rawPayload = payload;
|
|
5216
5244
|
const sharedContext = {
|
|
5245
|
+
// 📖 Pass process.env so terminal detection works from transcript path too
|
|
5246
|
+
env: process.env,
|
|
5217
5247
|
hookPayload: rawPayload,
|
|
5218
5248
|
sessionId,
|
|
5219
5249
|
source: "aisnitch://adapters/claude-code/transcript",
|
|
@@ -8870,6 +8900,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8870
8900
|
});
|
|
8871
8901
|
const context = {
|
|
8872
8902
|
cwd: extractOpenCodeCwd(payload),
|
|
8903
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
8904
|
+
env: this.env ?? process.env,
|
|
8873
8905
|
hookPayload: payload,
|
|
8874
8906
|
pid: getNumber5(payload, "pid"),
|
|
8875
8907
|
sessionId,
|
|
@@ -8880,6 +8912,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8880
8912
|
cwd: context.cwd,
|
|
8881
8913
|
errorMessage: extractOpenCodeErrorMessage(payload),
|
|
8882
8914
|
errorType: extractOpenCodeErrorType(payload),
|
|
8915
|
+
// 📖 Extract model from payload — OpenCode may send it as "model" or nested in properties
|
|
8916
|
+
model: getString7(payload, "model") ?? getString7(getRecord6(payload.properties), "model"),
|
|
8883
8917
|
project: extractOpenCodeProject(payload),
|
|
8884
8918
|
raw: payload,
|
|
8885
8919
|
toolInput: extractOpenCodeToolInput(payload),
|
|
@@ -9123,22 +9157,40 @@ var AdapterRegistry = class {
|
|
|
9123
9157
|
}
|
|
9124
9158
|
/**
|
|
9125
9159
|
* Starts every adapter enabled in the current AISnitch config.
|
|
9160
|
+
* 📖 Each adapter is started independently — one failure does not prevent
|
|
9161
|
+
* the others from starting.
|
|
9126
9162
|
*/
|
|
9127
9163
|
async startAll(config) {
|
|
9128
9164
|
for (const adapter of this.list()) {
|
|
9129
9165
|
if (config.adapters[adapter.name]?.enabled !== true) {
|
|
9130
9166
|
continue;
|
|
9131
9167
|
}
|
|
9132
|
-
|
|
9168
|
+
try {
|
|
9169
|
+
await adapter.start();
|
|
9170
|
+
} catch (error) {
|
|
9171
|
+
logger.error(
|
|
9172
|
+
{ error, adapter: adapter.name },
|
|
9173
|
+
`\u{1F4D6} Failed to start adapter "${adapter.name}" \u2014 skipping`
|
|
9174
|
+
);
|
|
9175
|
+
}
|
|
9133
9176
|
}
|
|
9134
9177
|
}
|
|
9135
9178
|
/**
|
|
9136
9179
|
* Stops every adapter in reverse registration order.
|
|
9180
|
+
* 📖 Each adapter is stopped independently — one failure does not prevent
|
|
9181
|
+
* the others from being stopped.
|
|
9137
9182
|
*/
|
|
9138
9183
|
async stopAll() {
|
|
9139
9184
|
const adapters = this.list().reverse();
|
|
9140
9185
|
for (const adapter of adapters) {
|
|
9141
|
-
|
|
9186
|
+
try {
|
|
9187
|
+
await adapter.stop();
|
|
9188
|
+
} catch (error) {
|
|
9189
|
+
logger.warn(
|
|
9190
|
+
{ error, adapter: adapter.name },
|
|
9191
|
+
`\u{1F4D6} Error stopping adapter "${adapter.name}" \u2014 continuing`
|
|
9192
|
+
);
|
|
9193
|
+
}
|
|
9142
9194
|
}
|
|
9143
9195
|
}
|
|
9144
9196
|
};
|
|
@@ -9243,26 +9295,32 @@ var Pipeline = class {
|
|
|
9243
9295
|
await adapter.handleHook(payload);
|
|
9244
9296
|
});
|
|
9245
9297
|
}
|
|
9298
|
+
try {
|
|
9299
|
+
this.wsPort = await this.wsServer.start({
|
|
9300
|
+
port: resolvedWsPort,
|
|
9301
|
+
eventBus: this.eventBus,
|
|
9302
|
+
activeTools
|
|
9303
|
+
});
|
|
9304
|
+
this.httpPort = await this.httpReceiver.start({
|
|
9305
|
+
port: resolvedHttpPort,
|
|
9306
|
+
onHook: async (tool, payload) => {
|
|
9307
|
+
await this.handleHook(tool, payload);
|
|
9308
|
+
},
|
|
9309
|
+
getHealthSnapshot: () => this.getHealthSnapshot()
|
|
9310
|
+
});
|
|
9311
|
+
this.socketPath = await this.udsServer.start({
|
|
9312
|
+
socketPath,
|
|
9313
|
+
onEvent: async (event) => {
|
|
9314
|
+
await this.publishEvent(event);
|
|
9315
|
+
}
|
|
9316
|
+
});
|
|
9317
|
+
await this.adapterRegistry.startAll(config);
|
|
9318
|
+
} catch (error) {
|
|
9319
|
+
logger.error({ error }, "\u{1F4D6} Pipeline start failed \u2014 rolling back already-started components");
|
|
9320
|
+
await this.rollbackPartialStart();
|
|
9321
|
+
throw error;
|
|
9322
|
+
}
|
|
9246
9323
|
this.startedAt = Date.now();
|
|
9247
|
-
this.wsPort = await this.wsServer.start({
|
|
9248
|
-
port: resolvedWsPort,
|
|
9249
|
-
eventBus: this.eventBus,
|
|
9250
|
-
activeTools
|
|
9251
|
-
});
|
|
9252
|
-
this.httpPort = await this.httpReceiver.start({
|
|
9253
|
-
port: resolvedHttpPort,
|
|
9254
|
-
onHook: async (tool, payload) => {
|
|
9255
|
-
await this.handleHook(tool, payload);
|
|
9256
|
-
},
|
|
9257
|
-
getHealthSnapshot: () => this.getHealthSnapshot()
|
|
9258
|
-
});
|
|
9259
|
-
this.socketPath = await this.udsServer.start({
|
|
9260
|
-
socketPath,
|
|
9261
|
-
onEvent: async (event) => {
|
|
9262
|
-
await this.publishEvent(event);
|
|
9263
|
-
}
|
|
9264
|
-
});
|
|
9265
|
-
await this.adapterRegistry.startAll(config);
|
|
9266
9324
|
logger.info(this.getStatus(), "Core pipeline started");
|
|
9267
9325
|
return this.getStatus();
|
|
9268
9326
|
}
|
|
@@ -9270,10 +9328,25 @@ var Pipeline = class {
|
|
|
9270
9328
|
* Stops every pipeline component in reverse dependency order.
|
|
9271
9329
|
*/
|
|
9272
9330
|
async stop() {
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9331
|
+
const stopSafely = async (label, fn) => {
|
|
9332
|
+
try {
|
|
9333
|
+
await fn();
|
|
9334
|
+
} catch (error) {
|
|
9335
|
+
logger.warn({ error }, `\u{1F4D6} Error while stopping ${label} \u2014 continuing shutdown`);
|
|
9336
|
+
}
|
|
9337
|
+
};
|
|
9338
|
+
await stopSafely("adapter registry", async () => {
|
|
9339
|
+
await this.adapterRegistry?.stopAll();
|
|
9340
|
+
});
|
|
9341
|
+
await stopSafely("HTTP receiver", async () => {
|
|
9342
|
+
await this.httpReceiver.stop();
|
|
9343
|
+
});
|
|
9344
|
+
await stopSafely("UDS server", async () => {
|
|
9345
|
+
await this.udsServer.stop();
|
|
9346
|
+
});
|
|
9347
|
+
await stopSafely("WebSocket server", async () => {
|
|
9348
|
+
await this.wsServer.stop();
|
|
9349
|
+
});
|
|
9277
9350
|
this.eventBus.unsubscribeAll();
|
|
9278
9351
|
this.adapterRegistry = null;
|
|
9279
9352
|
this.enabledTools.clear();
|
|
@@ -9290,11 +9363,50 @@ var Pipeline = class {
|
|
|
9290
9363
|
registerHookHandler(tool, handler) {
|
|
9291
9364
|
this.hookHandlers.set(tool, handler);
|
|
9292
9365
|
}
|
|
9366
|
+
/**
|
|
9367
|
+
* 📖 Rolls back any components that were successfully started before a
|
|
9368
|
+
* failure occurred, preventing orphaned servers or leaking resources.
|
|
9369
|
+
*/
|
|
9370
|
+
async rollbackPartialStart() {
|
|
9371
|
+
const stopSafe = async (label, fn) => {
|
|
9372
|
+
try {
|
|
9373
|
+
await fn();
|
|
9374
|
+
} catch (error) {
|
|
9375
|
+
logger.warn({ error }, `\u{1F4D6} Error rolling back ${label}`);
|
|
9376
|
+
}
|
|
9377
|
+
};
|
|
9378
|
+
await stopSafe("adapter registry", async () => {
|
|
9379
|
+
await this.adapterRegistry?.stopAll();
|
|
9380
|
+
});
|
|
9381
|
+
await stopSafe("UDS server", async () => {
|
|
9382
|
+
await this.udsServer.stop();
|
|
9383
|
+
});
|
|
9384
|
+
await stopSafe("HTTP receiver", async () => {
|
|
9385
|
+
await this.httpReceiver.stop();
|
|
9386
|
+
});
|
|
9387
|
+
await stopSafe("WebSocket server", async () => {
|
|
9388
|
+
await this.wsServer.stop();
|
|
9389
|
+
});
|
|
9390
|
+
this.adapterRegistry = null;
|
|
9391
|
+
this.enabledTools.clear();
|
|
9392
|
+
this.hookHandlers.clear();
|
|
9393
|
+
}
|
|
9293
9394
|
/**
|
|
9294
9395
|
* Publishes an event after best-effort context enrichment.
|
|
9396
|
+
* 📖 If enrichment fails, the original event is published un-enriched
|
|
9397
|
+
* rather than being dropped entirely.
|
|
9295
9398
|
*/
|
|
9296
9399
|
async publishEvent(event, context = {}) {
|
|
9297
|
-
|
|
9400
|
+
let enrichedEvent;
|
|
9401
|
+
try {
|
|
9402
|
+
enrichedEvent = await this.contextDetector.enrich(event, context);
|
|
9403
|
+
} catch (error) {
|
|
9404
|
+
logger.warn(
|
|
9405
|
+
{ error, eventId: event.id },
|
|
9406
|
+
"\u{1F4D6} Context enrichment failed \u2014 publishing un-enriched event"
|
|
9407
|
+
);
|
|
9408
|
+
enrichedEvent = event;
|
|
9409
|
+
}
|
|
9298
9410
|
return this.eventBus.publish(enrichedEvent);
|
|
9299
9411
|
}
|
|
9300
9412
|
/**
|
|
@@ -9330,6 +9442,16 @@ var Pipeline = class {
|
|
|
9330
9442
|
};
|
|
9331
9443
|
}
|
|
9332
9444
|
async handleHook(tool, payload) {
|
|
9445
|
+
try {
|
|
9446
|
+
await this.handleHookInner(tool, payload);
|
|
9447
|
+
} catch (error) {
|
|
9448
|
+
logger.error(
|
|
9449
|
+
{ error, tool },
|
|
9450
|
+
"\u{1F4D6} Unhandled error in hook handler \u2014 swallowing to prevent daemon crash"
|
|
9451
|
+
);
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
9454
|
+
async handleHookInner(tool, payload) {
|
|
9333
9455
|
if (!this.enabledTools.has(tool)) {
|
|
9334
9456
|
logger.debug({ tool }, "Ignoring hook for disabled tool");
|
|
9335
9457
|
return;
|
|
@@ -10505,18 +10627,22 @@ function parseSocketPayload(data) {
|
|
|
10505
10627
|
return parsedEvent.success ? parsedEvent.data : null;
|
|
10506
10628
|
}
|
|
10507
10629
|
function parseUnknownPayload(data) {
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10630
|
+
try {
|
|
10631
|
+
if (typeof data === "string") {
|
|
10632
|
+
return JSON.parse(data);
|
|
10633
|
+
}
|
|
10634
|
+
if (Array.isArray(data)) {
|
|
10635
|
+
return JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
10636
|
+
}
|
|
10637
|
+
if (data instanceof ArrayBuffer) {
|
|
10638
|
+
return JSON.parse(
|
|
10639
|
+
Buffer.from(new Uint8Array(data)).toString("utf8")
|
|
10640
|
+
);
|
|
10641
|
+
}
|
|
10642
|
+
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
10643
|
+
} catch {
|
|
10644
|
+
return null;
|
|
10518
10645
|
}
|
|
10519
|
-
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
10520
10646
|
}
|
|
10521
10647
|
|
|
10522
10648
|
// src/tui/hooks/useKeyBinds.ts
|
|
@@ -11244,16 +11370,20 @@ function ManagedDaemonApp({
|
|
|
11244
11370
|
}
|
|
11245
11371
|
function parseSocketPayload2(data) {
|
|
11246
11372
|
let parsedPayload;
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11373
|
+
try {
|
|
11374
|
+
if (typeof data === "string") {
|
|
11375
|
+
parsedPayload = JSON.parse(data);
|
|
11376
|
+
} else if (Array.isArray(data)) {
|
|
11377
|
+
parsedPayload = JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
11378
|
+
} else if (data instanceof ArrayBuffer) {
|
|
11379
|
+
parsedPayload = JSON.parse(
|
|
11380
|
+
Buffer.from(new Uint8Array(data)).toString("utf8")
|
|
11381
|
+
);
|
|
11382
|
+
} else {
|
|
11383
|
+
parsedPayload = JSON.parse(Buffer.from(data).toString("utf8"));
|
|
11384
|
+
}
|
|
11385
|
+
} catch {
|
|
11386
|
+
return null;
|
|
11257
11387
|
}
|
|
11258
11388
|
if (typeof parsedPayload === "object" && parsedPayload !== null && "type" in parsedPayload && parsedPayload.type === "welcome") {
|
|
11259
11389
|
return null;
|
|
@@ -11900,16 +12030,20 @@ function hexToRgb(hexColor) {
|
|
|
11900
12030
|
];
|
|
11901
12031
|
}
|
|
11902
12032
|
function parseSocketMessage(data) {
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
|
|
12033
|
+
try {
|
|
12034
|
+
if (typeof data === "string") {
|
|
12035
|
+
return JSON.parse(data);
|
|
12036
|
+
}
|
|
12037
|
+
if (Array.isArray(data)) {
|
|
12038
|
+
return JSON.parse(Buffer.concat(data).toString("utf8"));
|
|
12039
|
+
}
|
|
12040
|
+
if (data instanceof ArrayBuffer) {
|
|
12041
|
+
return JSON.parse(Buffer.from(new Uint8Array(data)).toString("utf8"));
|
|
12042
|
+
}
|
|
12043
|
+
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
12044
|
+
} catch {
|
|
12045
|
+
return null;
|
|
11911
12046
|
}
|
|
11912
|
-
return JSON.parse(Buffer.from(data).toString("utf8"));
|
|
11913
12047
|
}
|
|
11914
12048
|
function isWelcomeMessage(payload) {
|
|
11915
12049
|
if (!isRecord10(payload)) {
|
|
@@ -12257,6 +12391,20 @@ function createCliRuntime(dependencies = {}) {
|
|
|
12257
12391
|
process.once("SIGINT", () => {
|
|
12258
12392
|
void shutdown("SIGINT");
|
|
12259
12393
|
});
|
|
12394
|
+
process.once("uncaughtException", (error) => {
|
|
12395
|
+
output.stderr(
|
|
12396
|
+
`AISnitch crashed: ${error instanceof Error ? error.message : "unknown exception"}
|
|
12397
|
+
`
|
|
12398
|
+
);
|
|
12399
|
+
void shutdown("uncaughtException", 1);
|
|
12400
|
+
});
|
|
12401
|
+
process.once("unhandledRejection", (reason) => {
|
|
12402
|
+
output.stderr(
|
|
12403
|
+
`AISnitch rejected a promise: ${reason instanceof Error ? reason.message : "unknown rejection"}
|
|
12404
|
+
`
|
|
12405
|
+
);
|
|
12406
|
+
void shutdown("unhandledRejection", 1);
|
|
12407
|
+
});
|
|
12260
12408
|
await renderForegroundTui({
|
|
12261
12409
|
configuredAdapters: getEnabledAdapters(config),
|
|
12262
12410
|
eventBus: pipeline.getEventBus(),
|