khala-cli 0.4.18 → 0.4.19

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.
Files changed (42) hide show
  1. package/dist/{chunk-VK7ZSKQZ.mjs → chunk-2CM6MFLQ.mjs} +8 -7
  2. package/dist/chunk-5T2T2BGP.mjs +25 -0
  3. package/dist/{chunk-TYRNBET6.mjs → chunk-6CUBZHAI.mjs} +2 -2
  4. package/dist/{chunk-XXKDMHR2.mjs → chunk-7BR55Y3K.mjs} +2 -2
  5. package/dist/{chunk-ELI3YCFF.mjs → chunk-7JLJ2IVR.mjs} +19 -81
  6. package/dist/{chunk-4WHRO5ZU.mjs → chunk-A76X5SLZ.mjs} +9 -7
  7. package/dist/{chunk-UA6DEWBA.mjs → chunk-BUFSSWDE.mjs} +1 -1
  8. package/dist/{chunk-SRUPGV2G.mjs → chunk-CR2MQIB4.mjs} +11 -13
  9. package/dist/{chunk-FDSHLUCQ.mjs → chunk-DP24LQAW.mjs} +44 -22
  10. package/dist/{chunk-IESMCVXV.mjs → chunk-ERWPSB27.mjs} +1 -1
  11. package/dist/{chunk-YAXWRS5U.mjs → chunk-I3UIC3DD.mjs} +2 -2
  12. package/dist/chunk-IH5ALDEX.mjs +17 -0
  13. package/dist/{chunk-RJZ6FAFC.mjs → chunk-JALXEI56.mjs} +3 -3
  14. package/dist/{chunk-WM75XLND.mjs → chunk-K7YTGI7R.mjs} +6 -6
  15. package/dist/chunk-LOVF37KO.mjs +72 -0
  16. package/dist/{chunk-FCOL5HBH.mjs → chunk-LUQ2K5K2.mjs} +10 -12
  17. package/dist/chunk-LZXXWDKB.mjs +18 -0
  18. package/dist/{chunk-3RCRNCG3.mjs → chunk-ME3G775Q.mjs} +140 -5
  19. package/dist/{chunk-QYVSNP2P.mjs → chunk-NDII2VEP.mjs} +1 -1
  20. package/dist/{chunk-BG4JGKQG.mjs → chunk-NYMXWKOF.mjs} +2 -2
  21. package/dist/{chunk-LOGBYBPA.mjs → chunk-OFLK3DF2.mjs} +1 -1
  22. package/dist/{chunk-RWT4I2XJ.mjs → chunk-OPBCYCDJ.mjs} +23 -25
  23. package/dist/{chunk-OZ5C2ZMB.mjs → chunk-POSW5KU5.mjs} +10 -12
  24. package/dist/{chunk-43THPTMD.mjs → chunk-PQ6Y7QRL.mjs} +7 -7
  25. package/dist/{chunk-KCWHDC7S.mjs → chunk-PXNKY5ZM.mjs} +8 -10
  26. package/dist/{chunk-GVIPYERE.mjs → chunk-Q2ENNHOG.mjs} +3 -3
  27. package/dist/{chunk-HOKMRMNP.mjs → chunk-RYBGLMUZ.mjs} +2 -2
  28. package/dist/{chunk-MTDLNIUY.mjs → chunk-TJUM3DPB.mjs} +1 -1
  29. package/dist/{chunk-6ZL6OFW4.mjs → chunk-TZLAEDD2.mjs} +5 -5
  30. package/dist/{chunk-H2X64ISL.mjs → chunk-UI5OJ2TY.mjs} +3 -3
  31. package/dist/{chunk-5IZ3WN3S.mjs → chunk-V5SWPOLN.mjs} +53 -53
  32. package/dist/{chunk-JXV5S7FC.mjs → chunk-WOHXD3RS.mjs} +433 -129
  33. package/dist/{chunk-4IOLQB34.mjs → chunk-YN334EWZ.mjs} +1 -1
  34. package/dist/khala.mjs +11 -11
  35. package/dist/web/assets/{local-3eMi9596.js → local-SRORuW7r.js} +2 -2
  36. package/dist/web/index.html +1 -1
  37. package/package.json +1 -1
  38. package/dist/chunk-4FUYRD3G.mjs +0 -27
  39. package/dist/chunk-AU2FBBUY.mjs +0 -20
  40. package/dist/chunk-EBKZETYL.mjs +0 -142
  41. package/dist/chunk-KXLGRISD.mjs +0 -19
  42. package/dist/chunk-LNDZI73C.mjs +0 -323
@@ -1,31 +1,27 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
2
  import {
3
3
  DEFAULT_LISTENING_MODE,
4
- decodeListeningMode
5
- } from "./chunk-EBKZETYL.mjs";
6
- import {
7
- advanceCursor,
8
- isWakeEntry,
9
- listChannels,
10
- markNudged,
11
- mentions,
12
- readActivity,
13
- readCursor,
14
- readEntries,
15
- unread,
16
- wakeStart,
17
- wakesAgent
18
- } from "./chunk-LNDZI73C.mjs";
4
+ decodeChannelEvent,
5
+ decodeListeningMode,
6
+ formatChannelEventLine,
7
+ wakesOn
8
+ } from "./chunk-ME3G775Q.mjs";
19
9
  import {
20
10
  SESSION_ID_PATTERN,
21
11
  StateError,
12
+ channelFiles,
13
+ channelsDir,
22
14
  ensureStateDir,
15
+ filesForDir,
23
16
  readJson,
24
17
  readWatcherStatus,
18
+ removeStateFile,
25
19
  sessionFiles,
20
+ stateKey,
26
21
  stateRoot,
27
- writeJsonAtomic
28
- } from "./chunk-4IOLQB34.mjs";
22
+ writeJsonAtomic,
23
+ writeStateFile
24
+ } from "./chunk-YN334EWZ.mjs";
29
25
  import {
30
26
  cancelAttempt,
31
27
  confirmWakeText,
@@ -34,16 +30,16 @@ import {
34
30
  settleAttempts,
35
31
  wakeLine,
36
32
  withWakeLock
37
- } from "./chunk-LOGBYBPA.mjs";
33
+ } from "./chunk-OFLK3DF2.mjs";
38
34
  import {
39
35
  init_define_KHALA_BUNDLE
40
- } from "./chunk-QYVSNP2P.mjs";
36
+ } from "./chunk-NDII2VEP.mjs";
41
37
 
42
38
  // src/watch.ts
43
39
  init_define_KHALA_BUNDLE();
44
- import * as fs7 from "node:fs/promises";
40
+ import * as fs10 from "node:fs/promises";
45
41
  import { watch as watchDirectory } from "node:fs";
46
- import path7 from "node:path";
42
+ import path9 from "node:path";
47
43
  import { randomUUID as randomUUID3 } from "node:crypto";
48
44
 
49
45
  // src/monitor-storage.ts
@@ -76,6 +72,137 @@ async function monitorStorage(files) {
76
72
  return dir;
77
73
  }
78
74
 
75
+ // src/inbox.ts
76
+ init_define_KHALA_BUNDLE();
77
+ import * as fs2 from "node:fs/promises";
78
+
79
+ // src/events/receive.ts
80
+ init_define_KHALA_BUNDLE();
81
+ function toEventInboxEntry(base, rawContent, you) {
82
+ const decoded = decodeChannelEvent(rawContent);
83
+ if (!decoded.ok) return null;
84
+ const content = decoded.value;
85
+ const to = content.kind === "handoff" ? String(content.data?.to ?? "").replace(/^@/u, "").toLowerCase() : "";
86
+ return {
87
+ entry: {
88
+ ...base,
89
+ kind: "event",
90
+ body: formatChannelEventLine(content) + (content.url ? " " + content.url : ""),
91
+ eventKind: content.kind,
92
+ ...content.data ? { data: content.data } : {},
93
+ ...to && to === you?.toLowerCase() ? { mention: true } : {}
94
+ },
95
+ ...content.key !== void 0 ? { key: content.key } : {}
96
+ };
97
+ }
98
+ function createEventKeyFilter() {
99
+ const seen = /* @__PURE__ */ new Set();
100
+ return (key) => {
101
+ if (key === void 0) return true;
102
+ if (seen.has(key)) return false;
103
+ seen.add(key);
104
+ return true;
105
+ };
106
+ }
107
+ function isWakeEntry(entry) {
108
+ return entry.kind === "message" || entry.mention === true;
109
+ }
110
+ var agentGroupMention = /(?<![\p{L}\p{N}_@])@(?:channel|agents)(?![\p{L}\p{N}_])/iu;
111
+ function mentions(entry, name) {
112
+ if (entry.kind === "message" && entry.senderKind !== "agent" && agentGroupMention.test(entry.body)) return true;
113
+ if (!name) return false;
114
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
115
+ return new RegExp(`(?<![\\p{L}\\p{N}_])@?${escaped}(?![\\p{L}\\p{N}_])`, "iu").test(entry.body);
116
+ }
117
+ function wakesAgent(entry, mode, you) {
118
+ if (!isWakeEntry(entry)) return false;
119
+ return wakesOn(mode ?? "steer", entry.mention === true || mentions(entry, you), entry.senderKind === "agent");
120
+ }
121
+
122
+ // src/inbox.ts
123
+ async function readEntries(files) {
124
+ let content;
125
+ try {
126
+ content = await fs2.readFile(files.inbox, "utf8");
127
+ } catch (error) {
128
+ if (error.code === "ENOENT") return [];
129
+ throw new StateError("storage_failed");
130
+ }
131
+ const entries = [];
132
+ for (const line of content.split("\n").slice(0, -1)) {
133
+ try {
134
+ const entry = JSON.parse(line);
135
+ if (entry && ["eventId", "roomId", "ts", "sender", "senderLabel", "body"].every((key) => typeof entry[key] === "string") && ["human", "agent", "unknown"].includes(entry.senderKind) && ["message", "event"].includes(entry.kind)) entries.push(entry);
136
+ } catch {
137
+ }
138
+ }
139
+ return entries;
140
+ }
141
+ async function appendEntries(files, entries) {
142
+ const seen = new Set((await readEntries(files)).map((entry) => entry.eventId));
143
+ const fresh = entries.filter((entry) => {
144
+ if (seen.has(entry.eventId)) return false;
145
+ seen.add(entry.eventId);
146
+ return true;
147
+ });
148
+ if (fresh.length) {
149
+ try {
150
+ await fs2.appendFile(files.inbox, fresh.map((entry) => JSON.stringify(entry) + "\n").join(""), { mode: 384, flag: "a" });
151
+ } catch {
152
+ throw new StateError("storage_failed");
153
+ }
154
+ }
155
+ return fresh;
156
+ }
157
+ async function readCursor(files) {
158
+ const cursor = await readJson(files.cursor);
159
+ return cursor && (cursor.lastDeliveredEventId === null || typeof cursor.lastDeliveredEventId === "string") && Number.isSafeInteger(cursor.deliveredCount) && cursor.deliveredCount >= 0 ? cursor : { lastDeliveredEventId: null, deliveredCount: 0 };
160
+ }
161
+ async function unread(files) {
162
+ const cursor = await readCursor(files);
163
+ const entries = await readEntries(files);
164
+ return { entries: entries.slice(cursor.deliveredCount), cursor };
165
+ }
166
+ function wakeStart(cursor) {
167
+ return Math.max(cursor.deliveredCount, Number.isSafeInteger(cursor.nudgedCount) ? cursor.nudgedCount : 0);
168
+ }
169
+ async function unwoken(files) {
170
+ return (await readEntries(files)).slice(wakeStart(await readCursor(files)));
171
+ }
172
+ async function markNudged(files, expected, count) {
173
+ const current = await readCursor(files);
174
+ if (current.deliveredCount !== expected.deliveredCount || current.lastDeliveredEventId !== expected.lastDeliveredEventId) return;
175
+ await writeJsonAtomic(files.cursor, { ...current, nudgedCount: count });
176
+ }
177
+ async function unreadCounts(files) {
178
+ const { entries } = await unread(files);
179
+ return { total: entries.length, messages: entries.filter((entry) => isWakeEntry(entry)).length };
180
+ }
181
+ async function advanceCursor(files, expected, delivered, shown = false) {
182
+ const current = await readCursor(files);
183
+ if (current.deliveredCount !== expected.deliveredCount || current.lastDeliveredEventId !== expected.lastDeliveredEventId) return "conflict";
184
+ if (!delivered.length) return expected;
185
+ const entries = await readEntries(files);
186
+ const slice = entries.slice(expected.deliveredCount, expected.deliveredCount + delivered.length);
187
+ if (slice.length !== delivered.length || slice.some((entry, index) => entry.eventId !== delivered[index]?.eventId)) return "conflict";
188
+ const last = delivered[delivered.length - 1].eventId;
189
+ const shownEventId = shown ? last : current.shownEventId;
190
+ const next = {
191
+ lastDeliveredEventId: last,
192
+ deliveredCount: expected.deliveredCount + delivered.length,
193
+ ...shownEventId ? { shownEventId } : {},
194
+ ...wakeStart(current) > expected.deliveredCount + delivered.length ? { nudgedCount: current.nudgedCount } : {}
195
+ };
196
+ await writeJsonAtomic(files.cursor, next);
197
+ return next;
198
+ }
199
+ async function appendInbox(dir, entry) {
200
+ return (await appendEntries(filesForDir(dir), [entry])).length === 1;
201
+ }
202
+ function unreadCount(dir) {
203
+ return unreadCounts(filesForDir(dir));
204
+ }
205
+
79
206
  // src/mode.ts
80
207
  init_define_KHALA_BUNDLE();
81
208
  import path2 from "node:path";
@@ -127,17 +254,164 @@ async function applyListeningMode(files, next, meta, now = () => /* @__PURE__ */
127
254
  return { previous, next };
128
255
  }
129
256
 
257
+ // src/channels.ts
258
+ init_define_KHALA_BUNDLE();
259
+ import * as fs3 from "node:fs/promises";
260
+ import path3 from "node:path";
261
+ function channelKey(roomId2) {
262
+ return stateKey(roomId2);
263
+ }
264
+ async function exists(file) {
265
+ try {
266
+ await fs3.lstat(file);
267
+ return true;
268
+ } catch (error) {
269
+ if (error.code === "ENOENT") return false;
270
+ throw new StateError("storage_failed");
271
+ }
272
+ }
273
+ function roomId(value) {
274
+ if (value && typeof value === "object" && "roomId" in value && typeof value.roomId === "string" && value.roomId.length) return value.roomId;
275
+ return void 0;
276
+ }
277
+ async function legacyRoomId(files) {
278
+ const saved = roomId(await readJson(files.session));
279
+ if (saved) return saved;
280
+ let content;
281
+ try {
282
+ content = await fs3.readFile(files.inbox, "utf8");
283
+ } catch (error) {
284
+ if (error.code === "ENOENT") return void 0;
285
+ throw new StateError("storage_failed");
286
+ }
287
+ try {
288
+ return roomId(JSON.parse(content.split("\n")[0]));
289
+ } catch {
290
+ return void 0;
291
+ }
292
+ }
293
+ async function listChannels(files) {
294
+ await ensureStateDir(files.dir);
295
+ const channels = [];
296
+ const dir = channelsDir(files);
297
+ if (await exists(dir)) {
298
+ await ensureStateDir(dir);
299
+ for (const key of await fs3.readdir(dir)) {
300
+ if (!/^[a-f0-9]{24}$/.test(key)) continue;
301
+ const nested = filesForDir(path3.join(dir, key));
302
+ await ensureStateDir(nested.dir);
303
+ const metadata = await readJson(path3.join(nested.dir, "channel.json"));
304
+ const id = roomId(metadata);
305
+ if (!id || channelKey(id) !== key) continue;
306
+ channels.push({ key, roomId: id, ...typeof metadata?.channelName === "string" ? { channelName: metadata.channelName } : {}, files: nested, legacy: false });
307
+ }
308
+ }
309
+ if (await exists(files.inbox)) {
310
+ const id = await legacyRoomId(files);
311
+ if (id) {
312
+ const status = await readJson(files.status);
313
+ channels.push({ key: channelKey(id), roomId: id, ...typeof status?.channelName === "string" ? { channelName: status.channelName } : {}, files, legacy: true });
314
+ }
315
+ }
316
+ return channels.sort((a, b) => (a.channelName ?? "").localeCompare(b.channelName ?? "") || a.roomId.localeCompare(b.roomId));
317
+ }
318
+ async function migrateLegacy(files) {
319
+ await ensureStateDir(files.dir);
320
+ const names = ["cursor.json", "mode.json", "inbox.jsonl"];
321
+ const present = await Promise.all(names.map((name) => exists(path3.join(files.dir, name))));
322
+ if (!present.some(Boolean)) return "none";
323
+ const id = await legacyRoomId(files);
324
+ if (!id) {
325
+ for (const name of names) await removeStateFile(files.dir, name);
326
+ return "discarded";
327
+ }
328
+ const nested = channelFiles(files, id);
329
+ await ensureStateDir(nested.dir);
330
+ const destinationInbox = await exists(nested.inbox);
331
+ if (!destinationInbox && present[0] && await exists(nested.cursor)) {
332
+ const [legacyCursor, channelCursor] = await Promise.all([readCursor(files), readCursor(nested)]);
333
+ if (legacyCursor.deliveredCount !== channelCursor.deliveredCount || legacyCursor.lastDeliveredEventId !== channelCursor.lastDeliveredEventId) {
334
+ const safe = legacyCursor.deliveredCount < channelCursor.deliveredCount ? legacyCursor : channelCursor;
335
+ await writeStateFile(nested.dir, "cursor.json", safe);
336
+ console.warn(`Khala cursor migration: conflicting cursors for ${id}; using earlier position ${safe.deliveredCount} to avoid skipping messages (legacy=${legacyCursor.deliveredCount}, channel=${channelCursor.deliveredCount}).`);
337
+ }
338
+ }
339
+ const status = await readJson(files.status);
340
+ const metadata = await readJson(path3.join(nested.dir, "channel.json"));
341
+ if (!metadata || roomId(metadata) !== id) {
342
+ await writeStateFile(nested.dir, "channel.json", {
343
+ roomId: id,
344
+ ...typeof status?.channelName === "string" ? { channelName: status.channelName } : {},
345
+ joinedAt: (/* @__PURE__ */ new Date()).toISOString()
346
+ });
347
+ }
348
+ if (destinationInbox) {
349
+ if (present[2]) await appendEntries(nested, (await unread(files)).entries);
350
+ for (const name of ["inbox.jsonl", "cursor.json", "mode.json"]) await removeStateFile(files.dir, name);
351
+ return "moved";
352
+ }
353
+ for (const name of names) {
354
+ if (await exists(path3.join(nested.dir, name))) {
355
+ await removeStateFile(files.dir, name);
356
+ continue;
357
+ }
358
+ try {
359
+ await fs3.rename(path3.join(files.dir, name), path3.join(nested.dir, name));
360
+ } catch (error) {
361
+ if (error.code !== "ENOENT") throw new StateError("storage_failed");
362
+ }
363
+ }
364
+ return "moved";
365
+ }
366
+ function resolveChannel(list, ref) {
367
+ const channels = list.map((item) => ({ channel: item.channelName ?? item.roomId, roomId: item.roomId }));
368
+ if (ref === void 0) {
369
+ if (list.length === 1) return { ok: true, channel: list[0] };
370
+ return { ok: false, code: list.length ? "channel_required" : "channel_unknown", channels };
371
+ }
372
+ const exact = list.find((item) => item.roomId === ref);
373
+ if (exact) return { ok: true, channel: exact };
374
+ const normalized = ref.replace(/^#/, "").toLowerCase();
375
+ const matches = list.filter((item) => item.channelName?.replace(/^#/, "").toLowerCase() === normalized);
376
+ if (matches.length === 1) return { ok: true, channel: matches[0] };
377
+ return { ok: false, code: matches.length ? "channel_ambiguous" : "channel_unknown", channels };
378
+ }
379
+
380
+ // src/activity.ts
381
+ init_define_KHALA_BUNDLE();
382
+ import * as fs4 from "node:fs/promises";
383
+ import path4 from "node:path";
384
+ async function readActivity(files) {
385
+ const activity = await readJson(path4.join(files.dir, "activity.json"));
386
+ return activity && ["idle", "busy"].includes(activity.state) && typeof activity.updatedAt === "string" && Number.isFinite(Date.parse(activity.updatedAt)) ? activity : { state: "busy", updatedAt: (/* @__PURE__ */ new Date(0)).toISOString() };
387
+ }
388
+ var USAGE_LIMIT = "usage-limit.json";
389
+ async function markUsageLimit(files, now = () => /* @__PURE__ */ new Date()) {
390
+ await writeJsonAtomic(path4.join(files.dir, USAGE_LIMIT), { at: now().toISOString() });
391
+ }
392
+ async function clearUsageLimit(files) {
393
+ await fs4.rm(path4.join(files.dir, USAGE_LIMIT), { force: true });
394
+ }
395
+ async function usageLimited(dir) {
396
+ return await readJson(path4.join(dir, USAGE_LIMIT)) !== null;
397
+ }
398
+ async function writeActivity(files, state, now = () => /* @__PURE__ */ new Date()) {
399
+ const activity = { state, updatedAt: now().toISOString() };
400
+ await writeJsonAtomic(path4.join(files.dir, "activity.json"), activity);
401
+ return activity;
402
+ }
403
+
130
404
  // src/wake/shared/index.ts
131
405
  init_define_KHALA_BUNDLE();
132
406
 
133
407
  // src/wake/shared/settings.ts
134
408
  init_define_KHALA_BUNDLE();
135
- import * as fs2 from "node:fs/promises";
136
- import path3 from "node:path";
409
+ import * as fs5 from "node:fs/promises";
410
+ import path5 from "node:path";
137
411
  import { randomBytes } from "node:crypto";
138
412
  async function readWakeSettings(root) {
139
413
  try {
140
- const value = JSON.parse(await fs2.readFile(path3.join(root, "wake-settings.json"), "utf8"));
414
+ const value = JSON.parse(await fs5.readFile(path5.join(root, "wake-settings.json"), "utf8"));
141
415
  const entries = (input) => {
142
416
  if (!input || typeof input !== "object" || Array.isArray(input)) return {};
143
417
  return Object.fromEntries(Object.entries(input).filter(([, entry]) => entry && typeof entry === "object" && typeof entry.at === "string"));
@@ -149,14 +423,14 @@ async function readWakeSettings(root) {
149
423
  }
150
424
  }
151
425
  async function writeWakeSettings(root, settings) {
152
- await fs2.mkdir(root, { recursive: true, mode: 448 });
153
- const target2 = path3.join(root, "wake-settings.json");
426
+ await fs5.mkdir(root, { recursive: true, mode: 448 });
427
+ const target2 = path5.join(root, "wake-settings.json");
154
428
  const temp = `${target2}.${randomBytes(8).toString("hex")}.tmp`;
155
429
  try {
156
- await fs2.writeFile(temp, JSON.stringify(settings), { mode: 384 });
157
- await fs2.rename(temp, target2);
430
+ await fs5.writeFile(temp, JSON.stringify(settings), { mode: 384 });
431
+ await fs5.rename(temp, target2);
158
432
  } finally {
159
- await fs2.rm(temp, { force: true });
433
+ await fs5.rm(temp, { force: true });
160
434
  }
161
435
  }
162
436
  async function updateWakeSettings(root, update) {
@@ -175,9 +449,9 @@ async function driverAllowed(root, harness, driver, optIn) {
175
449
  // src/wake/muse-monitor.ts
176
450
  init_define_KHALA_BUNDLE();
177
451
  import { createInterface } from "node:readline";
178
- import * as fs4 from "node:fs/promises";
452
+ import * as fs7 from "node:fs/promises";
179
453
  import os2 from "node:os";
180
- import path5 from "node:path";
454
+ import path7 from "node:path";
181
455
 
182
456
  // src/install/cursor.ts
183
457
  init_define_KHALA_BUNDLE();
@@ -220,12 +494,12 @@ function nodeScriptCommand(platform, node, script) {
220
494
 
221
495
  // src/install/launcher.ts
222
496
  init_define_KHALA_BUNDLE();
223
- function installedLauncher(platform, path8, home, env) {
497
+ function installedLauncher(platform, path10, home, env) {
224
498
  const windows = platform === "win32";
225
499
  const candidate = windows ? env.LOCALAPPDATA : env.XDG_DATA_HOME;
226
- const dataHome = candidate && path8.isAbsolute(candidate) ? candidate : windows ? path8.join(home, "AppData", "Local") : path8.join(home, ".local", "share");
227
- const prefix = path8.join(dataHome, "khala", "npm");
228
- return { prefix, bin: windows ? path8.join(prefix, "khala.cmd") : path8.join(prefix, "bin", "khala") };
500
+ const dataHome = candidate && path10.isAbsolute(candidate) ? candidate : windows ? path10.join(home, "AppData", "Local") : path10.join(home, ".local", "share");
501
+ const prefix = path10.join(dataHome, "khala", "npm");
502
+ return { prefix, bin: windows ? path10.join(prefix, "khala.cmd") : path10.join(prefix, "bin", "khala") };
229
503
  }
230
504
  function launcherShellQuote(platform, bin) {
231
505
  return platform === "win32" ? `"${bin}"` : `'${bin.replaceAll("'", `'\\''`)}'`;
@@ -233,8 +507,8 @@ function launcherShellQuote(platform, bin) {
233
507
 
234
508
  // src/install/managed-file.ts
235
509
  init_define_KHALA_BUNDLE();
236
- import * as fs3 from "node:fs/promises";
237
- import path4 from "node:path";
510
+ import * as fs6 from "node:fs/promises";
511
+ import path6 from "node:path";
238
512
  import { isDeepStrictEqual } from "node:util";
239
513
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
240
514
  var BOM = "\uFEFF";
@@ -273,7 +547,7 @@ var textFormat = {
273
547
  };
274
548
  async function readManaged(file) {
275
549
  try {
276
- return { file, text: await fs3.readFile(file, "utf8") };
550
+ return { file, text: await fs6.readFile(file, "utf8") };
277
551
  } catch (error) {
278
552
  if (error.code === "ENOENT") return { file, text: null };
279
553
  throw error;
@@ -283,11 +557,11 @@ var LEGACY_BACKUP = ".khala-bak";
283
557
  var ManagedFiles = class {
284
558
  store;
285
559
  constructor(stateDir) {
286
- this.store = path4.join(stateDir, "install-originals.json");
560
+ this.store = path6.join(stateDir, "install-originals.json");
287
561
  }
288
562
  async load() {
289
563
  try {
290
- const value = JSON.parse(await fs3.readFile(this.store, "utf8"));
564
+ const value = JSON.parse(await fs6.readFile(this.store, "utf8"));
291
565
  return isObject(value) ? value : {};
292
566
  } catch (error) {
293
567
  if (error.code === "ENOENT") return {};
@@ -296,17 +570,17 @@ var ManagedFiles = class {
296
570
  }
297
571
  async save(recordings) {
298
572
  if (!Object.keys(recordings).length) {
299
- await fs3.rm(this.store, { force: true });
573
+ await fs6.rm(this.store, { force: true });
300
574
  return;
301
575
  }
302
- await fs3.mkdir(path4.dirname(this.store), { recursive: true, mode: 448 });
576
+ await fs6.mkdir(path6.dirname(this.store), { recursive: true, mode: 448 });
303
577
  const temp = `${this.store}.${process.pid}.tmp`;
304
- await fs3.writeFile(temp, JSON.stringify(recordings, null, 2) + "\n", { mode: 384 });
305
- await fs3.rename(temp, this.store);
578
+ await fs6.writeFile(temp, JSON.stringify(recordings, null, 2) + "\n", { mode: 384 });
579
+ await fs6.rename(temp, this.store);
306
580
  }
307
581
  /** The recorded original: `undefined` when nothing is recorded, `null` when the file did not exist. */
308
582
  async original(file) {
309
- const recording = (await this.load())[path4.resolve(file)];
583
+ const recording = (await this.load())[path6.resolve(file)];
310
584
  if (recording) return recording.existed ? Buffer.from(recording.bytes ?? "", "base64") : null;
311
585
  const legacy = await readBytes(file + LEGACY_BACKUP);
312
586
  return legacy ?? void 0;
@@ -318,14 +592,14 @@ var ManagedFiles = class {
318
592
  * the override still never replaces a previously saved recording.
319
593
  */
320
594
  async record(file, options) {
321
- const key = path4.resolve(file);
595
+ const key = path6.resolve(file);
322
596
  const recordings = await this.load();
323
597
  if (recordings[key]) return;
324
598
  const legacy = await readBytes(file + LEGACY_BACKUP);
325
599
  const current = options ? options.original : legacy ?? await readBytes(file);
326
- recordings[key] = current === null ? { existed: false, createdDirs: await missingDirs(path4.dirname(key)) } : { existed: true, bytes: current.toString("base64"), createdDirs: [] };
600
+ recordings[key] = current === null ? { existed: false, createdDirs: await missingDirs(path6.dirname(key)) } : { existed: true, bytes: current.toString("base64"), createdDirs: [] };
327
601
  await this.save(recordings);
328
- if (legacy) await fs3.rm(file + LEGACY_BACKUP, { force: true });
602
+ if (legacy) await fs6.rm(file + LEGACY_BACKUP, { force: true });
329
603
  }
330
604
  /**
331
605
  * Install: records every file's original first (so each knows which directories Khala
@@ -335,8 +609,8 @@ var ManagedFiles = class {
335
609
  for (const { current } of entries) await this.record(current.file);
336
610
  for (const { current, text, mode } of entries) {
337
611
  if (text === current.text) continue;
338
- await fs3.mkdir(path4.dirname(current.file), { recursive: true });
339
- await fs3.writeFile(current.file, text, mode === void 0 ? void 0 : { mode });
612
+ await fs6.mkdir(path6.dirname(current.file), { recursive: true });
613
+ await fs6.writeFile(current.file, text, mode === void 0 ? void 0 : { mode });
340
614
  }
341
615
  }
342
616
  /**
@@ -347,7 +621,7 @@ var ManagedFiles = class {
347
621
  * stripped content. The recording is then dropped.
348
622
  */
349
623
  async restore(current, format, strip) {
350
- const key = path4.resolve(current.file);
624
+ const key = path6.resolve(current.file);
351
625
  const recordings = await this.load();
352
626
  const recording = recordings[key];
353
627
  const original = await this.original(current.file);
@@ -364,25 +638,25 @@ var ManagedFiles = class {
364
638
  let value = strip(format.parse(current.text), originalValue);
365
639
  if (comparable && format.prune) value = format.prune(value, originalValue);
366
640
  if (comparable && original === null && format.empty(value)) {
367
- await fs3.rm(current.file, { force: true });
368
- for (const dir of recording?.createdDirs ?? []) await fs3.rmdir(dir).catch(() => void 0);
641
+ await fs6.rm(current.file, { force: true });
642
+ for (const dir of recording?.createdDirs ?? []) await fs6.rmdir(dir).catch(() => void 0);
369
643
  } else if (comparable && original && format.equal(value, originalValue)) {
370
- if (!original.equals(Buffer.from(current.text, "utf8"))) await fs3.writeFile(current.file, original);
644
+ if (!original.equals(Buffer.from(current.text, "utf8"))) await fs6.writeFile(current.file, original);
371
645
  } else {
372
646
  const text = format.serialize(value, current.text);
373
- if (text !== current.text) await fs3.writeFile(current.file, text);
647
+ if (text !== current.text) await fs6.writeFile(current.file, text);
374
648
  }
375
649
  }
376
650
  if (recording) {
377
651
  delete recordings[key];
378
652
  await this.save(recordings);
379
653
  }
380
- await fs3.rm(current.file + LEGACY_BACKUP, { force: true });
654
+ await fs6.rm(current.file + LEGACY_BACKUP, { force: true });
381
655
  }
382
656
  };
383
657
  async function readBytes(file) {
384
658
  try {
385
- return await fs3.readFile(file);
659
+ return await fs6.readFile(file);
386
660
  } catch (error) {
387
661
  if (error.code === "ENOENT") return null;
388
662
  throw error;
@@ -390,32 +664,32 @@ async function readBytes(file) {
390
664
  }
391
665
  async function missingDirs(dir) {
392
666
  const missing = [];
393
- for (let at = dir; ; at = path4.dirname(at)) {
667
+ for (let at = dir; ; at = path6.dirname(at)) {
394
668
  try {
395
- await fs3.stat(at);
669
+ await fs6.stat(at);
396
670
  return missing;
397
671
  } catch (error) {
398
672
  if (error.code !== "ENOENT") throw error;
399
673
  }
400
674
  missing.push(at);
401
- if (path4.dirname(at) === at) return missing;
675
+ if (path6.dirname(at) === at) return missing;
402
676
  }
403
677
  }
404
678
 
405
679
  // src/install/cursor.ts
406
680
  var CURSOR_HOOK_EVENTS = ["beforeSubmitPrompt", "postToolUse", "stop"];
407
681
  var HOOK_SUFFIX = " hook deliver --harness cursor";
408
- function cursorPaths({ platform, path: path8, home, env }, packageName = "khala-cli") {
409
- const cursorDir = path8.join(home, ".cursor");
682
+ function cursorPaths({ platform, path: path10, home, env }, packageName = "khala-cli") {
683
+ const cursorDir = path10.join(home, ".cursor");
410
684
  const windows = platform === "win32";
411
- const { prefix } = installedLauncher(platform, path8, home, env);
412
- const modules = windows ? path8.join(prefix, "node_modules") : path8.join(prefix, "lib", "node_modules");
685
+ const { prefix } = installedLauncher(platform, path10, home, env);
686
+ const modules = windows ? path10.join(prefix, "node_modules") : path10.join(prefix, "lib", "node_modules");
413
687
  return {
414
688
  cursorDir,
415
689
  prefix,
416
- mcpFile: path8.join(cursorDir, "mcp.json"),
417
- hooksFile: path8.join(cursorDir, "hooks.json"),
418
- script: path8.join(modules, ...packageName.split("/"), "dist", "khala.mjs")
690
+ mcpFile: path10.join(cursorDir, "mcp.json"),
691
+ hooksFile: path10.join(cursorDir, "hooks.json"),
692
+ script: path10.join(modules, ...packageName.split("/"), "dist", "khala.mjs")
419
693
  };
420
694
  }
421
695
  function cursorHookCommand(platform, node, script) {
@@ -514,8 +788,8 @@ var MUSE_WAKE_REQUEST = "muse-monitor-wake.json";
514
788
  var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
515
789
  function museCliPath(env = process.env) {
516
790
  const home = env.HOME || env.USERPROFILE || os2.homedir();
517
- const { prefix } = cursorPaths({ platform: process.platform, path: path5, home, env });
518
- return process.platform === "win32" ? path5.join(prefix, "khala.cmd") : path5.join(prefix, "bin", "khala");
791
+ const { prefix } = cursorPaths({ platform: process.platform, path: path7, home, env });
792
+ return process.platform === "win32" ? path7.join(prefix, "khala.cmd") : path7.join(prefix, "bin", "khala");
519
793
  }
520
794
  function museWatchCommand(sessionId, bin = museCliPath(), platform = process.platform) {
521
795
  const quote = platform === "win32" ? (value) => `'${value.replaceAll("'", "''")}'` : shellQuote;
@@ -550,7 +824,7 @@ function createMuseMonitorDriver() {
550
824
  if (ctx.signal.aborted || (await readActivity(ctx.files)).state !== "idle" || !await monitorArmed(ctx.files)) return "skipped";
551
825
  const epoch = (await readActivity(ctx.files)).updatedAt;
552
826
  for (const dir of monitorStorageCandidates(ctx.files)) {
553
- if ((await readJson(path5.join(dir, "monitor-wake-observed.json")))?.epoch === epoch) return "skipped";
827
+ if ((await readJson(path7.join(dir, "monitor-wake-observed.json")))?.epoch === epoch) return "skipped";
554
828
  }
555
829
  const owner = await monitorOwner(ctx.files);
556
830
  if (!owner) return "skipped";
@@ -558,12 +832,12 @@ function createMuseMonitorDriver() {
558
832
  let journalOffset = 0;
559
833
  if (journal) {
560
834
  try {
561
- journalOffset = (await fs4.stat(journal)).size;
835
+ journalOffset = (await fs7.stat(journal)).size;
562
836
  } catch (error) {
563
837
  if (error.code !== "ENOENT") throw error;
564
838
  }
565
839
  }
566
- await writeJsonAtomic(path5.join(ctx.files.dir, MUSE_WAKE_REQUEST), {
840
+ await writeJsonAtomic(path7.join(ctx.files.dir, MUSE_WAKE_REQUEST), {
567
841
  owner: owner.nonce,
568
842
  line,
569
843
  at: ctx.now,
@@ -578,8 +852,8 @@ function museJournalPath(sessionId, env) {
578
852
  const created = new Date(Number.parseInt(sessionId.replaceAll("-", "").slice(0, 12), 16));
579
853
  const date = [String(created.getFullYear()), String(created.getMonth() + 1).padStart(2, "0"), String(created.getDate()).padStart(2, "0")];
580
854
  const home = env.HOME || env.USERPROFILE || os2.homedir();
581
- const data = env.XDG_DATA_HOME && path5.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path5.join(home, ".local", "share");
582
- return path5.join(data, "muse", "sessions", ...date, sessionId, "session.jsonl");
855
+ const data = env.XDG_DATA_HOME && path7.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path7.join(home, ".local", "share");
856
+ return path7.join(data, "muse", "sessions", ...date, sessionId, "session.jsonl");
583
857
  }
584
858
  async function museStopWakeText(stdin, files, env) {
585
859
  const input = JSON.parse(stdin);
@@ -587,14 +861,14 @@ async function museStopWakeText(stdin, files, env) {
587
861
  return museWakeProof(files, input.session_id, env, input.turn_id);
588
862
  }
589
863
  async function museWakeProof(files, sessionId, env, turnId) {
590
- const request = await readJson(path5.join(files.dir, MUSE_WAKE_REQUEST));
864
+ const request = await readJson(path7.join(files.dir, MUSE_WAKE_REQUEST));
591
865
  if (!request) return void 0;
592
866
  const journal = museJournalPath(sessionId, env);
593
867
  if (!journal) return void 0;
594
868
  if (!Number.isSafeInteger(request.journalOffset) || request.journalOffset < 0) return void 0;
595
869
  let handle;
596
870
  try {
597
- handle = await fs4.open(journal, "r");
871
+ handle = await fs7.open(journal, "r");
598
872
  } catch (error) {
599
873
  if (error.code === "ENOENT") return void 0;
600
874
  throw error;
@@ -624,17 +898,17 @@ async function museWakeProof(files, sessionId, env, turnId) {
624
898
 
625
899
  // src/wake/qwen-socket.ts
626
900
  init_define_KHALA_BUNDLE();
627
- import * as fs6 from "node:fs/promises";
901
+ import * as fs9 from "node:fs/promises";
628
902
  import net from "node:net";
629
903
  import os3 from "node:os";
630
- import path6 from "node:path";
904
+ import path8 from "node:path";
631
905
  import { randomBytes as randomBytes2, randomUUID as randomUUID2 } from "node:crypto";
632
906
 
633
907
  // src/wake/qwen-transcript.ts
634
908
  init_define_KHALA_BUNDLE();
635
- import * as fs5 from "node:fs/promises";
909
+ import * as fs8 from "node:fs/promises";
636
910
  async function qwenNotification(filePath) {
637
- const file = await fs5.open(filePath, "r");
911
+ const file = await fs8.open(filePath, "r");
638
912
  let text;
639
913
  try {
640
914
  const stat3 = await file.stat();
@@ -666,23 +940,23 @@ async function verifyQwenTranscript(dir, filePath, now) {
666
940
  }
667
941
 
668
942
  // src/wake/qwen-socket.ts
669
- var qwenHome = (env) => env.QWEN_HOME ? path6.resolve(env.QWEN_HOME) : path6.join(env.HOME ?? os3.homedir(), ".qwen");
943
+ var qwenHome = (env) => env.QWEN_HOME ? path8.resolve(env.QWEN_HOME) : path8.join(env.HOME ?? os3.homedir(), ".qwen");
670
944
  async function qwenInboundAllowed(env, workspace = env.PWD ?? process.cwd()) {
671
- for (const file of [path6.join(qwenHome(env), "settings.json"), path6.join(workspace, ".qwen", "settings.json")]) {
945
+ for (const file of [path8.join(qwenHome(env), "settings.json"), path8.join(workspace, ".qwen", "settings.json")]) {
672
946
  const config = await readJson(file);
673
947
  if (["hold", "refuse"].includes(config?.agents?.crossSessionInbound ?? "") || config?.agents?.crossSessionMessaging === false) return false;
674
948
  }
675
949
  return true;
676
950
  }
677
- var qwenControllerFile = (env) => path6.join(stateRoot(env), "qwen", "controller.json");
951
+ var qwenControllerFile = (env) => path8.join(stateRoot(env), "qwen", "controller.json");
678
952
  async function qwenSession(env) {
679
953
  const socket = env.QWEN_CODE_MESSAGING_SOCKET;
680
954
  if (!socket) return void 0;
681
955
  try {
682
- const dir = path6.join(qwenHome(env), "sessions");
683
- for (const file of await fs6.readdir(dir)) {
956
+ const dir = path8.join(qwenHome(env), "sessions");
957
+ for (const file of await fs9.readdir(dir)) {
684
958
  if (!/^\d+\.json$/u.test(file)) continue;
685
- const record = await readJson(path6.join(dir, file));
959
+ const record = await readJson(path8.join(dir, file));
686
960
  if (record?.ipcPath === socket && typeof record.sessionId === "string" && SESSION_ID_PATTERN.test(record.sessionId)) return record.sessionId;
687
961
  }
688
962
  } catch {
@@ -694,7 +968,7 @@ async function target(env) {
694
968
  if (!sessionId) return void 0;
695
969
  try {
696
970
  const file = qwenControllerFile(env);
697
- const stat3 = await fs6.lstat(file);
971
+ const stat3 = await fs9.lstat(file);
698
972
  if (!stat3.isFile() || (stat3.mode & 63) !== 0) return void 0;
699
973
  const credential = await readJson(file);
700
974
  if (!credential?.token || !/^qpc_[0-9a-f]{64}$/u.test(credential.token)) return void 0;
@@ -719,10 +993,10 @@ function qwenFrames(target2, line, from, replyToken, msgId) {
719
993
  }
720
994
  async function sendQwenWake(target2, line, signal, options = {}) {
721
995
  if (signal.aborted) return "failed";
722
- const dir = await fs6.mkdtemp(path6.join(options.runtimeDir ?? os3.tmpdir(), "khala-qw-"));
723
- const from = path6.join(dir, "r.sock");
996
+ const dir = await fs9.mkdtemp(path8.join(options.runtimeDir ?? os3.tmpdir(), "khala-qw-"));
997
+ const from = path8.join(dir, "r.sock");
724
998
  if (Buffer.byteLength(from) > 103) {
725
- await fs6.rm(dir, { recursive: true, force: true });
999
+ await fs9.rm(dir, { recursive: true, force: true });
726
1000
  return "failed";
727
1001
  }
728
1002
  const replyToken = randomBytes2(32).toString("hex");
@@ -778,7 +1052,7 @@ async function sendQwenWake(target2, line, signal, options = {}) {
778
1052
  server.once("error", reject);
779
1053
  server.listen(from, resolve);
780
1054
  });
781
- await fs6.chmod(from, 384);
1055
+ await fs9.chmod(from, 384);
782
1056
  if (signal.aborted) return "failed";
783
1057
  sender = net.createConnection(target2.socket);
784
1058
  sender.on("error", () => finish("failed"));
@@ -794,20 +1068,20 @@ async function sendQwenWake(target2, line, signal, options = {}) {
794
1068
  sender?.destroy();
795
1069
  for (const socket of peers) socket.destroy();
796
1070
  await new Promise((resolve) => server.close(() => resolve()));
797
- await fs6.rm(dir, { recursive: true, force: true });
1071
+ await fs9.rm(dir, { recursive: true, force: true });
798
1072
  }
799
1073
  }
800
1074
  function createQwenSocketDriver(deps = {}) {
801
1075
  const resolve = deps.resolve ?? target;
802
1076
  const policy = async (env) => {
803
- const config = await readJson(path6.join(qwenHome(env), "settings.json"));
1077
+ const config = await readJson(path8.join(qwenHome(env), "settings.json"));
804
1078
  return config?.agents;
805
1079
  };
806
1080
  const reason = async (ctx) => {
807
1081
  const config = await policy(ctx.env);
808
1082
  if (config?.crossSessionInbound === "hold") return "qwen_held";
809
1083
  if ((deps.platform ?? process.platform) === "win32") return "windows";
810
- const receipt = await readJson(path6.join(ctx.files.dir, "qwen-receipt.json"));
1084
+ const receipt = await readJson(path8.join(ctx.files.dir, "qwen-receipt.json"));
811
1085
  if (receipt?.status === "held" && config?.crossSessionInbound !== "accept") return "qwen_held";
812
1086
  if (config?.crossSessionMessaging === false || config?.crossSessionInbound === "refuse") return "qwen_refused";
813
1087
  if (!deps.resolve && !ctx.env.QWEN_CODE_MESSAGING_SOCKET) return "qwen_session_missing";
@@ -821,15 +1095,15 @@ function createQwenSocketDriver(deps = {}) {
821
1095
  deadlineMs: 3e4,
822
1096
  verification: "transcript",
823
1097
  async verify(ctx) {
824
- const journal = await readJson(path6.join(ctx.files.dir, "wake-journal.json"));
825
- const receipt = await readJson(path6.join(ctx.files.dir, "qwen-receipt.json"));
1098
+ const journal = await readJson(path8.join(ctx.files.dir, "wake-journal.json"));
1099
+ const receipt = await readJson(path8.join(ctx.files.dir, "qwen-receipt.json"));
826
1100
  if (!journal?.attempts?.some((attempt) => attempt.driver === "socket") && receipt?.status !== "held") return;
827
- const transcript = await readJson(path6.join(ctx.files.dir, "qwen-transcript.json"));
1101
+ const transcript = await readJson(path8.join(ctx.files.dir, "qwen-transcript.json"));
828
1102
  if (!transcript?.path) return;
829
1103
  try {
830
1104
  const text = await verifyQwenTranscript(ctx.files.dir, transcript.path, ctx.now);
831
1105
  if (receipt?.status === "held" && receipt.nonce && text?.includes(`(k-${receipt.nonce})`)) {
832
- await writeJsonAtomic(path6.join(ctx.files.dir, "qwen-receipt.json"), { status: "delivered" });
1106
+ await writeJsonAtomic(path8.join(ctx.files.dir, "qwen-receipt.json"), { status: "delivered" });
833
1107
  }
834
1108
  } catch {
835
1109
  }
@@ -843,7 +1117,7 @@ function createQwenSocketDriver(deps = {}) {
843
1117
  const status = await (deps.send ?? sendQwenWake)(destination, line, ctx.signal);
844
1118
  await ensureStateDir(ctx.files.dir);
845
1119
  const nonce = line.match(/\(k-([0-9a-f]{8})\)$/u)?.[1];
846
- await writeJsonAtomic(path6.join(ctx.files.dir, "qwen-receipt.json"), { status, nonce });
1120
+ await writeJsonAtomic(path8.join(ctx.files.dir, "qwen-receipt.json"), { status, nonce });
847
1121
  if (nonce && status === "held") await cancelAttempt(ctx.files.dir, nonce);
848
1122
  else if (nonce && status !== "delivered") await failAttempt(ctx.files.dir, nonce, ctx.now);
849
1123
  }
@@ -853,12 +1127,13 @@ function createQwenSocketDriver(deps = {}) {
853
1127
  // src/watch.ts
854
1128
  var MARKER = "monitor.json";
855
1129
  var OBSERVATION = "monitor-cursor.json";
1130
+ var TICK_MS = 2e3;
856
1131
  var NONCE_PATTERN = /^[a-f0-9-]{36}$/;
857
- var leaseFile = (dir, nonce) => path7.join(dir, `monitor-${nonce}.json`);
1132
+ var leaseFile = (dir, nonce) => path9.join(dir, `monitor-${nonce}.json`);
858
1133
  async function monitorOwner(files) {
859
1134
  const owners = [];
860
1135
  for (const dir of monitorStorageCandidates(files)) {
861
- const owner = await readJson(path7.join(dir, MARKER));
1136
+ const owner = await readJson(path9.join(dir, MARKER));
862
1137
  if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 0 || typeof owner.nonce !== "string" || !NONCE_PATTERN.test(owner.nonce)) continue;
863
1138
  const lease = await readJson(leaseFile(dir, owner.nonce));
864
1139
  if (lease?.nonce !== owner.nonce) continue;
@@ -892,7 +1167,7 @@ async function watchSession(files, io) {
892
1167
  const wake = await readWakeSettings(stateRoot(env));
893
1168
  return await qwenInboundAllowed(env) && !wake.off["qwen/socket"] && !wake.off["qwen/background-shell"];
894
1169
  };
895
- let watcher;
1170
+ const watchers = [];
896
1171
  let timer;
897
1172
  let running;
898
1173
  let dirty = false;
@@ -907,7 +1182,7 @@ async function watchSession(files, io) {
907
1182
  if (done) return;
908
1183
  done = true;
909
1184
  code = exit;
910
- watcher?.close();
1185
+ for (const w of watchers) w.close();
911
1186
  clearInterval(timer);
912
1187
  if (!qwen || exit !== 0) io.stderr?.(`khala watch: stopped (${reason}). Do not re-arm.`);
913
1188
  finish();
@@ -977,7 +1252,7 @@ async function watchSession(files, io) {
977
1252
  }
978
1253
  const mode = await readListeningMode(target2);
979
1254
  const entries = await readEntries(target2);
980
- const observed = await readJson(path7.join(await observationDir(target2), OBSERVATION));
1255
+ const observed = await readJson(path9.join(await observationDir(target2), OBSERVATION));
981
1256
  const position = observed?.userId === session.userId && observed?.roomId === session.roomId && Number.isSafeInteger(observed.count) && observed.count >= 0 && observed.count <= entries.length && (observed.count === 0 || entries[observed.count - 1]?.eventId === observed.lastEventId) ? observed.count : 0;
982
1257
  const baseline = initialDelivered.get(target2.dir).cursor;
983
1258
  const initialCount = baseline.deliveredCount === 0 || entries[baseline.deliveredCount - 1]?.eventId === baseline.lastDeliveredEventId ? baseline.deliveredCount : 0;
@@ -1003,7 +1278,7 @@ async function watchSession(files, io) {
1003
1278
  }
1004
1279
  if (position !== entries.length && !done && await ownsSession() && JSON.stringify(await readJson(target2.session)) === JSON.stringify(latestSession)) {
1005
1280
  try {
1006
- await writeJsonAtomic(path7.join(await observationDir(target2), OBSERVATION), {
1281
+ await writeJsonAtomic(path9.join(await observationDir(target2), OBSERVATION), {
1007
1282
  userId: session.userId,
1008
1283
  roomId: session.roomId,
1009
1284
  count: entries.length,
@@ -1015,7 +1290,7 @@ async function watchSession(files, io) {
1015
1290
  }
1016
1291
  }
1017
1292
  if (io.harness === "muse" && !done && await ownsSession()) {
1018
- const request = await readJson(path7.join(files.dir, MUSE_WAKE_REQUEST));
1293
+ const request = await readJson(path9.join(files.dir, MUSE_WAKE_REQUEST));
1019
1294
  const now = (io.now ?? Date.now)();
1020
1295
  const wakeNonce = typeof request?.line === "string" ? /\(k-([a-f0-9]{8})\)$/.exec(request.line)?.[1] : void 0;
1021
1296
  if (request?.owner === nonce && wakeNonce && wakeLine(wakeNonce) === request.line && request.line !== emittedRequest && now >= request.at && now < request.deadline && await driverAllowed(stateRoot(io.env ?? process.env), "muse", "monitor", false) && !(await readWakeState(files.dir)).monitor?.disabled && (await readActivity(files)).state === "idle") {
@@ -1033,7 +1308,7 @@ async function watchSession(files, io) {
1033
1308
  const activity = await readActivity(files);
1034
1309
  if (eligible && await ownsSession() && activity.state === "idle") {
1035
1310
  const epoch = activity.updatedAt;
1036
- const dedupFile = path7.join(storage, "monitor-wake-observed.json");
1311
+ const dedupFile = path9.join(storage, "monitor-wake-observed.json");
1037
1312
  const observed = await readJson(dedupFile);
1038
1313
  if (observed?.epoch === epoch) return;
1039
1314
  await writeJsonAtomic(dedupFile, { epoch, line: request.line });
@@ -1058,15 +1333,16 @@ async function watchSession(files, io) {
1058
1333
  });
1059
1334
  };
1060
1335
  let published = false;
1061
- watcher = watchDirectory(files.dir, (_event, filename) => {
1062
- if (published && (filename === null || [MARKER, MUSE_WAKE_REQUEST, "inbox.jsonl", "cursor.json", "mode.json", "session.json", "status.json"].includes(String(filename)))) notify();
1063
- });
1064
- watcher.on("error", (error) => stop(3, errorReason(error)));
1336
+ const onChange = (_event, filename) => {
1337
+ if (published && (filename === null || [MARKER, MUSE_WAKE_REQUEST, "inbox.jsonl", "cursor.json", "mode.json", "session.json", "status.json", "activity.json"].includes(path9.basename(String(filename))))) notify();
1338
+ };
1339
+ watchers.push(watchDirectory(files.dir, onChange), watchDirectory(files.dir, { recursive: true }, onChange));
1340
+ for (const w of watchers) w.on("error", (error) => stop(3, errorReason(error)));
1065
1341
  lastHeartbeat = Date.now();
1066
1342
  await writeJsonAtomic(leaseFile(storage, nonce), { nonce, pid: process.pid, heartbeatAt: new Date(lastHeartbeat).toISOString() });
1067
- await writeJsonAtomic(path7.join(storage, MARKER), { nonce, pid: process.pid, startedAt: Date.now() });
1343
+ await writeJsonAtomic(path9.join(storage, MARKER), { nonce, pid: process.pid, startedAt: Date.now() });
1068
1344
  published = true;
1069
- timer = setInterval(notify, 100);
1345
+ timer = setInterval(notify, TICK_MS);
1070
1346
  io.signal.addEventListener("abort", abort, { once: true });
1071
1347
  if (io.signal.aborted) stop();
1072
1348
  else notify();
@@ -1078,13 +1354,17 @@ async function watchSession(files, io) {
1078
1354
  return 3;
1079
1355
  } finally {
1080
1356
  clearInterval(timer);
1081
- watcher?.close();
1357
+ for (const w of watchers) w.close();
1082
1358
  io.signal.removeEventListener("abort", abort);
1083
- await fs7.unlink(leaseFile(storage, nonce)).catch((error) => {
1359
+ await fs10.unlink(leaseFile(storage, nonce)).catch((error) => {
1084
1360
  if (!["ENOENT", "EACCES", "EROFS"].includes(error.code ?? "")) throw error;
1085
1361
  });
1086
1362
  }
1087
1363
  }
1364
+ function claudeWatchCommand(sessionId, env) {
1365
+ const root = env.CLAUDE_PLUGIN_ROOT;
1366
+ return `${root ? `'${path9.join(root, "bin", "khala").replaceAll("'", "'\\''")}'` : "khala"} watch --harness claude --session ${sessionId}`;
1367
+ }
1088
1368
  var WATCH_USAGE = "usage: khala watch [--harness <id>] [--session <id>]";
1089
1369
  async function run(argv) {
1090
1370
  if (argv.length === 1 && ["--help", "-h"].includes(argv[0])) {
@@ -1097,8 +1377,8 @@ ${WATCH_USAGE}`);
1097
1377
  return 1;
1098
1378
  };
1099
1379
  if (argv.length !== 0 && !(argv.length === 2 && ["--session", "--harness"].includes(argv[0])) && !(argv.length === 4 && argv[0] === "--harness" && argv[2] === "--session")) return invalid("invalid_arguments");
1100
- const { resolveHarness, resolveSessionId } = await import("./chunk-AU2FBBUY.mjs");
1101
- const { adapterFor } = await import("./chunk-KXLGRISD.mjs");
1380
+ const { resolveHarness, resolveSessionId } = await import("./chunk-LZXXWDKB.mjs");
1381
+ const { adapterFor } = await import("./chunk-IH5ALDEX.mjs");
1102
1382
  const harness = resolveHarness(argv, process.env);
1103
1383
  if (harness === "invalid" || !adapterFor(harness)?.codec) return invalid("invalid_harness");
1104
1384
  const index = argv.indexOf("--session");
@@ -1123,9 +1403,29 @@ ${WATCH_USAGE}`);
1123
1403
  }
1124
1404
 
1125
1405
  export {
1126
- readWakeSettings,
1127
- updateWakeSettings,
1128
- driverAllowed,
1406
+ toEventInboxEntry,
1407
+ createEventKeyFilter,
1408
+ isWakeEntry,
1409
+ mentions,
1410
+ wakesAgent,
1411
+ readEntries,
1412
+ readCursor,
1413
+ unread,
1414
+ wakeStart,
1415
+ unwoken,
1416
+ markNudged,
1417
+ advanceCursor,
1418
+ appendInbox,
1419
+ unreadCount,
1420
+ channelKey,
1421
+ listChannels,
1422
+ migrateLegacy,
1423
+ resolveChannel,
1424
+ readActivity,
1425
+ markUsageLimit,
1426
+ clearUsageLimit,
1427
+ usageLimited,
1428
+ writeActivity,
1129
1429
  readModeState,
1130
1430
  delegateChannel,
1131
1431
  isDelegated,
@@ -1133,9 +1433,13 @@ export {
1133
1433
  applyArchived,
1134
1434
  ATTENTION,
1135
1435
  applyListeningMode,
1436
+ readWakeSettings,
1437
+ updateWakeSettings,
1438
+ driverAllowed,
1136
1439
  CURSOR_WORKSPACE_ENV,
1137
1440
  CURSOR_DEFAULT_SESSION,
1138
1441
  cursorSessionId,
1442
+ nodeScriptCommand,
1139
1443
  installedLauncher,
1140
1444
  launcherShellQuote,
1141
1445
  formatJson,
@@ -1143,23 +1447,23 @@ export {
1143
1447
  textFormat,
1144
1448
  readManaged,
1145
1449
  ManagedFiles,
1450
+ cursorPaths,
1451
+ installCursor,
1452
+ museCliPath,
1453
+ museWatchCommand,
1454
+ museMonitorInstruction,
1455
+ createMuseMonitorDriver,
1456
+ museStopWakeText,
1146
1457
  verifyQwenTranscript,
1147
1458
  qwenHome,
1148
1459
  qwenInboundAllowed,
1149
1460
  qwenControllerFile,
1150
1461
  qwenSession,
1151
1462
  createQwenSocketDriver,
1152
- nodeScriptCommand,
1153
- cursorPaths,
1154
- installCursor,
1155
1463
  monitorOwner,
1156
1464
  monitorArmed,
1157
1465
  watchSession,
1466
+ claudeWatchCommand,
1158
1467
  WATCH_USAGE,
1159
- run,
1160
- museCliPath,
1161
- museWatchCommand,
1162
- museMonitorInstruction,
1163
- createMuseMonitorDriver,
1164
- museStopWakeText
1468
+ run
1165
1469
  };