khala-cli 0.4.9 → 0.4.10
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/THIRD_PARTY_NOTICES.txt +28 -0
- package/dist/{chunk-NIX5F4LT.mjs → chunk-2AMWQ7RB.mjs} +330 -66
- package/dist/{chunk-PAJ4JJPV.mjs → chunk-3E5DH3Y6.mjs} +43 -6
- package/dist/{chunk-NGSYOVQ4.mjs → chunk-4BP7V3OP.mjs} +2 -2
- package/dist/{chunk-TEFOQXBK.mjs → chunk-4BZQ4GVW.mjs} +3 -3
- package/dist/{chunk-ITFD3JZS.mjs → chunk-5JSGA6OB.mjs} +424 -78
- package/dist/{chunk-QKWEZXR3.mjs → chunk-6AQVW5W7.mjs} +12 -4
- package/dist/{chunk-UK5GJO4Y.mjs → chunk-6J2PUM7L.mjs} +6 -5
- package/dist/{chunk-P3YIN7G7.mjs → chunk-6SUV3EW6.mjs} +11 -10
- package/dist/{chunk-GSZADJMW.mjs → chunk-ARIDZOTI.mjs} +309 -81
- package/dist/chunk-C2WEDMAF.mjs +15 -0
- package/dist/{chunk-DJM23SIU.mjs → chunk-CHC4VZF5.mjs} +7 -8
- package/dist/{chunk-BLCRVRIK.mjs → chunk-CKLCR5G4.mjs} +456 -87
- package/dist/{chunk-7R3EIDGR.mjs → chunk-EVT2DWNZ.mjs} +1 -1
- package/dist/{chunk-KMLXOPBQ.mjs → chunk-FJVVSKZ3.mjs} +4 -2
- package/dist/{chunk-3KDOTR54.mjs → chunk-G3NMB4PY.mjs} +1 -1
- package/dist/{chunk-SSO5RFOX.mjs → chunk-HHEZVPRR.mjs} +1 -1
- package/dist/{chunk-BV45NK7K.mjs → chunk-JKOJQTFT.mjs} +100 -6
- package/dist/{chunk-T7IL7V7Q.mjs → chunk-NDMFD5IM.mjs} +13 -10
- package/dist/{chunk-TANCKTDK.mjs → chunk-OEV5DFLW.mjs} +70 -5
- package/dist/{chunk-BEO7WIIM.mjs → chunk-OIZ4R5H7.mjs} +28 -8
- package/dist/{chunk-FZEH2IXZ.mjs → chunk-PJKI7SRI.mjs} +176 -26
- package/dist/{chunk-NPSQNCOY.mjs → chunk-PN25BUQ4.mjs} +13 -4
- package/dist/{chunk-3R6HFSLL.mjs → chunk-QDPJ4VT2.mjs} +92 -38
- package/dist/{chunk-FO5GXCST.mjs → chunk-SRZTHCXP.mjs} +5 -5
- package/dist/{chunk-LBT47HLZ.mjs → chunk-TSK3HJEG.mjs} +10 -9
- package/dist/{chunk-6BO3JSUP.mjs → chunk-UX34IVN5.mjs} +2 -2
- package/dist/{chunk-V4F3SYKW.mjs → chunk-VAC3JTAV.mjs} +25 -17
- package/dist/{chunk-PDZ5Z5ZE.mjs → chunk-VDZK4LW3.mjs} +24 -9
- package/dist/chunk-W6HM6F2U.mjs +901 -0
- package/dist/{chunk-WDOHRV3D.mjs → chunk-WPNMWNCL.mjs} +14 -14
- package/dist/chunk-WUDXHF2Q.mjs +14 -0
- package/dist/{chunk-XBQDIKB5.mjs → chunk-WZJ6DQJV.mjs} +9 -8
- package/dist/{chunk-ANSR4IAW.mjs → chunk-XR625T3I.mjs} +2 -2
- package/dist/{chunk-K7V7X6WU.mjs → chunk-ZSKLNZWZ.mjs} +21 -4
- package/dist/iterm2_send.py +56 -0
- package/dist/khala.mjs +11 -11
- package/dist/web/assets/local-CSPuGnH2.css +1 -0
- package/dist/web/assets/local-Dtyxaacx.js +9 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-47XNLAJG.mjs +0 -38
- package/dist/chunk-XUCWSYVS.mjs +0 -148
- package/dist/web/assets/local-CFzRHZpB.js +0 -9
- package/dist/web/assets/local-ciypUFoK.css +0 -1
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import "./chunk-SSO5RFOX.mjs";
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
retryUnexpired
|
|
4
|
+
} from "./chunk-C2WEDMAF.mjs";
|
|
5
|
+
import {
|
|
6
|
+
RESTORE_HISTORY_MAX_PAGES
|
|
7
|
+
} from "./chunk-WUDXHF2Q.mjs";
|
|
8
|
+
import "./chunk-HHEZVPRR.mjs";
|
|
8
9
|
import {
|
|
9
10
|
LISTENING_MODE_COMMAND_TYPE,
|
|
10
11
|
LISTENING_MODE_MEMBER_KEY,
|
|
11
12
|
memberListeningMode
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-4BP7V3OP.mjs";
|
|
14
|
+
import {
|
|
15
|
+
CHANNEL_EVENT_TYPE,
|
|
16
|
+
encodeChannelEvent,
|
|
17
|
+
memberRenameContent
|
|
18
|
+
} from "./chunk-JKOJQTFT.mjs";
|
|
13
19
|
import {
|
|
14
20
|
StateError
|
|
15
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-PN25BUQ4.mjs";
|
|
16
22
|
import {
|
|
17
23
|
init_define_KHALA_BUNDLE
|
|
18
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-EVT2DWNZ.mjs";
|
|
19
25
|
|
|
20
26
|
// src/matrix/session.ts
|
|
21
27
|
init_define_KHALA_BUNDLE();
|
|
22
28
|
import { randomUUID } from "node:crypto";
|
|
23
|
-
import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, Direction, Method, EventType } from "matrix-js-sdk";
|
|
29
|
+
import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, SetPresence, Direction, Method, EventType } from "matrix-js-sdk";
|
|
24
30
|
function message(event) {
|
|
25
31
|
const type = event.getType();
|
|
26
32
|
const eventId = event.getId();
|
|
@@ -44,7 +50,7 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
44
50
|
const started = Date.now();
|
|
45
51
|
const rejoinTxnId = `khala.rejoin.${randomUUID()}`;
|
|
46
52
|
const log = (line) => opts?.log?.(line);
|
|
47
|
-
const persistent = opts?.cryptoStore ? await (await import("./chunk-
|
|
53
|
+
const persistent = opts?.cryptoStore ? await (await import("./chunk-3E5DH3Y6.mjs")).openCryptoStore(opts.cryptoStore.dir, opts.cryptoStore.root, creds) : void 0;
|
|
48
54
|
const client = createClient({ ...persistent ? { store: persistent.sync } : {}, baseUrl: creds.homeserver, userId: creds.userId, accessToken: creds.accessToken, deviceId: creds.deviceId });
|
|
49
55
|
const handlers = /* @__PURE__ */ new Set();
|
|
50
56
|
const ended = /* @__PURE__ */ new Set();
|
|
@@ -52,13 +58,38 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
52
58
|
const inviters = /* @__PURE__ */ new Map();
|
|
53
59
|
const emitted = /* @__PURE__ */ new Set();
|
|
54
60
|
const liveEvents = /* @__PURE__ */ new Set();
|
|
61
|
+
const undecryptableIds = new Map((persistent?.undecryptableEventIds ?? []).map((entry) => [entry.id, entry]));
|
|
62
|
+
let retrySave = Promise.resolve();
|
|
63
|
+
const saveRetries = () => {
|
|
64
|
+
const entries = [...undecryptableIds.values()];
|
|
65
|
+
retrySave = retrySave.then(() => persistent?.rememberUndecryptable(entries)).catch(() => log("decryption_retry_save_failed"));
|
|
66
|
+
};
|
|
67
|
+
const rememberDecryption = (event) => {
|
|
68
|
+
const id = event.getId();
|
|
69
|
+
if (stopped || !persistent || !id) return;
|
|
70
|
+
const failed = event.getType() === "m.room.encrypted" || event.isDecryptionFailure();
|
|
71
|
+
if (failed) {
|
|
72
|
+
if (undecryptableIds.has(id)) return;
|
|
73
|
+
undecryptableIds.set(id, { id, firstSeen: Date.now() });
|
|
74
|
+
} else if (!undecryptableIds.delete(id)) return;
|
|
75
|
+
if (undecryptableIds.size > 100) undecryptableIds.delete(undecryptableIds.keys().next().value);
|
|
76
|
+
saveRetries();
|
|
77
|
+
};
|
|
55
78
|
const joinTimes = /* @__PURE__ */ new Map();
|
|
56
79
|
const cancellations = /* @__PURE__ */ new Set();
|
|
57
80
|
let joinedRoom;
|
|
58
81
|
let stopped = false;
|
|
82
|
+
let presenceTimer;
|
|
83
|
+
let away;
|
|
84
|
+
const publishPresence = () => {
|
|
85
|
+
void client.setSyncPresence(away ? SetPresence.Offline : void 0);
|
|
86
|
+
void client.setPresence(away ? { presence: "offline", status_msg: away } : { presence: "online", status_msg: "" }).catch(() => log("presence_failed"));
|
|
87
|
+
};
|
|
59
88
|
let recovering = persistent?.restored ?? false;
|
|
60
89
|
const deliver = (event) => {
|
|
61
|
-
if (stopped
|
|
90
|
+
if (stopped) return;
|
|
91
|
+
if (liveEvents.has(event)) rememberDecryption(event);
|
|
92
|
+
if (recovering || !liveEvents.has(event)) return;
|
|
62
93
|
const command = event.getType() === LISTENING_MODE_COMMAND_TYPE && !event.isDecryptionFailure() && event.getId() && event.getRoomId() && event.getSender() ? { eventId: event.getId(), roomId: event.getRoomId(), sender: event.getSender(), ts: event.getTs(), content: event.getContent() } : void 0;
|
|
63
94
|
const m = command ?? message(event);
|
|
64
95
|
if (!m) {
|
|
@@ -96,7 +127,10 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
96
127
|
const timeline = (event, _room, toStart, _removed, data) => {
|
|
97
128
|
if (stopped || toStart || data?.liveEvent === false && !persistent?.restored) return;
|
|
98
129
|
liveEvents.add(event);
|
|
99
|
-
void client.decryptEventIfNeeded(event).then(() => deliver(event), () =>
|
|
130
|
+
void client.decryptEventIfNeeded(event).then(() => deliver(event), () => {
|
|
131
|
+
rememberDecryption(event);
|
|
132
|
+
log("live_decryption_failed");
|
|
133
|
+
});
|
|
100
134
|
};
|
|
101
135
|
const decrypted = (event) => deliver(event);
|
|
102
136
|
const syncLog = (state, _previous, data) => {
|
|
@@ -167,6 +201,24 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
167
201
|
const stopSession = async () => {
|
|
168
202
|
if (stopped) return;
|
|
169
203
|
stopped = true;
|
|
204
|
+
clearInterval(presenceTimer);
|
|
205
|
+
if (presenceTimer && !wipe) {
|
|
206
|
+
await Promise.race([
|
|
207
|
+
client.setPresence({ presence: "offline", status_msg: "session ended" }).catch(() => log("presence_failed")),
|
|
208
|
+
new Promise((resolve) => setTimeout(resolve, 3e3).unref())
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
await retrySave;
|
|
212
|
+
if (wipe) {
|
|
213
|
+
try {
|
|
214
|
+
await client.http.authedRequest(Method.Post, "/logout", void 0, {}, { localTimeoutMs: 5e3 });
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (matrixCode(error) !== "M_UNKNOWN_TOKEN" && !(typeof error === "object" && error !== null && "httpStatus" in error && error.httpStatus === 401)) {
|
|
217
|
+
log("discarded_device_logout_failed");
|
|
218
|
+
console.error("khala: discarded_device_logout_failed");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
170
222
|
if (wipe && persistent?.forgetIdentity) {
|
|
171
223
|
try {
|
|
172
224
|
await persistent.forgetIdentity();
|
|
@@ -236,8 +288,7 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
236
288
|
await client.initRustCrypto(persistent ? { useIndexedDB: true, cryptoDatabasePrefix: persistent.prefix } : { useIndexedDB: false });
|
|
237
289
|
} catch (error) {
|
|
238
290
|
if (!persistent?.restored || error instanceof StateError && error.code === "unsafe_state_dir") throw error;
|
|
239
|
-
|
|
240
|
-
throw new (await import("./chunk-PAJ4JJPV.mjs")).CryptoStoreCorruptError();
|
|
291
|
+
throw new (await import("./chunk-3E5DH3Y6.mjs")).CryptoStoreCorruptError();
|
|
241
292
|
}
|
|
242
293
|
const crypto = client.getCrypto();
|
|
243
294
|
if (!crypto) throw new Error("crypto_unavailable");
|
|
@@ -264,6 +315,9 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
264
315
|
}, () => client.getSyncState() === SyncState.Prepared || client.getSyncState() === SyncState.Syncing, 3e4, "sync_timeout");
|
|
265
316
|
await Promise.all([client.startClient({ initialSyncLimit: 30 }), prepared]);
|
|
266
317
|
log(`session_ready_ms=${Date.now() - started}`);
|
|
318
|
+
publishPresence();
|
|
319
|
+
presenceTimer = setInterval(publishPresence, 6e4);
|
|
320
|
+
presenceTimer.unref();
|
|
267
321
|
} catch (error) {
|
|
268
322
|
await stop();
|
|
269
323
|
throw error;
|
|
@@ -282,6 +336,7 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
282
336
|
return {
|
|
283
337
|
userId: creds.userId,
|
|
284
338
|
...persistent?.recovered ? { cryptoReset: true } : {},
|
|
339
|
+
...persistent?.restored ? { recoversOnJoin: true } : {},
|
|
285
340
|
onEnded(handler) {
|
|
286
341
|
if (!stopped) ended.add(handler);
|
|
287
342
|
return () => {
|
|
@@ -317,7 +372,6 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
317
372
|
async join(roomId) {
|
|
318
373
|
if (stopped) throw new Error("session_stopped");
|
|
319
374
|
if (joinedRoom === roomId) return;
|
|
320
|
-
const rejoinedAt = Date.now();
|
|
321
375
|
const membership = client.getRoom(roomId)?.getMyMembership();
|
|
322
376
|
if (membership !== "join") {
|
|
323
377
|
if (membership !== "invite") throw new Error("not_invited");
|
|
@@ -338,30 +392,80 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
338
392
|
const content = { ...ownJoin.getContent(), membership: "join", "com.khala.invited_by": inviters.get(roomId) };
|
|
339
393
|
await client.sendStateEvent(roomId, EventType.RoomMember, content, creds.userId);
|
|
340
394
|
}
|
|
341
|
-
const joinedAt = persistent?.joinedAt ??
|
|
395
|
+
const joinedAt = persistent?.joinedAt ?? ownJoin.getTs();
|
|
342
396
|
await persistent?.rememberJoin(joinedAt);
|
|
343
397
|
joinTimes.set(roomId, joinedAt);
|
|
344
398
|
if (persistent?.restored) {
|
|
399
|
+
let pruned = false;
|
|
400
|
+
for (const [id, entry] of [...undecryptableIds]) {
|
|
401
|
+
if (stopped) throw new Error("session_stopped");
|
|
402
|
+
if (!retryUnexpired(entry)) {
|
|
403
|
+
undecryptableIds.delete(id);
|
|
404
|
+
pruned = true;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
const raw = await client.fetchRoomEvent(roomId, id);
|
|
409
|
+
const event = client.getEventMapper()({ ...raw, room_id: roomId });
|
|
410
|
+
if (event.getTs() < joinedAt) {
|
|
411
|
+
undecryptableIds.delete(id);
|
|
412
|
+
pruned = true;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
liveEvents.add(event);
|
|
416
|
+
await client.decryptEventIfNeeded(event).catch(() => log("recovery_decryption_failed"));
|
|
417
|
+
rememberDecryption(event);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
const status = typeof error === "object" && error !== null && "httpStatus" in error ? error.httpStatus : void 0;
|
|
420
|
+
if (matrixCode(error) === "M_NOT_FOUND" || matrixCode(error) === "M_FORBIDDEN" || status === 404 || status === 403) {
|
|
421
|
+
undecryptableIds.delete(id);
|
|
422
|
+
pruned = true;
|
|
423
|
+
}
|
|
424
|
+
log("recovery_event_retry_failed");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (pruned) saveRetries();
|
|
345
428
|
let token = null;
|
|
346
429
|
const seenTokens = /* @__PURE__ */ new Set();
|
|
430
|
+
let pages = 0;
|
|
431
|
+
let undecryptable = 0;
|
|
347
432
|
for (; ; ) {
|
|
348
433
|
if (stopped) throw new Error("session_stopped");
|
|
349
434
|
const page = await client.createMessagesRequest(roomId, token, 100, Direction.Backward);
|
|
350
|
-
|
|
435
|
+
pages++;
|
|
436
|
+
let reachedBoundary = false;
|
|
351
437
|
for (const raw of page.chunk) {
|
|
352
438
|
const event = client.getEventMapper()({ ...raw, room_id: roomId });
|
|
353
|
-
if (event.getTs() < joinedAt) {
|
|
354
|
-
|
|
355
|
-
|
|
439
|
+
if (opts?.restoreStopAtEventId && event.getId() === opts.restoreStopAtEventId || event.getTs() < joinedAt) {
|
|
440
|
+
reachedBoundary = true;
|
|
441
|
+
break;
|
|
356
442
|
}
|
|
357
443
|
liveEvents.add(event);
|
|
358
444
|
await client.decryptEventIfNeeded(event).catch(() => log("recovery_decryption_failed"));
|
|
445
|
+
rememberDecryption(event);
|
|
446
|
+
if (event.getType() === "m.room.encrypted" || event.isDecryptionFailure()) {
|
|
447
|
+
undecryptable++;
|
|
448
|
+
liveEvents.delete(event);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (reachedBoundary || page.chunk.length === 0 || !page.end) break;
|
|
452
|
+
if (pages >= RESTORE_HISTORY_MAX_PAGES) {
|
|
453
|
+
const count = `restore_catchup_truncated pages=${pages}`;
|
|
454
|
+
if (opts?.log) log(count);
|
|
455
|
+
else process.stderr.write(`${count}
|
|
456
|
+
`);
|
|
457
|
+
break;
|
|
359
458
|
}
|
|
360
|
-
if (reachedJoin || page.chunk.length === 0 || !page.end) break;
|
|
361
459
|
if (seenTokens.has(page.end)) throw new Error("recovery_pagination_stalled");
|
|
362
460
|
seenTokens.add(page.end);
|
|
363
461
|
token = page.end;
|
|
364
462
|
}
|
|
463
|
+
if (undecryptable > 0) {
|
|
464
|
+
const count = `history_undecryptable=${undecryptable}`;
|
|
465
|
+
if (opts?.log) log(count);
|
|
466
|
+
else process.stderr.write(`${count}
|
|
467
|
+
`);
|
|
468
|
+
}
|
|
365
469
|
}
|
|
366
470
|
recovering = false;
|
|
367
471
|
if (!joinedRoom) client.on(RoomEvent.MyMembership, membershipEnded);
|
|
@@ -370,7 +474,8 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
370
474
|
if (event.getRoomId() === roomId) deliver(event);
|
|
371
475
|
}
|
|
372
476
|
},
|
|
373
|
-
|
|
477
|
+
joinedAt: (roomId) => joinTimes.get(roomId),
|
|
478
|
+
async history(roomId, limit, before, options) {
|
|
374
479
|
if (stopped) throw new Error("session_stopped");
|
|
375
480
|
let token = null;
|
|
376
481
|
if (before !== void 0) {
|
|
@@ -380,16 +485,26 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
380
485
|
const res = await guarded(roomId, () => client.createMessagesRequest(roomId, token, limit, Direction.Backward));
|
|
381
486
|
const messages = [];
|
|
382
487
|
let undecryptable = 0;
|
|
488
|
+
let oldest;
|
|
489
|
+
let oldestTs;
|
|
490
|
+
let reachedBoundary = false;
|
|
383
491
|
for (const raw of res.chunk) {
|
|
384
492
|
const event = client.getEventMapper()({ ...raw, room_id: roomId });
|
|
493
|
+
oldest = event.getId();
|
|
494
|
+
oldestTs = Math.min(oldestTs ?? event.getTs(), event.getTs());
|
|
495
|
+
if (options?.stopAtEventId && oldest === options.stopAtEventId) {
|
|
496
|
+
reachedBoundary = true;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
385
499
|
try {
|
|
386
500
|
await client.decryptEventIfNeeded(event);
|
|
387
501
|
} catch {
|
|
388
502
|
}
|
|
503
|
+
rememberDecryption(event);
|
|
389
504
|
if (event.getType() === "m.room.encrypted" || event.isDecryptionFailure()) {
|
|
390
505
|
undecryptable++;
|
|
391
506
|
const eventId = event.getId(), sender = event.getSender();
|
|
392
|
-
if (eventId && sender && eventId !== before) {
|
|
507
|
+
if (options?.includeUnavailable !== false && eventId && sender && eventId !== before) {
|
|
393
508
|
const body = "[Encrypted message unavailable: this device does not have its key. Keys may not have been shared for messages sent before joining or while this agent was offline.]";
|
|
394
509
|
messages.push({
|
|
395
510
|
eventId,
|
|
@@ -406,10 +521,16 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
406
521
|
const m = message(event);
|
|
407
522
|
if (m && m.eventId !== before) messages.push(m);
|
|
408
523
|
}
|
|
409
|
-
|
|
524
|
+
const count = `history_undecryptable=${undecryptable}`;
|
|
525
|
+
log(count);
|
|
526
|
+
if (options?.includeUnavailable === false && undecryptable > 0 && !opts?.log) process.stderr.write(`${count}
|
|
527
|
+
`);
|
|
410
528
|
messages.reverse();
|
|
411
|
-
|
|
412
|
-
|
|
529
|
+
return {
|
|
530
|
+
messages,
|
|
531
|
+
...typeof res.end === "string" && oldest ? { nextBefore: oldest } : {},
|
|
532
|
+
...options?.includeUnavailable === false || options?.stopAtEventId ? { ...oldestTs === void 0 ? {} : { oldestTs }, reachedBoundary } : {}
|
|
533
|
+
};
|
|
413
534
|
},
|
|
414
535
|
async send(roomId, text) {
|
|
415
536
|
if (stopped) throw new Error("session_stopped");
|
|
@@ -428,6 +549,35 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
428
549
|
const value = joinedRoom && client.getRoom(joinedRoom)?.currentState.getStateEvents("m.room.member", userId)?.getContent().displayname;
|
|
429
550
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
430
551
|
},
|
|
552
|
+
members(roomId) {
|
|
553
|
+
return (client.getRoom(roomId)?.getJoinedMembers() ?? []).filter((member) => member.userId !== creds.userId).map((member) => {
|
|
554
|
+
const user = client.getUser(member.userId);
|
|
555
|
+
const event = user?.events.presence;
|
|
556
|
+
if (!user || !event) return { name: member.name, presence: "unknown" };
|
|
557
|
+
if (user.presence !== "offline") return { name: member.name, presence: "online" };
|
|
558
|
+
const reason = event.getContent().status_msg;
|
|
559
|
+
return {
|
|
560
|
+
name: member.name,
|
|
561
|
+
presence: "away",
|
|
562
|
+
since: new Date(user.lastPresenceTs - (user.lastActiveAgo ?? 0)).toISOString(),
|
|
563
|
+
...typeof reason === "string" && reason ? { reason: reason.slice(0, 80) } : {}
|
|
564
|
+
};
|
|
565
|
+
});
|
|
566
|
+
},
|
|
567
|
+
seenBy(roomId, eventId) {
|
|
568
|
+
const room = client.getRoom(roomId);
|
|
569
|
+
if (!room?.findEventById(eventId)) return [];
|
|
570
|
+
return room.getJoinedMembers().filter((member) => member.userId !== creds.userId && room.hasUserReadEvent(member.userId, eventId)).map((member) => member.name);
|
|
571
|
+
},
|
|
572
|
+
setAway(reason) {
|
|
573
|
+
if (reason === away) return;
|
|
574
|
+
away = reason;
|
|
575
|
+
if (presenceTimer && !stopped) publishPresence();
|
|
576
|
+
},
|
|
577
|
+
async markRead(roomId, eventId) {
|
|
578
|
+
if (stopped) throw new Error("session_stopped");
|
|
579
|
+
await guarded(roomId, () => client.setRoomReadMarkersHttpRequest(roomId, eventId, eventId));
|
|
580
|
+
},
|
|
431
581
|
stop
|
|
432
582
|
};
|
|
433
583
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
init_define_KHALA_BUNDLE
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EVT2DWNZ.mjs";
|
|
5
5
|
|
|
6
6
|
// ../contracts/src/m1/harness.ts
|
|
7
7
|
init_define_KHALA_BUNDLE();
|
|
@@ -18,8 +18,8 @@ var HARNESS_REGISTRY = [
|
|
|
18
18
|
{ id: "copilot", displayName: "Copilot CLI", modelName: "Copilot", logoKey: "copilot", steer: true, sync: true, idleWake: "opt-in", registered: true },
|
|
19
19
|
{ id: "vscode", displayName: "Copilot (VS Code)", modelName: "VSCode", logoKey: "copilot", steer: true, sync: true, idleWake: "opt-in", registered: true },
|
|
20
20
|
{ id: "gemini", displayName: "Gemini CLI", modelName: "Gemini", logoKey: "gemini", steer: true, sync: true, idleWake: "opt-in", registered: true },
|
|
21
|
-
//
|
|
22
|
-
{ id: "antigravity", displayName: "Antigravity CLI", modelName: "Antigravity", logoKey: null, steer:
|
|
21
|
+
// Spike-backed capabilities; transport availability is reported per session.
|
|
22
|
+
{ id: "antigravity", displayName: "Antigravity CLI", modelName: "Antigravity", logoKey: null, steer: true, sync: true, idleWake: "opt-in", registered: true },
|
|
23
23
|
{ id: "qwen", displayName: "Qwen Code", modelName: "Qwen", logoKey: "qwen", steer: true, sync: true, idleWake: "default", registered: true },
|
|
24
24
|
{ id: "muse", displayName: "Muse Code", modelName: "Muse", logoKey: null, steer: true, sync: true, idleWake: "default", registered: true },
|
|
25
25
|
{ id: "generic", displayName: "MCP agent", modelName: "Agent", logoKey: null, steer: false, sync: false, idleWake: "none", registered: true }
|
|
@@ -328,6 +328,14 @@ function cachedProcessReader(read = readProcess) {
|
|
|
328
328
|
return result;
|
|
329
329
|
};
|
|
330
330
|
}
|
|
331
|
+
async function readProcessArguments(pid) {
|
|
332
|
+
if (!validPid(pid) || process.platform !== "linux") return null;
|
|
333
|
+
try {
|
|
334
|
+
return (await readFile(`/proc/${pid}/cmdline`, "utf8")).split("\0").slice(0, -1);
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
331
339
|
|
|
332
340
|
// src/state.ts
|
|
333
341
|
var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
@@ -512,7 +520,7 @@ async function readWatcherStatus(files, read = readProcess, now = () => /* @__PU
|
|
|
512
520
|
return writer && writer.startTime !== owner?.startTime ? { ...status, state: "disconnected", detail: "process_exited" } : status;
|
|
513
521
|
}
|
|
514
522
|
async function writeStateFile(dir, name, value) {
|
|
515
|
-
if (!["join.json", "session.json", "cursor.json", "status.json", "mode.json", "rejoin.json", "channel.json", "resume.json"].includes(name)) throw new StateError("storage_failed");
|
|
523
|
+
if (!["join.json", "session.json", "cursor.json", "status.json", "mode.json", "rejoin.json", "channel.json", "resume.json", "fold.json"].includes(name)) throw new StateError("storage_failed");
|
|
516
524
|
await writeJsonAtomic(path.join(dir, name), name === "status.json" ? await ownStatus(value) : value);
|
|
517
525
|
}
|
|
518
526
|
function readStateFile(dir, name) {
|
|
@@ -565,6 +573,7 @@ export {
|
|
|
565
573
|
ancestors,
|
|
566
574
|
nearestNonShellAncestor,
|
|
567
575
|
cachedProcessReader,
|
|
576
|
+
readProcessArguments,
|
|
568
577
|
SESSION_ID_PATTERN,
|
|
569
578
|
StateError,
|
|
570
579
|
stateRoot,
|
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
isWakeEntry
|
|
4
|
-
} from "./chunk-47XNLAJG.mjs";
|
|
5
|
-
import "./chunk-BV45NK7K.mjs";
|
|
6
2
|
import {
|
|
7
3
|
wakeDisableNotice
|
|
8
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-W6HM6F2U.mjs";
|
|
9
5
|
import {
|
|
10
6
|
adapterFor,
|
|
11
7
|
createCopilotCodec,
|
|
12
8
|
recordHookSession
|
|
13
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5JSGA6OB.mjs";
|
|
14
10
|
import {
|
|
15
11
|
capturePane
|
|
16
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-6AQVW5W7.mjs";
|
|
17
13
|
import {
|
|
18
14
|
readListeningMode
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-2AMWQ7RB.mjs";
|
|
16
|
+
import "./chunk-4BP7V3OP.mjs";
|
|
21
17
|
import {
|
|
22
18
|
advanceCursor,
|
|
19
|
+
clearUsageLimit,
|
|
20
|
+
isWakeEntry,
|
|
23
21
|
listChannels,
|
|
22
|
+
markUsageLimit,
|
|
24
23
|
readActivity,
|
|
25
24
|
unread,
|
|
25
|
+
wakesAgent,
|
|
26
26
|
writeActivity
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-OEV5DFLW.mjs";
|
|
28
|
+
import "./chunk-JKOJQTFT.mjs";
|
|
28
29
|
import {
|
|
29
30
|
StateError,
|
|
30
31
|
TERMINAL_SESSION_DETAILS,
|
|
31
32
|
readJson,
|
|
32
33
|
readStatus,
|
|
33
34
|
sessionFiles
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-PN25BUQ4.mjs";
|
|
35
36
|
import {
|
|
36
37
|
settleAttempts
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-ZSKLNZWZ.mjs";
|
|
38
39
|
import {
|
|
39
40
|
init_define_KHALA_BUNDLE
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-EVT2DWNZ.mjs";
|
|
41
42
|
|
|
42
43
|
// hooks/deliver.ts
|
|
43
44
|
init_define_KHALA_BUNDLE();
|
|
@@ -53,26 +54,36 @@ import * as fs from "node:fs/promises";
|
|
|
53
54
|
import { constants } from "node:fs";
|
|
54
55
|
var MAX_TAIL_BYTES = 64 * 1024;
|
|
55
56
|
var MARKERS = /* @__PURE__ */ new Set(["[Request interrupted by user]", "[Request interrupted by user for tool use]"]);
|
|
56
|
-
async function
|
|
57
|
-
if (!transcriptPath) return
|
|
57
|
+
async function tailLines(transcriptPath) {
|
|
58
|
+
if (!transcriptPath) return [];
|
|
58
59
|
let handle;
|
|
59
60
|
try {
|
|
60
61
|
handle = await fs.open(transcriptPath, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
61
62
|
const stat2 = await handle.stat();
|
|
62
|
-
if (!stat2.isFile()) return
|
|
63
|
+
if (!stat2.isFile()) return [];
|
|
63
64
|
const length = Math.min(stat2.size, MAX_TAIL_BYTES);
|
|
64
65
|
const start = stat2.size - length;
|
|
65
66
|
const buffer = Buffer.alloc(length);
|
|
66
67
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
67
|
-
if (bytesRead !== length) return
|
|
68
|
+
if (bytesRead !== length) return [];
|
|
68
69
|
let tail = buffer.toString("utf8");
|
|
69
70
|
if (start > 0) {
|
|
70
71
|
const newline = tail.indexOf("\n");
|
|
71
|
-
if (newline < 0) return
|
|
72
|
+
if (newline < 0) return [];
|
|
72
73
|
tail = tail.slice(newline + 1);
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
return tail.split("\n").filter((line) => line.trim() !== "");
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
} finally {
|
|
79
|
+
await handle?.close().catch(() => {
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function claudeTranscriptInterrupted(transcriptPath, activityAt) {
|
|
84
|
+
const lines = await tailLines(transcriptPath);
|
|
85
|
+
if (!lines.length) return false;
|
|
86
|
+
try {
|
|
76
87
|
const entry = JSON.parse(lines.at(-1));
|
|
77
88
|
if (entry?.type !== "user" || entry.message?.role !== "user" || typeof entry.timestamp !== "string" || !(Date.parse(entry.timestamp) > Date.parse(activityAt))) return false;
|
|
78
89
|
const content = entry.message.content;
|
|
@@ -80,11 +91,24 @@ async function claudeTranscriptInterrupted(transcriptPath, activityAt) {
|
|
|
80
91
|
return Array.isArray(content) && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string" && MARKERS.has(content[0].text);
|
|
81
92
|
} catch {
|
|
82
93
|
return false;
|
|
83
|
-
} finally {
|
|
84
|
-
await handle?.close().catch(() => {
|
|
85
|
-
});
|
|
86
94
|
}
|
|
87
95
|
}
|
|
96
|
+
async function claudeTranscriptVersion(transcriptPath) {
|
|
97
|
+
for (const line of (await tailLines(transcriptPath)).reverse()) {
|
|
98
|
+
try {
|
|
99
|
+
const version = JSON.parse(line)?.version;
|
|
100
|
+
if (typeof version === "string" && /^\d+\.\d+\.\d+$/.test(version)) return version;
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
function versionAtLeast(version, floor) {
|
|
107
|
+
if (version === void 0) return false;
|
|
108
|
+
const a = version.split(".").map(Number), b = floor.split(".").map(Number);
|
|
109
|
+
for (let i = 0; i < b.length; i++) if ((a[i] ?? 0) !== b[i]) return (a[i] ?? 0) > b[i];
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
88
112
|
|
|
89
113
|
// src/harness/deliver-core.ts
|
|
90
114
|
var MAX_FRAME_BYTES = 64 * 1024;
|
|
@@ -162,7 +186,8 @@ function selectFrames(groups, budget = MAX_FRAME_BYTES) {
|
|
|
162
186
|
}
|
|
163
187
|
return selected;
|
|
164
188
|
}
|
|
165
|
-
async function channelFrames(files, steerOnly, requireWake, io) {
|
|
189
|
+
async function channelFrames(files, steerOnly, requireWake, io, quiet, shown) {
|
|
190
|
+
const wakes = (group) => group.consumed.some((entry) => quiet ? wakesAgent(entry, group.mode, group.you) : isWakeEntry(entry));
|
|
166
191
|
const groups = [];
|
|
167
192
|
for (const channel of await listChannels(files)) {
|
|
168
193
|
const mode = await readListeningMode(channel.files);
|
|
@@ -171,6 +196,7 @@ async function channelFrames(files, steerOnly, requireWake, io) {
|
|
|
171
196
|
groups.push({
|
|
172
197
|
channel: { ...channel, ...status?.channelName !== void 0 ? { channelName: status.channelName } : {} },
|
|
173
198
|
...pending,
|
|
199
|
+
mode,
|
|
174
200
|
you: typeof status?.displayName === "string" ? status.displayName : void 0
|
|
175
201
|
});
|
|
176
202
|
}
|
|
@@ -179,16 +205,16 @@ async function channelFrames(files, steerOnly, requireWake, io) {
|
|
|
179
205
|
const reserved = selected.reduce((sum, group) => sum + Buffer.byteLength(group.frame), 0) + Math.max(0, selected.length - 1);
|
|
180
206
|
let spare = MAX_FRAME_BYTES - reserved;
|
|
181
207
|
const order = selected.map((group, index) => ({ group, index }));
|
|
182
|
-
if (requireWake) order.sort((a, b) => Number(b.group
|
|
208
|
+
if (requireWake) order.sort((a, b) => Number(wakes(b.group)) - Number(wakes(a.group)));
|
|
183
209
|
let hasWake = false;
|
|
184
210
|
const failures = [];
|
|
185
211
|
for (const { group, index } of order) {
|
|
186
|
-
if (requireWake && !hasWake && !group
|
|
212
|
+
if (requireWake && !hasWake && !wakes(group)) continue;
|
|
187
213
|
try {
|
|
188
214
|
let current = group;
|
|
189
215
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
190
|
-
if (!current.frame || requireWake && !hasWake && !current
|
|
191
|
-
if (await advanceCursor(current.channel.files, current.cursor, current.consumed) === "conflict") {
|
|
216
|
+
if (!current.frame || requireWake && !hasWake && !wakes(current)) break;
|
|
217
|
+
if (await advanceCursor(current.channel.files, current.cursor, current.consumed, shown) === "conflict") {
|
|
192
218
|
if (attempt === 0) {
|
|
193
219
|
const pending = await unread(current.channel.files);
|
|
194
220
|
const retry = selectFrame(
|
|
@@ -204,7 +230,7 @@ async function channelFrames(files, steerOnly, requireWake, io) {
|
|
|
204
230
|
}
|
|
205
231
|
spare += Buffer.byteLength(group.frame) - Buffer.byteLength(current.frame);
|
|
206
232
|
emitted.set(index, current.frame);
|
|
207
|
-
hasWake ||= current
|
|
233
|
+
hasWake ||= wakes(current);
|
|
208
234
|
break;
|
|
209
235
|
}
|
|
210
236
|
} catch (error) {
|
|
@@ -241,7 +267,7 @@ async function deliverCore(stdin, adapter, io) {
|
|
|
241
267
|
if (!codec) return 0;
|
|
242
268
|
const input = codec.parse(stdin);
|
|
243
269
|
let output = codec.noop(input?.event);
|
|
244
|
-
if (input && (input.event === "prompt" || input.event === "start") && input.sessionId && adapter.sessionSources.some((source) => source.kind === "hook-map")) {
|
|
270
|
+
if (input && (input.event === "prompt" || input.event === "start" || adapter.id === "opencode" && input.event === "stop") && input.sessionId && adapter.sessionSources.some((source) => source.kind === "hook-map")) {
|
|
245
271
|
try {
|
|
246
272
|
await recordHookSession(adapter.id, input.sessionId, io.env, { now: io.now, ...io.pid !== void 0 ? { pid: io.pid } : {}, ...io.readProcess ? { readProcess: io.readProcess } : {}, ...input.workspace !== void 0 ? { workspace: input.workspace } : {} });
|
|
247
273
|
} catch (error) {
|
|
@@ -257,6 +283,18 @@ async function deliverCore(stdin, adapter, io) {
|
|
|
257
283
|
}
|
|
258
284
|
if (files) {
|
|
259
285
|
try {
|
|
286
|
+
if (input.event === "limit") {
|
|
287
|
+
await markUsageLimit(files, io.now);
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
if (input.event === "tool" || input.event === "stop") await clearUsageLimit(files);
|
|
291
|
+
if (adapter.verifyWake) {
|
|
292
|
+
try {
|
|
293
|
+
await adapter.verifyWake(stdin, files, io.now().getTime());
|
|
294
|
+
} catch {
|
|
295
|
+
diagnostic(io, "wake_verification_failed");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
260
298
|
if (adapter.emptyPrompt && (input.event === "start" || input.event === "prompt")) {
|
|
261
299
|
try {
|
|
262
300
|
await capturePane(files, io.env, {
|
|
@@ -288,18 +326,21 @@ async function deliverCore(stdin, adapter, io) {
|
|
|
288
326
|
}
|
|
289
327
|
if (input.event !== "start") {
|
|
290
328
|
let steerOnly = input.event === "tool";
|
|
291
|
-
if (input.event === "prompt") {
|
|
292
|
-
const activity = await readActivity(files);
|
|
293
|
-
steerOnly = adapter.id === "claude" && activity.state === "busy" && Date.parse(activity.updatedAt) !== 0 && io.now().getTime() - Date.parse(activity.updatedAt) < CLAUDE_STALE_BUSY_MS && !await claudeTranscriptInterrupted(input.transcriptPath, activity.updatedAt);
|
|
329
|
+
if (adapter.hookPromptText || input.event === "prompt") {
|
|
294
330
|
try {
|
|
295
331
|
await settleAttempts(files.dir, {
|
|
296
332
|
now: io.now().getTime(),
|
|
297
|
-
activity,
|
|
298
|
-
promptText: input.promptText ?? ""
|
|
333
|
+
activity: await readActivity(files),
|
|
334
|
+
promptText: adapter.hookPromptText ? await adapter.hookPromptText(input, files) : input.promptText ?? "",
|
|
335
|
+
deferVerification: Boolean(adapter.hookPromptText)
|
|
299
336
|
});
|
|
300
337
|
} catch {
|
|
301
338
|
diagnostic(io, "wake_verification_failed");
|
|
302
339
|
}
|
|
340
|
+
}
|
|
341
|
+
if (input.event === "prompt") {
|
|
342
|
+
const activity = await readActivity(files);
|
|
343
|
+
steerOnly = adapter.id === "claude" && activity.state === "busy" && Date.parse(activity.updatedAt) !== 0 && io.now().getTime() - Date.parse(activity.updatedAt) < CLAUDE_STALE_BUSY_MS && !await claudeTranscriptInterrupted(input.transcriptPath, activity.updatedAt);
|
|
303
344
|
await writeActivity(files, "busy", io.now);
|
|
304
345
|
}
|
|
305
346
|
if (adapter.id === "claude" && input.event === "tool" && (await readActivity(files)).state === "busy") await writeActivity(files, "busy", io.now);
|
|
@@ -316,16 +357,21 @@ async function deliverCore(stdin, adapter, io) {
|
|
|
316
357
|
return 0;
|
|
317
358
|
}
|
|
318
359
|
}
|
|
360
|
+
const stopContext = input.event === "stop" && codec.stopContextFloor !== void 0 && versionAtLeast(await claudeTranscriptVersion(input.transcriptPath), codec.stopContextFloor);
|
|
319
361
|
const frame = await channelFrames(
|
|
320
362
|
files,
|
|
321
363
|
steerOnly,
|
|
322
364
|
input.event !== "prompt" || !codec.promptDeliversWithoutWake,
|
|
323
|
-
io
|
|
365
|
+
io,
|
|
366
|
+
input.event === "stop",
|
|
367
|
+
// Stop shows the frame only as verified Claude context; every other Stop is a fixed nudge,
|
|
368
|
+
// which consumes the batch without showing it and so earns no read receipt.
|
|
369
|
+
input.event !== "stop" || stopContext
|
|
324
370
|
);
|
|
325
371
|
if (frame) {
|
|
326
372
|
if (!wake) await writeActivity(files, "busy", io.now);
|
|
327
373
|
const notice = await wakeDisableNotice(files.dir);
|
|
328
|
-
output = (wake ? wake + "\n" : "") + codec.render(input.event, frame + (notice ? "\n" + notice : ""));
|
|
374
|
+
output = (wake ? wake + "\n" : "") + codec.render(input.event, frame + (notice ? "\n" + notice : ""), stopContext);
|
|
329
375
|
} else if (input.event === "stop") await writeActivity(files, "idle", io.now);
|
|
330
376
|
}
|
|
331
377
|
}
|
|
@@ -357,7 +403,15 @@ async function deliver(stdin, argv, io = {
|
|
|
357
403
|
let env = io.env;
|
|
358
404
|
const museRoots = argv.length === 6 && argv[0] === "--harness" && argv[1] === "muse" && argv[2] === "--state-home" && argv[4] === "--data-home" && path.isAbsolute(argv[3]) && path.isAbsolute(argv[5]);
|
|
359
405
|
if (museRoots) env = { ...env, XDG_STATE_HOME: argv[3], XDG_DATA_HOME: argv[5] };
|
|
360
|
-
|
|
406
|
+
const antigravityEvent = argv.length === 4 && argv[0] === "--harness" && argv[1] === "antigravity" && argv[2] === "--event" && ["PreInvocation", "Stop"].includes(argv[3]) ? argv[3] : void 0;
|
|
407
|
+
let adapter = (argv.length === 2 || museRoots || antigravityEvent || argv.length === 4 && argv[1] === "copilot" && argv[2] === "--event") && argv[0] === "--harness" ? adapterFor(argv[1]) : void 0;
|
|
408
|
+
if (antigravityEvent) {
|
|
409
|
+
try {
|
|
410
|
+
stdin = JSON.stringify({ ...JSON.parse(stdin), khalaHookEvent: antigravityEvent });
|
|
411
|
+
} catch {
|
|
412
|
+
stdin = "{}";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
361
415
|
if (adapter?.id === "copilot" && argv.length === 4) adapter = { ...adapter, codec: createCopilotCodec(argv[3]) };
|
|
362
416
|
if (!adapter?.codec) {
|
|
363
417
|
diagnostic(io, "invalid_harness");
|