khala-cli 0.4.6 → 0.4.7

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 (34) hide show
  1. package/dist/{chunk-2U4E7AM7.mjs → chunk-25AX7UMF.mjs} +294 -101
  2. package/dist/{chunk-AUJEUKVM.mjs → chunk-3OFKVYOX.mjs} +1 -1
  3. package/dist/{chunk-AWBKVTLV.mjs → chunk-4LSCOWWN.mjs} +13 -14
  4. package/dist/chunk-5RGEO5HN.mjs +142 -0
  5. package/dist/{chunk-B2MOLCJM.mjs → chunk-6YEYXZZB.mjs} +8 -9
  6. package/dist/{chunk-2XTYRBYX.mjs → chunk-7V2YQIRI.mjs} +4 -4
  7. package/dist/{chunk-55KBLEX5.mjs → chunk-APIWKRKG.mjs} +1 -1
  8. package/dist/{chunk-PY2ZNVNY.mjs → chunk-C5SMOUTO.mjs} +34 -5
  9. package/dist/{chunk-JG5J3SHW.mjs → chunk-IG2NU5J6.mjs} +4 -4
  10. package/dist/chunk-JEI32DEA.mjs +634 -0
  11. package/dist/{chunk-4X35OUAN.mjs → chunk-K6EJSZYO.mjs} +92 -95
  12. package/dist/{chunk-FH2CZX5T.mjs → chunk-L7L6UNXN.mjs} +83 -36
  13. package/dist/{chunk-P5CP32X2.mjs → chunk-LNG3ZZBK.mjs} +1 -1
  14. package/dist/{chunk-CWHGNVZV.mjs → chunk-OAFYPBCP.mjs} +1 -1
  15. package/dist/{chunk-OECJ45NM.mjs → chunk-ODTSGEID.mjs} +24 -8
  16. package/dist/{chunk-XUJIJ4KB.mjs → chunk-OP2HZ7EM.mjs} +1 -1
  17. package/dist/{chunk-6E3UCO22.mjs → chunk-S3FX7VCY.mjs} +8 -11
  18. package/dist/{chunk-PFDUXY3N.mjs → chunk-SU6362YU.mjs} +1 -1
  19. package/dist/{chunk-Z4EDYB6Y.mjs → chunk-TOBQUAPD.mjs} +59 -43
  20. package/dist/{chunk-SN3STAO2.mjs → chunk-U3MKSPRO.mjs} +40 -3
  21. package/dist/{chunk-SZIEG3RS.mjs → chunk-W5HS2PQO.mjs} +12 -14
  22. package/dist/{chunk-OW3DSTEZ.mjs → chunk-WNOIHCCC.mjs} +5 -5
  23. package/dist/{chunk-RRBIVI77.mjs → chunk-YIHRHD7W.mjs} +1 -1
  24. package/dist/khala.mjs +10 -10
  25. package/dist/web/assets/local-BQl2KrE1.js +9 -0
  26. package/dist/web/assets/local-OwDq47O4.css +1 -0
  27. package/dist/web/index.html +2 -2
  28. package/package.json +1 -1
  29. package/dist/chunk-DPW43YNC.mjs +0 -36
  30. package/dist/chunk-IULQYQJZ.mjs +0 -144
  31. package/dist/chunk-WHONG7UF.mjs +0 -267
  32. package/dist/chunk-ZHMGB3PP.mjs +0 -82
  33. package/dist/web/assets/local-K8EcTVxW.js +0 -9
  34. package/dist/web/assets/local-R2y0Gl4T.css +0 -1
@@ -0,0 +1,142 @@
1
+ import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
+ import {
3
+ SESSION_ID_PATTERN,
4
+ ensureStateDir,
5
+ readJson,
6
+ stateRoot,
7
+ writeJsonAtomic
8
+ } from "./chunk-WNOIHCCC.mjs";
9
+
10
+ // src/harness/session-sources.ts
11
+ import path from "node:path";
12
+
13
+ // src/harness/proc.ts
14
+ import { execFile } from "node:child_process";
15
+ import { readFile } from "node:fs/promises";
16
+ import { promisify } from "node:util";
17
+ var execFileAsync = promisify(execFile);
18
+ var validPid = (pid) => Number.isSafeInteger(pid) && pid > 0;
19
+ function startToken(value) {
20
+ return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-");
21
+ }
22
+ function parseLinuxStat(pid, text) {
23
+ const open = text.indexOf("(");
24
+ const close = text.lastIndexOf(")");
25
+ if (!validPid(pid) || open < 0 || close < open || Number(text.slice(0, open).trim()) !== pid) return null;
26
+ const fields = text.slice(close + 1).trim().split(/\s+/);
27
+ const ppid = Number(fields[1]);
28
+ const startTime = fields[19];
29
+ if (!Number.isSafeInteger(ppid) || ppid < 0 || !startTime || !/^\d+$/.test(startTime)) return null;
30
+ return { pid, ppid, startTime, command: text.slice(open + 1, close) };
31
+ }
32
+ function parseDarwinPs(pid, text) {
33
+ const match = text.trim().match(/^(\d+)\s+([A-Za-z]{3}\s+[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s+(.+)$/);
34
+ if (!validPid(pid) || !match) return null;
35
+ return { pid, ppid: Number(match[1]), startTime: startToken(match[2]), command: match[3] };
36
+ }
37
+ function csvFields(line) {
38
+ const fields = [];
39
+ let value = "";
40
+ let quoted = false;
41
+ for (let i = 0; i < line.length; i++) {
42
+ const char = line[i];
43
+ if (char === '"') {
44
+ if (quoted && line[i + 1] === '"') {
45
+ value += '"';
46
+ i++;
47
+ } else quoted = !quoted;
48
+ } else if (char === "," && !quoted) {
49
+ fields.push(value);
50
+ value = "";
51
+ } else value += char;
52
+ }
53
+ if (quoted) return null;
54
+ fields.push(value);
55
+ return fields;
56
+ }
57
+ function parseWindowsCsv(text, pid) {
58
+ if (!validPid(pid)) return null;
59
+ const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/).filter((line) => line.trim() && !line.startsWith("#"));
60
+ const headers = csvFields(lines[0] ?? "");
61
+ if (!headers) return null;
62
+ for (const line of lines.slice(1)) {
63
+ const values = csvFields(line);
64
+ if (!values) continue;
65
+ const field = (name) => values[headers.indexOf(name)] ?? "";
66
+ if (Number(field("ProcessId")) !== pid) continue;
67
+ const ppid = Number(field("ParentProcessId"));
68
+ const startTime = startToken(field("CreationDate"));
69
+ const command = field("Name");
70
+ if (!field("ParentProcessId") || !Number.isSafeInteger(ppid) || ppid < 0 || !startTime || !command) return null;
71
+ return { pid, ppid, startTime, command };
72
+ }
73
+ return null;
74
+ }
75
+ async function readProcess(pid) {
76
+ if (!validPid(pid)) return null;
77
+ try {
78
+ if (process.platform === "linux") return parseLinuxStat(pid, await readFile(`/proc/${pid}/stat`, "utf8"));
79
+ if (process.platform === "darwin") {
80
+ const { stdout } = await execFileAsync("ps", ["-o", "ppid=,lstart=,comm=", "-p", String(pid)], { env: { ...process.env, LC_ALL: "C" }, timeout: 5e3 });
81
+ return parseDarwinPs(pid, stdout);
82
+ }
83
+ if (process.platform === "win32") {
84
+ const script = `Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' | Select-Object ProcessId,ParentProcessId,@{Name='CreationDate';Expression={$_.CreationDate.ToUniversalTime().ToString('o')}},Name | ConvertTo-Csv -NoTypeInformation`;
85
+ const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: 5e3 });
86
+ return parseWindowsCsv(stdout, pid);
87
+ }
88
+ } catch {
89
+ }
90
+ return null;
91
+ }
92
+ async function* walkAncestors(pid, read = readProcess) {
93
+ const seen = /* @__PURE__ */ new Set([pid]);
94
+ let current = await read(pid);
95
+ while (current && validPid(current.ppid) && !seen.has(current.ppid)) {
96
+ seen.add(current.ppid);
97
+ current = await read(current.ppid);
98
+ if (current) yield current;
99
+ }
100
+ }
101
+ async function nearestNonShellAncestor(pid, read = readProcess) {
102
+ for await (const ancestor of walkAncestors(pid, read)) {
103
+ const name = ancestor.command.split(/[\\/]/).pop()?.replace(/^-/, "").toLowerCase() ?? "";
104
+ if (!/^(?:sh|bash|dash|zsh|fish|ksh|csh|tcsh|ash|cmd(?:\.exe)?|powershell(?:\.exe)?|pwsh(?:\.exe)?)$/.test(name)) return ancestor;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ // src/harness/session-sources.ts
110
+ var SESSION_UNKNOWN_HINT = "Send the agent one message first, then retry.";
111
+ var mappingDir = (harness, env) => path.join(stateRoot(env), harness, ".by-pid");
112
+ async function recordHookSession(harness, sessionId, env, options = {}) {
113
+ if (!SESSION_ID_PATTERN.test(sessionId)) return;
114
+ const parent = await nearestNonShellAncestor(options.pid ?? process.pid, options.readProcess ?? readProcess);
115
+ if (!parent) return;
116
+ const dir = mappingDir(harness, env);
117
+ await ensureStateDir(dir);
118
+ const file = path.join(dir, `${parent.pid}.json`);
119
+ const at = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
120
+ const previous = await readJson(file);
121
+ if (previous?.startTime === parent.startTime && typeof previous.at === "string" && previous.at > at) return;
122
+ await writeJsonAtomic(file, {
123
+ sessionId,
124
+ startTime: parent.startTime,
125
+ at,
126
+ ...options.workspace !== void 0 ? { workspace: options.workspace } : {}
127
+ });
128
+ }
129
+ async function resolveSources(sources, meta, env, context) {
130
+ for (const source of sources) {
131
+ const value = await source.resolve(meta, env, context);
132
+ if (value === void 0 || value === null) continue;
133
+ return typeof value === "string" && SESSION_ID_PATTERN.test(value) ? { sessionId: value, rejoinable: source.rejoinable(value) } : null;
134
+ }
135
+ return null;
136
+ }
137
+
138
+ export {
139
+ SESSION_UNKNOWN_HINT,
140
+ recordHookSession,
141
+ resolveSources
142
+ };
@@ -5,15 +5,14 @@ import {
5
5
  monitorArmed,
6
6
  run,
7
7
  watchSession
8
- } from "./chunk-AWBKVTLV.mjs";
9
- import "./chunk-WHONG7UF.mjs";
10
- import "./chunk-ZHMGB3PP.mjs";
11
- import "./chunk-XUJIJ4KB.mjs";
12
- import "./chunk-DPW43YNC.mjs";
13
- import "./chunk-OW3DSTEZ.mjs";
14
- import "./chunk-AUJEUKVM.mjs";
15
- import "./chunk-SN3STAO2.mjs";
16
- import "./chunk-55KBLEX5.mjs";
8
+ } from "./chunk-4LSCOWWN.mjs";
9
+ import "./chunk-5RGEO5HN.mjs";
10
+ import "./chunk-JEI32DEA.mjs";
11
+ import "./chunk-OP2HZ7EM.mjs";
12
+ import "./chunk-WNOIHCCC.mjs";
13
+ import "./chunk-3OFKVYOX.mjs";
14
+ import "./chunk-U3MKSPRO.mjs";
15
+ import "./chunk-APIWKRKG.mjs";
17
16
  export {
18
17
  WATCH_USAGE,
19
18
  run as default,
@@ -1,14 +1,14 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
2
  import {
3
3
  listChannels
4
- } from "./chunk-XUJIJ4KB.mjs";
4
+ } from "./chunk-OP2HZ7EM.mjs";
5
5
  import {
6
6
  TERMINAL_SESSION_DETAILS,
7
7
  readStatus,
8
8
  sessionFiles
9
- } from "./chunk-OW3DSTEZ.mjs";
10
- import "./chunk-SN3STAO2.mjs";
11
- import "./chunk-55KBLEX5.mjs";
9
+ } from "./chunk-WNOIHCCC.mjs";
10
+ import "./chunk-U3MKSPRO.mjs";
11
+ import "./chunk-APIWKRKG.mjs";
12
12
 
13
13
  // hooks/session-start.ts
14
14
  async function run(stdin, argv) {
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
2
 
3
3
  // <define:__KHALA_BUNDLE__>
4
- var define_KHALA_BUNDLE_default = { name: "khala-cli", version: "0.4.6" };
4
+ var define_KHALA_BUNDLE_default = { name: "khala-cli", version: "0.4.7" };
5
5
 
6
6
  export {
7
7
  define_KHALA_BUNDLE_default
@@ -1,10 +1,18 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
- import "./chunk-55KBLEX5.mjs";
2
+ import {
3
+ adapterFor
4
+ } from "./chunk-JEI32DEA.mjs";
5
+ import "./chunk-OP2HZ7EM.mjs";
6
+ import {
7
+ resolveStateDir
8
+ } from "./chunk-WNOIHCCC.mjs";
9
+ import "./chunk-3OFKVYOX.mjs";
10
+ import "./chunk-U3MKSPRO.mjs";
11
+ import "./chunk-APIWKRKG.mjs";
3
12
 
4
13
  // hooks/claude-wake.ts
5
14
  import fs from "node:fs/promises";
6
15
  import path from "node:path";
7
- import os from "node:os";
8
16
  import { randomBytes } from "node:crypto";
9
17
  import { setTimeout as sleep } from "node:timers/promises";
10
18
  var NOTICE = "Khala: new channel messages. They arrive in the next hook context.\n";
@@ -73,20 +81,33 @@ function parentAlive(parent) {
73
81
  }
74
82
  async function watch(stdin, _argv, io = { stderr: process.stderr, env: process.env, now: () => /* @__PURE__ */ new Date() }) {
75
83
  let temporary;
84
+ let finish;
85
+ let state = "exited";
76
86
  try {
77
87
  const parent = process.ppid;
78
88
  const input = JSON.parse(stdin);
79
89
  if (input?.hook_event_name !== "Stop" || typeof input.session_id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(input.session_id)) return 0;
80
- const root = io.env.XDG_STATE_HOME && path.isAbsolute(io.env.XDG_STATE_HOME) ? io.env.XDG_STATE_HOME : path.join(io.env.HOME ?? os.homedir(), ".local/state");
81
- const dir = path.join(root, "khala", "claude", input.session_id);
90
+ const dir = resolveStateDir(adapterFor("claude").id, input.session_id, io.env);
82
91
  if (!(await fs.stat(dir)).isDirectory()) return 0;
83
92
  const nonce = randomBytes(6).toString("hex");
84
93
  const owner = path.join(dir, "watcher.json");
85
94
  temporary = path.join(dir, `.watcher-${nonce}.tmp`);
86
95
  const started = io.now();
87
- await fs.writeFile(temporary, JSON.stringify({ nonce, armedAt: started.toISOString() }) + "\n", { mode: 384, flag: "wx" });
96
+ const record = { nonce, armedAt: started.toISOString(), pid: process.pid, parentPid: parent, state: "armed" };
97
+ await fs.writeFile(temporary, JSON.stringify(record) + "\n", { mode: 384, flag: "wx" });
88
98
  await fs.rename(temporary, owner);
89
99
  temporary = void 0;
100
+ finish = async () => {
101
+ const file = await fs.open(owner, "r+");
102
+ try {
103
+ if (JSON.parse(await file.readFile("utf8")).nonce !== nonce) return;
104
+ const body = JSON.stringify({ ...record, state }) + "\n";
105
+ await file.write(body, 0, "utf8");
106
+ await file.truncate(Buffer.byteLength(body));
107
+ } finally {
108
+ await file.close();
109
+ }
110
+ };
90
111
  const deadline = started.getTime() + testDuration(io.env.KHALA_WAKE_TEST_DEADLINE_MS, DEADLINE_MS);
91
112
  const poll = testDuration(io.env.KHALA_WAKE_TEST_POLL_MS, POLL_MS);
92
113
  const owns = async () => (await readJson(owner))?.nonce === nonce;
@@ -99,13 +120,21 @@ async function watch(stdin, _argv, io = { stderr: process.stderr, env: process.e
99
120
  if (await idle() && await unreadMessages(dir) > 0) {
100
121
  if (await unreadMessages(dir) > 0 && await idle() && await owns() && parentAlive(parent) && io.now().getTime() < deadline) {
101
122
  io.stderr.write(NOTICE);
123
+ state = "woke";
102
124
  return 2;
103
125
  }
104
126
  }
105
127
  await sleep(Math.min(poll, Math.max(0, deadline - io.now().getTime())));
106
128
  }
129
+ state = "expired";
107
130
  } catch {
108
131
  } finally {
132
+ if (finish) {
133
+ try {
134
+ await finish();
135
+ } catch {
136
+ }
137
+ }
109
138
  if (temporary) {
110
139
  try {
111
140
  await fs.unlink(temporary);
@@ -4,14 +4,14 @@ import {
4
4
  CHANNEL_EVENT_TYPE,
5
5
  encodeChannelEvent,
6
6
  memberRenameContent
7
- } from "./chunk-P5CP32X2.mjs";
7
+ } from "./chunk-LNG3ZZBK.mjs";
8
8
  import {
9
9
  LISTENING_MODE_COMMAND_TYPE,
10
10
  LISTENING_MODE_MEMBER_KEY,
11
11
  memberListeningMode
12
- } from "./chunk-AUJEUKVM.mjs";
13
- import "./chunk-SN3STAO2.mjs";
14
- import "./chunk-55KBLEX5.mjs";
12
+ } from "./chunk-3OFKVYOX.mjs";
13
+ import "./chunk-U3MKSPRO.mjs";
14
+ import "./chunk-APIWKRKG.mjs";
15
15
 
16
16
  // src/matrix/session.ts
17
17
  import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, Direction, Method, EventType } from "matrix-js-sdk";