khala-cli 0.4.9 → 0.4.11

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 (48) hide show
  1. package/dist/THIRD_PARTY_NOTICES.txt +28 -0
  2. package/dist/{chunk-DJM23SIU.mjs → chunk-3KHLSF4A.mjs} +7 -8
  3. package/dist/{chunk-PDZ5Z5ZE.mjs → chunk-4CENUF7M.mjs} +24 -9
  4. package/dist/{chunk-QKWEZXR3.mjs → chunk-4PUNVPC3.mjs} +12 -4
  5. package/dist/{chunk-WDOHRV3D.mjs → chunk-7FFHZ7EI.mjs} +14 -14
  6. package/dist/chunk-AQX2OYEF.mjs +19 -0
  7. package/dist/{chunk-ITFD3JZS.mjs → chunk-BVG7PJNZ.mjs} +424 -78
  8. package/dist/{chunk-NGSYOVQ4.mjs → chunk-CXVANPY5.mjs} +2 -2
  9. package/dist/chunk-CYINL2V3.mjs +20 -0
  10. package/dist/{chunk-KMLXOPBQ.mjs → chunk-E5QRD6W3.mjs} +4 -2
  11. package/dist/{chunk-FO5GXCST.mjs → chunk-FGHPOKXG.mjs} +5 -5
  12. package/dist/{chunk-FZEH2IXZ.mjs → chunk-GVZD4TLW.mjs} +180 -26
  13. package/dist/{chunk-V4F3SYKW.mjs → chunk-H2L5GQ5W.mjs} +25 -17
  14. package/dist/chunk-HZH74L5T.mjs +901 -0
  15. package/dist/{chunk-NPSQNCOY.mjs → chunk-JNSB3E6C.mjs} +13 -4
  16. package/dist/{chunk-7R3EIDGR.mjs → chunk-LDW4HOQW.mjs} +1 -1
  17. package/dist/chunk-LNKI3CZO.mjs +15 -0
  18. package/dist/{chunk-TEFOQXBK.mjs → chunk-MAIMFERG.mjs} +3 -3
  19. package/dist/chunk-MYIZQCRW.mjs +27 -0
  20. package/dist/{chunk-TANCKTDK.mjs → chunk-N6UYX3SY.mjs} +72 -5
  21. package/dist/{chunk-6BO3JSUP.mjs → chunk-NE4S7N3R.mjs} +2 -2
  22. package/dist/{chunk-BLCRVRIK.mjs → chunk-NJ5ICCVZ.mjs} +456 -87
  23. package/dist/{chunk-3R6HFSLL.mjs → chunk-NN5RHU6C.mjs} +92 -38
  24. package/dist/{chunk-NIX5F4LT.mjs → chunk-NOKTVPSY.mjs} +330 -66
  25. package/dist/{chunk-3KDOTR54.mjs → chunk-NUQSDSVT.mjs} +1 -1
  26. package/dist/{chunk-BEO7WIIM.mjs → chunk-OSVXLZKJ.mjs} +28 -8
  27. package/dist/{chunk-SSO5RFOX.mjs → chunk-R5NDTJRA.mjs} +1 -1
  28. package/dist/{chunk-K7V7X6WU.mjs → chunk-TVQ4MSEI.mjs} +21 -4
  29. package/dist/{chunk-BV45NK7K.mjs → chunk-VWWI4S5A.mjs} +100 -6
  30. package/dist/{chunk-UK5GJO4Y.mjs → chunk-WA5LB5GN.mjs} +6 -5
  31. package/dist/chunk-X3BAMHAR.mjs +14 -0
  32. package/dist/{chunk-PAJ4JJPV.mjs → chunk-YDEZFVF7.mjs} +43 -6
  33. package/dist/{chunk-GSZADJMW.mjs → chunk-YFX5NDSL.mjs} +338 -81
  34. package/dist/{chunk-ANSR4IAW.mjs → chunk-YIKJI26V.mjs} +2 -2
  35. package/dist/{chunk-T7IL7V7Q.mjs → chunk-YNUCRFM2.mjs} +13 -10
  36. package/dist/iterm2_send.py +56 -0
  37. package/dist/khala.mjs +11 -11
  38. package/dist/web/assets/local-DPcMk9Xz.css +1 -0
  39. package/dist/web/assets/local-SDOFp8OA.js +9 -0
  40. package/dist/web/index.html +2 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-47XNLAJG.mjs +0 -38
  43. package/dist/chunk-LBT47HLZ.mjs +0 -19
  44. package/dist/chunk-P3YIN7G7.mjs +0 -26
  45. package/dist/chunk-XBQDIKB5.mjs +0 -18
  46. package/dist/chunk-XUCWSYVS.mjs +0 -148
  47. package/dist/web/assets/local-CFzRHZpB.js +0 -9
  48. package/dist/web/assets/local-ciypUFoK.css +0 -1
@@ -2,40 +2,47 @@ import { createRequire as __khalaCreateRequire } from 'node:module'; const requi
2
2
  import {
3
3
  DEFAULT_LISTENING_MODE,
4
4
  decodeListeningMode
5
- } from "./chunk-NGSYOVQ4.mjs";
5
+ } from "./chunk-CXVANPY5.mjs";
6
6
  import {
7
7
  advanceCursor,
8
+ isWakeEntry,
8
9
  listChannels,
10
+ mentions,
9
11
  readActivity,
10
12
  readCursor,
11
13
  readEntries,
12
- unread
13
- } from "./chunk-TANCKTDK.mjs";
14
+ unread,
15
+ wakesAgent
16
+ } from "./chunk-N6UYX3SY.mjs";
14
17
  import {
15
18
  SESSION_ID_PATTERN,
16
19
  StateError,
20
+ ensureStateDir,
17
21
  readJson,
18
22
  readWatcherStatus,
19
23
  sessionFiles,
20
24
  stateRoot,
21
25
  writeJsonAtomic
22
- } from "./chunk-NPSQNCOY.mjs";
26
+ } from "./chunk-JNSB3E6C.mjs";
23
27
  import {
28
+ cancelAttempt,
29
+ confirmWakeText,
30
+ failAttempt,
24
31
  readWakeState,
25
32
  settleAttempts,
26
33
  wakeLine,
27
34
  withWakeLock
28
- } from "./chunk-K7V7X6WU.mjs";
35
+ } from "./chunk-TVQ4MSEI.mjs";
29
36
  import {
30
37
  init_define_KHALA_BUNDLE
31
- } from "./chunk-7R3EIDGR.mjs";
38
+ } from "./chunk-LDW4HOQW.mjs";
32
39
 
33
40
  // src/watch.ts
34
41
  init_define_KHALA_BUNDLE();
35
- import * as fs5 from "node:fs/promises";
42
+ import * as fs7 from "node:fs/promises";
36
43
  import { watch as watchDirectory } from "node:fs";
37
- import path5 from "node:path";
38
- import { randomUUID as randomUUID2 } from "node:crypto";
44
+ import path6 from "node:path";
45
+ import { randomUUID as randomUUID3 } from "node:crypto";
39
46
 
40
47
  // src/monitor-storage.ts
41
48
  init_define_KHALA_BUNDLE();
@@ -110,11 +117,11 @@ async function readWakeSettings(root) {
110
117
  }
111
118
  async function writeWakeSettings(root, settings) {
112
119
  await fs2.mkdir(root, { recursive: true, mode: 448 });
113
- const target = path2.join(root, "wake-settings.json");
114
- const temp = `${target}.${randomBytes(8).toString("hex")}.tmp`;
120
+ const target2 = path2.join(root, "wake-settings.json");
121
+ const temp = `${target2}.${randomBytes(8).toString("hex")}.tmp`;
115
122
  try {
116
123
  await fs2.writeFile(temp, JSON.stringify(settings), { mode: 384 });
117
- await fs2.rename(temp, target);
124
+ await fs2.rename(temp, target2);
118
125
  } finally {
119
126
  await fs2.rm(temp, { force: true });
120
127
  }
@@ -178,6 +185,19 @@ function nodeScriptCommand(platform, node, script) {
178
185
  return `${quote(node)} ${quote(script)}`;
179
186
  }
180
187
 
188
+ // src/install/launcher.ts
189
+ init_define_KHALA_BUNDLE();
190
+ function installedLauncher(platform, path7, home, env) {
191
+ const windows = platform === "win32";
192
+ const candidate = windows ? env.LOCALAPPDATA : env.XDG_DATA_HOME;
193
+ const dataHome = candidate && path7.isAbsolute(candidate) ? candidate : windows ? path7.join(home, "AppData", "Local") : path7.join(home, ".local", "share");
194
+ const prefix = path7.join(dataHome, "khala", "npm");
195
+ return { prefix, bin: windows ? path7.join(prefix, "khala.cmd") : path7.join(prefix, "bin", "khala") };
196
+ }
197
+ function launcherShellQuote(platform, bin) {
198
+ return platform === "win32" ? `"${bin}"` : `'${bin.replaceAll("'", `'\\''`)}'`;
199
+ }
200
+
181
201
  // src/install/managed-file.ts
182
202
  init_define_KHALA_BUNDLE();
183
203
  import * as fs3 from "node:fs/promises";
@@ -352,18 +372,17 @@ async function missingDirs(dir) {
352
372
  // src/install/cursor.ts
353
373
  var CURSOR_HOOK_EVENTS = ["beforeSubmitPrompt", "postToolUse", "stop"];
354
374
  var HOOK_SUFFIX = " hook deliver --harness cursor";
355
- function cursorPaths({ platform, path: path6, home, env }, packageName = "khala-cli") {
356
- const cursorDir = path6.join(home, ".cursor");
375
+ function cursorPaths({ platform, path: path7, home, env }, packageName = "khala-cli") {
376
+ const cursorDir = path7.join(home, ".cursor");
357
377
  const windows = platform === "win32";
358
- const dataHome = windows ? env.LOCALAPPDATA && path6.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : path6.join(home, "AppData", "Local") : env.XDG_DATA_HOME && path6.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path6.join(home, ".local", "share");
359
- const prefix = path6.join(dataHome, "khala", "npm");
360
- const modules = windows ? path6.join(prefix, "node_modules") : path6.join(prefix, "lib", "node_modules");
378
+ const { prefix } = installedLauncher(platform, path7, home, env);
379
+ const modules = windows ? path7.join(prefix, "node_modules") : path7.join(prefix, "lib", "node_modules");
361
380
  return {
362
381
  cursorDir,
363
382
  prefix,
364
- mcpFile: path6.join(cursorDir, "mcp.json"),
365
- hooksFile: path6.join(cursorDir, "hooks.json"),
366
- script: path6.join(modules, ...packageName.split("/"), "dist", "khala.mjs")
383
+ mcpFile: path7.join(cursorDir, "mcp.json"),
384
+ hooksFile: path7.join(cursorDir, "hooks.json"),
385
+ script: path7.join(modules, ...packageName.split("/"), "dist", "khala.mjs")
367
386
  };
368
387
  }
369
388
  function cursorHookCommand(platform, node, script) {
@@ -465,11 +484,12 @@ function museCliPath(env = process.env) {
465
484
  const { prefix } = cursorPaths({ platform: process.platform, path: path4, home, env });
466
485
  return process.platform === "win32" ? path4.join(prefix, "khala.cmd") : path4.join(prefix, "bin", "khala");
467
486
  }
468
- function museWatchCommand(sessionId, bin = museCliPath()) {
469
- return `${shellQuote(bin)} watch --harness muse --session ${shellQuote(sessionId ?? "<current session id from khala_status>")}`;
487
+ function museWatchCommand(sessionId, bin = museCliPath(), platform = process.platform) {
488
+ const quote = platform === "win32" ? (value) => `'${value.replaceAll("'", "''")}'` : shellQuote;
489
+ return `${platform === "win32" ? "& " : ""}${quote(bin)} watch --harness muse --session ${quote(sessionId ?? "<current session id from khala_status>")}`;
470
490
  }
471
491
  function museMonitorInstruction(sessionId, command = museWatchCommand(sessionId)) {
472
- return `start Muse's monitor tool with command ${JSON.stringify(command)}, persistent: true, wake_delay_ms: 0, show_lines: true. Always pass the current session id explicitly; the monitor shell does not inherit MUSE_SESSION_ID. Keep one monitor per session; If it prints "Do not re-arm", report the reason and do not restart it. Notifications are wake notices; use khala_read for channel content. Never run the watcher as a foreground shell command.`;
492
+ return `start Muse's monitor tool with command ${JSON.stringify(command)}, persistent: true, wake_delay_ms: 0, show_lines: true. Always pass the current session id explicitly; the monitor shell does not inherit MUSE_SESSION_ID. Keep one monitor per session; If it prints "Do not re-arm", report the reason and do not restart it. Notifications are wake notices, not channel messages. Do not call khala_read or reply to the wake notice; Khala hooks supply the <khala-channel-messages> frame. End the turn so the Stop hook can deliver the frame in sync mode; in steer mode it can arrive after a tool. Use khala_read only for history or owner-requested manual reads; if you already replied to a message from history, do not reply again when the same message appears in a hook frame. Never run the watcher as a foreground shell command.`;
473
493
  }
474
494
  function createMuseMonitorDriver() {
475
495
  return {
@@ -569,15 +589,243 @@ async function museWakeProof(files, sessionId, env, turnId) {
569
589
  return void 0;
570
590
  }
571
591
 
592
+ // src/wake/qwen-socket.ts
593
+ init_define_KHALA_BUNDLE();
594
+ import * as fs6 from "node:fs/promises";
595
+ import net from "node:net";
596
+ import os3 from "node:os";
597
+ import path5 from "node:path";
598
+ import { randomBytes as randomBytes2, randomUUID as randomUUID2 } from "node:crypto";
599
+
600
+ // src/wake/qwen-transcript.ts
601
+ init_define_KHALA_BUNDLE();
602
+ import * as fs5 from "node:fs/promises";
603
+ async function qwenNotification(filePath) {
604
+ const file = await fs5.open(filePath, "r");
605
+ let text;
606
+ try {
607
+ const stat3 = await file.stat();
608
+ const start = Math.max(0, stat3.size - 1024 * 1024);
609
+ const buffer = Buffer.alloc(Math.min(stat3.size, 1024 * 1024));
610
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, start);
611
+ text = buffer.subarray(0, bytesRead).toString("utf8");
612
+ if (start) text = text.slice(text.indexOf("\n") + 1);
613
+ } finally {
614
+ await file.close();
615
+ }
616
+ const notifications = [];
617
+ for (const line of text.trimEnd().split("\n").reverse()) {
618
+ let entry;
619
+ try {
620
+ entry = JSON.parse(line);
621
+ } catch {
622
+ continue;
623
+ }
624
+ if (entry?.type !== "user" || entry.provenance !== "system" || entry.subtype !== "notification" || entry.deliveredTurn !== true || entry.message?.role !== "user" || !Array.isArray(entry.message.parts)) continue;
625
+ notifications.push(entry.message.parts.filter((part) => typeof part?.text === "string").map((part) => part.text).join("\n"));
626
+ }
627
+ return notifications.length ? notifications.join("\n") : void 0;
628
+ }
629
+ async function verifyQwenTranscript(dir, filePath, now) {
630
+ const text = await qwenNotification(filePath);
631
+ if (text !== void 0) await confirmWakeText(dir, "socket", text, now);
632
+ return text;
633
+ }
634
+
635
+ // src/wake/qwen-socket.ts
636
+ var qwenHome = (env) => env.QWEN_HOME ? path5.resolve(env.QWEN_HOME) : path5.join(env.HOME ?? os3.homedir(), ".qwen");
637
+ async function qwenInboundAllowed(env, workspace = env.PWD ?? process.cwd()) {
638
+ for (const file of [path5.join(qwenHome(env), "settings.json"), path5.join(workspace, ".qwen", "settings.json")]) {
639
+ const config = await readJson(file);
640
+ if (["hold", "refuse"].includes(config?.agents?.crossSessionInbound ?? "") || config?.agents?.crossSessionMessaging === false) return false;
641
+ }
642
+ return true;
643
+ }
644
+ var qwenControllerFile = (env) => path5.join(stateRoot(env), "qwen", "controller.json");
645
+ async function qwenSession(env) {
646
+ const socket = env.QWEN_CODE_MESSAGING_SOCKET;
647
+ if (!socket) return void 0;
648
+ try {
649
+ const dir = path5.join(qwenHome(env), "sessions");
650
+ for (const file of await fs6.readdir(dir)) {
651
+ if (!/^\d+\.json$/u.test(file)) continue;
652
+ const record = await readJson(path5.join(dir, file));
653
+ if (record?.ipcPath === socket && typeof record.sessionId === "string" && SESSION_ID_PATTERN.test(record.sessionId)) return record.sessionId;
654
+ }
655
+ } catch {
656
+ }
657
+ return void 0;
658
+ }
659
+ async function target(env) {
660
+ const sessionId = await qwenSession(env);
661
+ if (!sessionId) return void 0;
662
+ try {
663
+ const file = qwenControllerFile(env);
664
+ const stat3 = await fs6.lstat(file);
665
+ if (!stat3.isFile() || (stat3.mode & 63) !== 0) return void 0;
666
+ const credential = await readJson(file);
667
+ if (!credential?.token || !/^qpc_[0-9a-f]{64}$/u.test(credential.token)) return void 0;
668
+ return { socket: env.QWEN_CODE_MESSAGING_SOCKET, sessionId, token: credential.token };
669
+ } catch {
670
+ return void 0;
671
+ }
672
+ }
673
+ function qwenFrames(target2, line, from, replyToken, msgId) {
674
+ return JSON.stringify({ msgV: 1, type: "auth", token: target2.token }) + "\n" + JSON.stringify({
675
+ msgV: 1,
676
+ msgId,
677
+ type: "user",
678
+ from,
679
+ replyToken,
680
+ fromName: "khala",
681
+ fromMode: "prompting",
682
+ toSessionId: target2.sessionId,
683
+ priority: "next",
684
+ message: { role: "user", content: line }
685
+ }) + "\n";
686
+ }
687
+ async function sendQwenWake(target2, line, signal, options = {}) {
688
+ if (signal.aborted) return "failed";
689
+ const dir = await fs6.mkdtemp(path5.join(options.runtimeDir ?? os3.tmpdir(), "khala-qw-"));
690
+ const from = path5.join(dir, "r.sock");
691
+ if (Buffer.byteLength(from) > 103) {
692
+ await fs6.rm(dir, { recursive: true, force: true });
693
+ return "failed";
694
+ }
695
+ const replyToken = randomBytes2(32).toString("hex");
696
+ const msgId = randomUUID2();
697
+ const peers = /* @__PURE__ */ new Set();
698
+ let sender;
699
+ let finish;
700
+ const result = new Promise((resolve) => {
701
+ finish = resolve;
702
+ });
703
+ const aborted = () => finish("failed");
704
+ const server = net.createServer((socket) => {
705
+ peers.add(socket);
706
+ socket.on("close", () => peers.delete(socket));
707
+ socket.on("error", () => {
708
+ });
709
+ let buffer = "", authenticated = false;
710
+ socket.setEncoding("utf8");
711
+ socket.on("data", (chunk) => {
712
+ buffer += chunk;
713
+ if (buffer.length > 64 * 1024) {
714
+ socket.destroy();
715
+ return;
716
+ }
717
+ let at;
718
+ while ((at = buffer.indexOf("\n")) >= 0) {
719
+ const text = buffer.slice(0, at);
720
+ buffer = buffer.slice(at + 1);
721
+ let frame;
722
+ try {
723
+ frame = JSON.parse(text);
724
+ } catch {
725
+ socket.destroy();
726
+ return;
727
+ }
728
+ if (!authenticated) {
729
+ authenticated = frame?.msgV === 1 && frame.type === "auth" && frame.token === replyToken;
730
+ if (!authenticated) {
731
+ socket.destroy();
732
+ return;
733
+ }
734
+ } else if (frame?.msgV === 1 && frame.type === "control" && frame.action === "delivery_status" && frame.origMsgId === msgId && ["delivered", "held", "refused", "denied", "expired", "misaddressed", "dropped"].includes(frame.status)) {
735
+ finish(frame.status);
736
+ }
737
+ }
738
+ });
739
+ });
740
+ server.on("error", () => finish("failed"));
741
+ signal.addEventListener("abort", aborted, { once: true });
742
+ const timer = setTimeout(() => finish("failed"), options.timeoutMs ?? 5e3);
743
+ try {
744
+ await new Promise((resolve, reject) => {
745
+ server.once("error", reject);
746
+ server.listen(from, resolve);
747
+ });
748
+ await fs6.chmod(from, 384);
749
+ if (signal.aborted) return "failed";
750
+ sender = net.createConnection(target2.socket);
751
+ sender.on("error", () => finish("failed"));
752
+ sender.on("connect", () => {
753
+ sender.end(qwenFrames(target2, line, from, replyToken, msgId));
754
+ });
755
+ return await result;
756
+ } catch {
757
+ return "failed";
758
+ } finally {
759
+ clearTimeout(timer);
760
+ signal.removeEventListener("abort", aborted);
761
+ sender?.destroy();
762
+ for (const socket of peers) socket.destroy();
763
+ await new Promise((resolve) => server.close(() => resolve()));
764
+ await fs6.rm(dir, { recursive: true, force: true });
765
+ }
766
+ }
767
+ function createQwenSocketDriver(deps = {}) {
768
+ const resolve = deps.resolve ?? target;
769
+ const policy = async (env) => {
770
+ const config = await readJson(path5.join(qwenHome(env), "settings.json"));
771
+ return config?.agents;
772
+ };
773
+ const reason = async (ctx) => {
774
+ const config = await policy(ctx.env);
775
+ if (config?.crossSessionInbound === "hold") return "qwen_held";
776
+ if ((deps.platform ?? process.platform) === "win32") return "windows";
777
+ const receipt = await readJson(path5.join(ctx.files.dir, "qwen-receipt.json"));
778
+ if (receipt?.status === "held" && config?.crossSessionInbound !== "accept") return "qwen_held";
779
+ if (config?.crossSessionMessaging === false || config?.crossSessionInbound === "refuse") return "qwen_refused";
780
+ if (!deps.resolve && !ctx.env.QWEN_CODE_MESSAGING_SOCKET) return "qwen_session_missing";
781
+ return await resolve(ctx.env) ? void 0 : "qwen_socket_missing";
782
+ };
783
+ return {
784
+ id: "socket",
785
+ rung: 1,
786
+ optIn: false,
787
+ minIdleMs: 0,
788
+ deadlineMs: 3e4,
789
+ verification: "transcript",
790
+ async verify(ctx) {
791
+ const journal = await readJson(path5.join(ctx.files.dir, "wake-journal.json"));
792
+ const receipt = await readJson(path5.join(ctx.files.dir, "qwen-receipt.json"));
793
+ if (!journal?.attempts?.some((attempt) => attempt.driver === "socket") && receipt?.status !== "held") return;
794
+ const transcript = await readJson(path5.join(ctx.files.dir, "qwen-transcript.json"));
795
+ if (!transcript?.path) return;
796
+ try {
797
+ const text = await verifyQwenTranscript(ctx.files.dir, transcript.path, ctx.now);
798
+ if (receipt?.status === "held" && receipt.nonce && text?.includes(`(k-${receipt.nonce})`)) {
799
+ await writeJsonAtomic(path5.join(ctx.files.dir, "qwen-receipt.json"), { status: "delivered" });
800
+ }
801
+ } catch {
802
+ }
803
+ },
804
+ available: async (ctx) => !await reason(ctx),
805
+ unavailableReason: reason,
806
+ async wake(ctx, line) {
807
+ if (ctx.signal.aborted || await reason(ctx)) return "skipped";
808
+ const destination = await resolve(ctx.env);
809
+ if (!destination || destination.sessionId !== ctx.sessionId) return "skipped";
810
+ const status = await (deps.send ?? sendQwenWake)(destination, line, ctx.signal);
811
+ await ensureStateDir(ctx.files.dir);
812
+ const nonce = line.match(/\(k-([0-9a-f]{8})\)$/u)?.[1];
813
+ await writeJsonAtomic(path5.join(ctx.files.dir, "qwen-receipt.json"), { status, nonce });
814
+ if (nonce && status === "held") await cancelAttempt(ctx.files.dir, nonce);
815
+ else if (nonce && status !== "delivered") await failAttempt(ctx.files.dir, nonce, ctx.now);
816
+ }
817
+ };
818
+ }
819
+
572
820
  // src/watch.ts
573
821
  var MARKER = "monitor.json";
574
822
  var OBSERVATION = "monitor-cursor.json";
575
823
  var NONCE_PATTERN = /^[a-f0-9-]{36}$/;
576
- var leaseFile = (dir, nonce) => path5.join(dir, `monitor-${nonce}.json`);
824
+ var leaseFile = (dir, nonce) => path6.join(dir, `monitor-${nonce}.json`);
577
825
  async function monitorOwner(files) {
578
826
  const owners = [];
579
827
  for (const dir of monitorStorageCandidates(files)) {
580
- const owner = await readJson(path5.join(dir, MARKER));
828
+ const owner = await readJson(path6.join(dir, MARKER));
581
829
  if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 0 || typeof owner.nonce !== "string" || !NONCE_PATTERN.test(owner.nonce)) continue;
582
830
  const lease = await readJson(leaseFile(dir, owner.nonce));
583
831
  if (lease?.nonce !== owner.nonce) continue;
@@ -598,17 +846,19 @@ async function monitorOwner(files) {
598
846
  async function monitorArmed(files) {
599
847
  return !!await monitorOwner(files);
600
848
  }
601
- function mentions(entry, name) {
602
- if (!name) return false;
603
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
604
- return new RegExp(`(?<![\\p{L}\\p{N}_])@?${escaped}(?![\\p{L}\\p{N}_])`, "iu").test(entry.body);
605
- }
606
849
  function channelLabel(name) {
607
850
  return Array.from(name.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, " ").trim()).slice(0, 80).join("");
608
851
  }
609
852
  async function watchSession(files, io) {
610
- const nonce = randomUUID2();
853
+ const nonce = randomUUID3();
611
854
  let storage = files.dir;
855
+ const env = io.env ?? process.env;
856
+ const qwen = io.harness === "qwen";
857
+ const wakeAllowed = async () => {
858
+ if (!qwen) return true;
859
+ const wake = await readWakeSettings(stateRoot(env));
860
+ return await qwenInboundAllowed(env) && !wake.off["qwen/socket"] && !wake.off["qwen/background-shell"];
861
+ };
612
862
  let watcher;
613
863
  let timer;
614
864
  let running;
@@ -626,7 +876,7 @@ async function watchSession(files, io) {
626
876
  code = exit;
627
877
  watcher?.close();
628
878
  clearInterval(timer);
629
- io.stderr?.(`khala watch: stopped (${reason}). Do not re-arm.`);
879
+ if (!qwen || exit !== 0) io.stderr?.(`khala watch: stopped (${reason}). Do not re-arm.`);
630
880
  finish();
631
881
  };
632
882
  let lastHeartbeat = 0;
@@ -647,19 +897,19 @@ async function watchSession(files, io) {
647
897
  }
648
898
  storage = await monitorStorage(files);
649
899
  const storageDirs = /* @__PURE__ */ new Map([[files.dir, storage]]);
650
- const observationDir = async (target) => {
651
- let dir = storageDirs.get(target.dir);
900
+ const observationDir = async (target2) => {
901
+ let dir = storageDirs.get(target2.dir);
652
902
  if (!dir) {
653
- dir = await monitorStorage(target);
654
- storageDirs.set(target.dir, dir);
903
+ dir = await monitorStorage(target2);
904
+ storageDirs.set(target2.dir, dir);
655
905
  }
656
906
  return dir;
657
907
  };
658
908
  const initialDelivered = /* @__PURE__ */ new Map();
659
- const seedBaseline = async (target, session) => {
660
- initialDelivered.set(target.dir, {
909
+ const seedBaseline = async (target2, session) => {
910
+ initialDelivered.set(target2.dir, {
661
911
  identity: JSON.stringify([session.roomId, session.userId]),
662
- cursor: await readCursor(target)
912
+ cursor: await readCursor(target2)
663
913
  });
664
914
  };
665
915
  if (initialSession) await seedBaseline(files, initialSession);
@@ -682,68 +932,75 @@ async function watchSession(files, io) {
682
932
  lastHeartbeat = Date.now();
683
933
  await writeJsonAtomic(leaseFile(storage, nonce), { nonce, pid: process.pid, heartbeatAt: new Date(lastHeartbeat).toISOString() });
684
934
  }
935
+ if (!await wakeAllowed()) return;
685
936
  for (const channel of await listChannels(files)) {
686
- const target = channel.files;
687
- const session = await readJson(target.session);
688
- const status2 = await readWatcherStatus(target);
937
+ const target2 = channel.files;
938
+ const session = await readJson(target2.session);
939
+ const status2 = await readWatcherStatus(target2);
689
940
  if (!session || session.roomId !== channel.roomId || typeof session.userId !== "string" || !["connected", "send_failed"].includes(status2?.state ?? "")) continue;
690
941
  const identity = JSON.stringify([session.roomId, session.userId]);
691
- if (initialDelivered.get(target.dir)?.identity !== identity) {
692
- await seedBaseline(target, session);
942
+ if (initialDelivered.get(target2.dir)?.identity !== identity) {
943
+ await seedBaseline(target2, session);
693
944
  }
694
- const mode = await readListeningMode(target);
695
- const entries = await readEntries(target);
696
- const observed = await readJson(path5.join(await observationDir(target), OBSERVATION));
945
+ const mode = await readListeningMode(target2);
946
+ const entries = await readEntries(target2);
947
+ const observed = await readJson(path6.join(await observationDir(target2), OBSERVATION));
697
948
  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;
698
- const baseline = initialDelivered.get(target.dir).cursor;
949
+ const baseline = initialDelivered.get(target2.dir).cursor;
699
950
  const initialCount = baseline.deliveredCount === 0 || entries[baseline.deliveredCount - 1]?.eventId === baseline.lastDeliveredEventId ? baseline.deliveredCount : 0;
700
951
  const fresh = entries.slice(Math.max(initialCount, position));
701
- const latestMode = await readListeningMode(target);
702
- const latestSession = await readJson(target.session);
703
- const latestStatus = await readWatcherStatus(target);
952
+ const latestMode = await readListeningMode(target2);
953
+ const latestSession = await readJson(target2.session);
954
+ const latestStatus = await readWatcherStatus(target2);
704
955
  if (!await ownsSession()) {
705
956
  stop(2, exitReason);
706
957
  return;
707
958
  }
959
+ if (!await wakeAllowed()) return;
708
960
  if (latestSession?.userId !== session.userId || latestSession?.roomId !== session.roomId || !["connected", "send_failed"].includes(latestStatus?.state ?? "")) continue;
709
- const messages = fresh.filter((entry) => entry.kind === "message" && entry.sender !== session.userId && entry.roomId === session.roomId);
710
- if (io.harness !== "muse" && !done && mode !== "async" && latestMode !== "async" && messages.length) {
961
+ const messages = fresh.filter((entry) => isWakeEntry(entry) && entry.sender !== session.userId && entry.roomId === session.roomId);
962
+ if (io.harness !== "muse" && !done && mode !== "async" && latestMode !== "async" && messages.some((entry) => wakesAgent(entry, latestMode, latestStatus?.displayName))) {
711
963
  const count = messages.filter((entry) => mentions(entry, latestStatus?.displayName)).length;
712
964
  io.write(`khala: ${messages.length} new message${messages.length === 1 ? "" : "s"} in #${channelLabel(latestStatus?.channelName ?? channel.channelName ?? "channel")} (${count} mentions you)
713
965
  `);
966
+ if (qwen) {
967
+ stop();
968
+ return;
969
+ }
714
970
  }
715
- if (position !== entries.length && !done && await ownsSession() && JSON.stringify(await readJson(target.session)) === JSON.stringify(latestSession)) {
971
+ if (position !== entries.length && !done && await ownsSession() && JSON.stringify(await readJson(target2.session)) === JSON.stringify(latestSession)) {
716
972
  try {
717
- await writeJsonAtomic(path5.join(await observationDir(target), OBSERVATION), {
973
+ await writeJsonAtomic(path6.join(await observationDir(target2), OBSERVATION), {
718
974
  userId: session.userId,
719
975
  roomId: session.roomId,
720
976
  count: entries.length,
721
977
  lastEventId: entries.at(-1)?.eventId ?? null
722
978
  });
723
979
  } catch (error) {
724
- if (await readJson(target.session)) throw error;
980
+ if (await readJson(target2.session)) throw error;
725
981
  }
726
982
  }
727
983
  }
728
984
  if (io.harness === "muse" && !done && await ownsSession()) {
729
- const request = await readJson(path5.join(files.dir, MUSE_WAKE_REQUEST));
985
+ const request = await readJson(path6.join(files.dir, MUSE_WAKE_REQUEST));
730
986
  const now = (io.now ?? Date.now)();
731
987
  const wakeNonce = typeof request?.line === "string" ? /\(k-([a-f0-9]{8})\)$/.exec(request.line)?.[1] : void 0;
732
988
  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") {
733
989
  const channels = await listChannels(files);
734
990
  let eligible = false;
735
991
  for (const channel of channels) {
736
- if (await readListeningMode(channel.files) === "async") continue;
992
+ const channelMode = await readListeningMode(channel.files);
993
+ if (channelMode === "async") continue;
737
994
  const session = await readJson(channel.files.session);
738
995
  const status2 = await readWatcherStatus(channel.files);
739
996
  if (!["connected", "send_failed"].includes(status2?.state ?? "")) continue;
740
997
  const cursor = await readCursor(channel.files);
741
- eligible ||= (await readEntries(channel.files)).slice(cursor.deliveredCount).some((entry) => entry.kind === "message" && entry.sender !== session?.userId);
998
+ eligible ||= (await readEntries(channel.files)).slice(cursor.deliveredCount).some((entry) => entry.sender !== session?.userId && wakesAgent(entry, channelMode, status2?.displayName));
742
999
  }
743
1000
  const activity = await readActivity(files);
744
1001
  if (eligible && await ownsSession() && activity.state === "idle") {
745
1002
  const epoch = activity.updatedAt;
746
- const dedupFile = path5.join(storage, "monitor-wake-observed.json");
1003
+ const dedupFile = path6.join(storage, "monitor-wake-observed.json");
747
1004
  const observed = await readJson(dedupFile);
748
1005
  if (observed?.epoch === epoch) return;
749
1006
  await writeJsonAtomic(dedupFile, { epoch, line: request.line });
@@ -774,7 +1031,7 @@ async function watchSession(files, io) {
774
1031
  watcher.on("error", (error) => stop(3, errorReason(error)));
775
1032
  lastHeartbeat = Date.now();
776
1033
  await writeJsonAtomic(leaseFile(storage, nonce), { nonce, pid: process.pid, heartbeatAt: new Date(lastHeartbeat).toISOString() });
777
- await writeJsonAtomic(path5.join(storage, MARKER), { nonce, pid: process.pid, startedAt: Date.now() });
1034
+ await writeJsonAtomic(path6.join(storage, MARKER), { nonce, pid: process.pid, startedAt: Date.now() });
778
1035
  published = true;
779
1036
  timer = setInterval(notify, 100);
780
1037
  io.signal.addEventListener("abort", abort, { once: true });
@@ -790,7 +1047,7 @@ async function watchSession(files, io) {
790
1047
  clearInterval(timer);
791
1048
  watcher?.close();
792
1049
  io.signal.removeEventListener("abort", abort);
793
- await fs5.unlink(leaseFile(storage, nonce)).catch((error) => {
1050
+ await fs7.unlink(leaseFile(storage, nonce)).catch((error) => {
794
1051
  if (!["ENOENT", "EACCES", "EROFS"].includes(error.code ?? "")) throw error;
795
1052
  });
796
1053
  }
@@ -807,8 +1064,8 @@ ${WATCH_USAGE}`);
807
1064
  return 1;
808
1065
  };
809
1066
  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");
810
- const { resolveHarness, resolveSessionId } = await import("./chunk-LBT47HLZ.mjs");
811
- const { adapterFor } = await import("./chunk-XBQDIKB5.mjs");
1067
+ const { resolveHarness, resolveSessionId } = await import("./chunk-CYINL2V3.mjs");
1068
+ const { adapterFor } = await import("./chunk-AQX2OYEF.mjs");
812
1069
  const harness = resolveHarness(argv, process.env);
813
1070
  if (harness === "invalid" || !adapterFor(harness)?.codec) return invalid("invalid_harness");
814
1071
  const index = argv.indexOf("--session");
@@ -823,7 +1080,7 @@ ${WATCH_USAGE}`);
823
1080
  try {
824
1081
  return await watchSession(files, { write: (line) => {
825
1082
  process.stdout.write(line);
826
- }, signal: controller.signal, harness, stderr: (line) => {
1083
+ }, signal: controller.signal, harness, env: process.env, stderr: (line) => {
827
1084
  process.stderr.write(line + "\n");
828
1085
  } });
829
1086
  } finally {
@@ -841,17 +1098,24 @@ export {
841
1098
  CURSOR_WORKSPACE_ENV,
842
1099
  CURSOR_DEFAULT_SESSION,
843
1100
  cursorSessionId,
1101
+ installedLauncher,
1102
+ launcherShellQuote,
844
1103
  formatJson,
845
1104
  jsonFormat,
846
1105
  textFormat,
847
1106
  readManaged,
848
1107
  ManagedFiles,
1108
+ verifyQwenTranscript,
1109
+ qwenHome,
1110
+ qwenInboundAllowed,
1111
+ qwenControllerFile,
1112
+ qwenSession,
1113
+ createQwenSocketDriver,
849
1114
  nodeScriptCommand,
850
1115
  cursorPaths,
851
1116
  installCursor,
852
1117
  monitorOwner,
853
1118
  monitorArmed,
854
- mentions,
855
1119
  watchSession,
856
1120
  WATCH_USAGE,
857
1121
  run,
@@ -2,7 +2,7 @@ import { createRequire as __khalaCreateRequire } from 'node:module'; const requi
2
2
  import {
3
3
  define_KHALA_BUNDLE_default,
4
4
  init_define_KHALA_BUNDLE
5
- } from "./chunk-7R3EIDGR.mjs";
5
+ } from "./chunk-LDW4HOQW.mjs";
6
6
 
7
7
  // src/bundle.ts
8
8
  init_define_KHALA_BUNDLE();