khala-cli 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/THIRD_PARTY_NOTICES.txt +809 -0
- package/dist/chunk-2AMWQ7RB.mjs +1127 -0
- package/dist/chunk-3E5DH3Y6.mjs +9864 -0
- package/dist/{chunk-IQJQ4RF5.mjs → chunk-4BP7V3OP.mjs} +12 -1
- package/dist/{chunk-FV32AQTC.mjs → chunk-4BZQ4GVW.mjs} +10 -6
- package/dist/chunk-5JSGA6OB.mjs +1483 -0
- package/dist/chunk-6AQVW5W7.mjs +60 -0
- package/dist/{chunk-W6R4UJUQ.mjs → chunk-6J2PUM7L.mjs} +16 -9
- package/dist/chunk-6SUV3EW6.mjs +27 -0
- package/dist/{chunk-RNDUFTBF.mjs → chunk-ARIDZOTI.mjs} +499 -115
- package/dist/chunk-C2WEDMAF.mjs +15 -0
- package/dist/{chunk-Z4JF2M5C.mjs → chunk-CHC4VZF5.mjs} +9 -9
- package/dist/chunk-CKLCR5G4.mjs +1369 -0
- package/dist/chunk-EVT2DWNZ.mjs +58 -0
- package/dist/chunk-FJVVSKZ3.mjs +22 -0
- package/dist/{chunk-APMWDF6B.mjs → chunk-G3NMB4PY.mjs} +4 -2
- package/dist/chunk-HHEZVPRR.mjs +7 -0
- package/dist/{chunk-54XFVSEC.mjs → chunk-JKOJQTFT.mjs} +103 -5
- package/dist/{chunk-ZJ4W7P33.mjs → chunk-NDMFD5IM.mjs} +15 -10
- package/dist/{chunk-VKQ3KUYO.mjs → chunk-OEV5DFLW.mjs} +77 -7
- package/dist/chunk-OIZ4R5H7.mjs +173 -0
- package/dist/{chunk-CJTCU755.mjs → chunk-PJKI7SRI.mjs} +256 -26
- package/dist/chunk-PN25BUQ4.mjs +599 -0
- package/dist/chunk-QDPJ4VT2.mjs +432 -0
- package/dist/{chunk-2DHIAFEV.mjs → chunk-SRZTHCXP.mjs} +24 -10
- package/dist/chunk-TSK3HJEG.mjs +20 -0
- package/dist/chunk-UX34IVN5.mjs +33 -0
- package/dist/{chunk-BKLW2SPG.mjs → chunk-VAC3JTAV.mjs} +34 -17
- package/dist/{chunk-VAI7IUI4.mjs → chunk-VDZK4LW3.mjs} +30 -12
- package/dist/chunk-W6HM6F2U.mjs +901 -0
- package/dist/{chunk-H3K4GA2P.mjs → chunk-WPNMWNCL.mjs} +33 -21
- package/dist/chunk-WUDXHF2Q.mjs +14 -0
- package/dist/chunk-WZJ6DQJV.mjs +19 -0
- package/dist/{chunk-5LIE5GUU.mjs → chunk-XR625T3I.mjs} +5 -1
- package/dist/chunk-ZSKLNZWZ.mjs +224 -0
- package/dist/iterm2_send.py +56 -0
- package/dist/khala.mjs +20 -13
- package/dist/web/assets/local-CSPuGnH2.css +1 -0
- package/dist/web/assets/local-Dtyxaacx.js +9 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -3
- package/dist/chunk-2VCAW4GH.mjs +0 -34
- package/dist/chunk-5BQTYU66.mjs +0 -886
- package/dist/chunk-5GLTKWJ5.mjs +0 -1
- package/dist/chunk-5PUQT3YS.mjs +0 -569
- package/dist/chunk-7HBRFY3Z.mjs +0 -8
- package/dist/chunk-CBPLCCRZ.mjs +0 -191
- package/dist/chunk-FH34B3I3.mjs +0 -235
- package/dist/chunk-NKX3SLS7.mjs +0 -227
- package/dist/chunk-VUSV2ADL.mjs +0 -267
- package/dist/web/assets/local-BQl2KrE1.js +0 -9
- package/dist/web/assets/local-OwDq47O4.css +0 -1
package/dist/chunk-CBPLCCRZ.mjs
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
isHarnessId
|
|
4
|
-
} from "./chunk-NKX3SLS7.mjs";
|
|
5
|
-
|
|
6
|
-
// src/state.ts
|
|
7
|
-
import { constants } from "node:fs";
|
|
8
|
-
import * as fs from "node:fs/promises";
|
|
9
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
-
import os from "node:os";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
13
|
-
var StateError = class extends Error {
|
|
14
|
-
code;
|
|
15
|
-
constructor(code) {
|
|
16
|
-
super(code);
|
|
17
|
-
this.name = "StateError";
|
|
18
|
-
this.code = code;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
var WINDOWS = process.platform === "win32";
|
|
22
|
-
async function renameWithRetry(from, to) {
|
|
23
|
-
for (let attempt = 0; ; attempt++) {
|
|
24
|
-
try {
|
|
25
|
-
await fs.rename(from, to);
|
|
26
|
-
return;
|
|
27
|
-
} catch (error) {
|
|
28
|
-
const code = error.code;
|
|
29
|
-
if (!WINDOWS || attempt >= 20 || !["EPERM", "EACCES", "EBUSY"].includes(code ?? "")) throw error;
|
|
30
|
-
await new Promise((resolve) => setTimeout(resolve, 10 + attempt * 5));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function stateRoot(env = process.env) {
|
|
35
|
-
const base = env.XDG_STATE_HOME && path.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : path.join(WINDOWS ? os.homedir() : env.HOME ?? os.homedir(), ".local", "state");
|
|
36
|
-
return path.join(base, "khala");
|
|
37
|
-
}
|
|
38
|
-
function filesForDir(dir) {
|
|
39
|
-
return { dir, mode: path.join(dir, "mode.json"), join: path.join(dir, "join.json"), session: path.join(dir, "session.json"), inbox: path.join(dir, "inbox.jsonl"), cursor: path.join(dir, "cursor.json"), status: path.join(dir, "status.json") };
|
|
40
|
-
}
|
|
41
|
-
function stateKey(value) {
|
|
42
|
-
return createHash("sha256").update(value).digest("hex").slice(0, 24);
|
|
43
|
-
}
|
|
44
|
-
function channelsDir(files) {
|
|
45
|
-
return path.join(files.dir, "channels");
|
|
46
|
-
}
|
|
47
|
-
function channelFiles(files, roomId) {
|
|
48
|
-
return filesForDir(path.join(channelsDir(files), stateKey(roomId)));
|
|
49
|
-
}
|
|
50
|
-
function joinFilePath(files, key) {
|
|
51
|
-
if (!/^[a-f0-9]{24}$/.test(key)) throw new StateError("storage_failed");
|
|
52
|
-
return path.join(files.dir, "joins", `${key}.json`);
|
|
53
|
-
}
|
|
54
|
-
async function writeJoinFile(files, link, join) {
|
|
55
|
-
const file = joinFilePath(files, stateKey(link));
|
|
56
|
-
await ensureStateDir(path.dirname(file));
|
|
57
|
-
await writeJsonAtomic(file, join);
|
|
58
|
-
}
|
|
59
|
-
async function readJoinFile(files, link) {
|
|
60
|
-
const file = joinFilePath(files, stateKey(link));
|
|
61
|
-
await ensureStateDir(path.dirname(file));
|
|
62
|
-
return readJson(file);
|
|
63
|
-
}
|
|
64
|
-
async function removeJoinFile(files, link) {
|
|
65
|
-
const file = joinFilePath(files, stateKey(link));
|
|
66
|
-
await ensureStateDir(path.dirname(file));
|
|
67
|
-
try {
|
|
68
|
-
await fs.unlink(file);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (error.code !== "ENOENT") throw new StateError("storage_failed");
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function sessionFiles(harness, sessionId, env) {
|
|
74
|
-
if (!isHarnessId(harness) || !SESSION_ID_PATTERN.test(sessionId)) throw new StateError("invalid_session_id");
|
|
75
|
-
return filesForDir(path.join(stateRoot(env), harness, sessionId));
|
|
76
|
-
}
|
|
77
|
-
function resolveStateDir(harness, sessionId, env) {
|
|
78
|
-
return sessionFiles(harness, sessionId, env).dir;
|
|
79
|
-
}
|
|
80
|
-
async function ensureStateDir(dir) {
|
|
81
|
-
try {
|
|
82
|
-
let sessionDir = dir;
|
|
83
|
-
if (path.basename(path.dirname(dir)) === "channels" && /^[a-f0-9]{24}$/.test(path.basename(dir)) && isHarnessId(path.basename(path.dirname(path.dirname(path.dirname(dir))))) && path.basename(path.dirname(path.dirname(path.dirname(path.dirname(dir))))) === "khala") {
|
|
84
|
-
sessionDir = path.dirname(path.dirname(dir));
|
|
85
|
-
} else if (["channels", "joins"].includes(path.basename(dir)) && isHarnessId(path.basename(path.dirname(path.dirname(dir)))) && path.basename(path.dirname(path.dirname(path.dirname(dir)))) === "khala") {
|
|
86
|
-
sessionDir = path.dirname(dir);
|
|
87
|
-
}
|
|
88
|
-
const directories = [path.dirname(path.dirname(sessionDir)), path.dirname(sessionDir), sessionDir];
|
|
89
|
-
const relative = path.relative(sessionDir, dir);
|
|
90
|
-
let parent = sessionDir;
|
|
91
|
-
for (const component of relative ? relative.split(path.sep) : []) {
|
|
92
|
-
parent = path.join(parent, component);
|
|
93
|
-
directories.push(parent);
|
|
94
|
-
}
|
|
95
|
-
for (const directory of directories) {
|
|
96
|
-
try {
|
|
97
|
-
await fs.mkdir(directory, { recursive: true, mode: 448 });
|
|
98
|
-
} catch (error) {
|
|
99
|
-
if (error.code !== "EEXIST") throw error;
|
|
100
|
-
}
|
|
101
|
-
const stat = await fs.lstat(directory);
|
|
102
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !WINDOWS && (stat.mode & 63) !== 0 || typeof process.getuid === "function" && stat.uid !== process.getuid()) throw new StateError("unsafe_state_dir");
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
if (error instanceof StateError) throw error;
|
|
106
|
-
throw new StateError("storage_failed");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async function writeJsonAtomic(file, value) {
|
|
110
|
-
const dir = path.dirname(file);
|
|
111
|
-
const temporary = path.join(dir, `.${path.basename(file)}-${randomUUID()}.tmp`);
|
|
112
|
-
let handle;
|
|
113
|
-
try {
|
|
114
|
-
handle = await fs.open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
|
|
115
|
-
await handle.writeFile(JSON.stringify(value) + "\n", "utf8");
|
|
116
|
-
await handle.sync();
|
|
117
|
-
await handle.close();
|
|
118
|
-
handle = void 0;
|
|
119
|
-
await renameWithRetry(temporary, file);
|
|
120
|
-
if (!WINDOWS) {
|
|
121
|
-
const directory = await fs.open(dir, constants.O_RDONLY);
|
|
122
|
-
try {
|
|
123
|
-
await directory.sync();
|
|
124
|
-
} finally {
|
|
125
|
-
await directory.close();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
throw new StateError("storage_failed");
|
|
130
|
-
} finally {
|
|
131
|
-
await handle?.close().catch(() => void 0);
|
|
132
|
-
await fs.unlink(temporary).catch(() => void 0);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async function readJson(file) {
|
|
136
|
-
let content;
|
|
137
|
-
try {
|
|
138
|
-
content = await fs.readFile(file, "utf8");
|
|
139
|
-
} catch (error) {
|
|
140
|
-
if (error.code === "ENOENT") return null;
|
|
141
|
-
throw new StateError("storage_failed");
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
return JSON.parse(content);
|
|
145
|
-
} catch {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function readStatus(files) {
|
|
150
|
-
return readJson(files.status);
|
|
151
|
-
}
|
|
152
|
-
async function writeStateFile(dir, name, value) {
|
|
153
|
-
if (!["join.json", "session.json", "cursor.json", "status.json", "mode.json", "rejoin.json", "channel.json", "resume.json"].includes(name)) throw new StateError("storage_failed");
|
|
154
|
-
await writeJsonAtomic(path.join(dir, name), value);
|
|
155
|
-
}
|
|
156
|
-
function readStateFile(dir, name) {
|
|
157
|
-
if (!/^[a-z]+\.json$/.test(name)) throw new StateError("storage_failed");
|
|
158
|
-
return readJson(path.join(dir, name));
|
|
159
|
-
}
|
|
160
|
-
async function removeStateFile(dir, name) {
|
|
161
|
-
if (!/^[a-z]+\.json$/.test(name) && name !== "inbox.jsonl") throw new StateError("storage_failed");
|
|
162
|
-
try {
|
|
163
|
-
await fs.unlink(path.join(dir, name));
|
|
164
|
-
} catch (error) {
|
|
165
|
-
if (error.code !== "ENOENT") throw new StateError("storage_failed");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
var TERMINAL_SESSION_DETAILS = ["left", "removed", "revoked", "unauthorized", "channel_deleted"];
|
|
169
|
-
|
|
170
|
-
export {
|
|
171
|
-
SESSION_ID_PATTERN,
|
|
172
|
-
StateError,
|
|
173
|
-
stateRoot,
|
|
174
|
-
filesForDir,
|
|
175
|
-
stateKey,
|
|
176
|
-
channelsDir,
|
|
177
|
-
channelFiles,
|
|
178
|
-
writeJoinFile,
|
|
179
|
-
readJoinFile,
|
|
180
|
-
removeJoinFile,
|
|
181
|
-
sessionFiles,
|
|
182
|
-
resolveStateDir,
|
|
183
|
-
ensureStateDir,
|
|
184
|
-
writeJsonAtomic,
|
|
185
|
-
readJson,
|
|
186
|
-
readStatus,
|
|
187
|
-
writeStateFile,
|
|
188
|
-
readStateFile,
|
|
189
|
-
removeStateFile,
|
|
190
|
-
TERMINAL_SESSION_DETAILS
|
|
191
|
-
};
|
package/dist/chunk-FH34B3I3.mjs
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
adapterFor,
|
|
4
|
-
readListeningMode,
|
|
5
|
-
resolveSources
|
|
6
|
-
} from "./chunk-5BQTYU66.mjs";
|
|
7
|
-
import {
|
|
8
|
-
listChannels,
|
|
9
|
-
readCursor,
|
|
10
|
-
readEntries
|
|
11
|
-
} from "./chunk-VKQ3KUYO.mjs";
|
|
12
|
-
import {
|
|
13
|
-
SESSION_ID_PATTERN,
|
|
14
|
-
readJson,
|
|
15
|
-
readStatus,
|
|
16
|
-
sessionFiles,
|
|
17
|
-
writeJsonAtomic
|
|
18
|
-
} from "./chunk-CBPLCCRZ.mjs";
|
|
19
|
-
|
|
20
|
-
// src/watch.ts
|
|
21
|
-
import * as fs from "node:fs/promises";
|
|
22
|
-
import { watch as watchDirectory } from "node:fs";
|
|
23
|
-
import path from "node:path";
|
|
24
|
-
import { randomUUID } from "node:crypto";
|
|
25
|
-
|
|
26
|
-
// src/mcp/session-id.ts
|
|
27
|
-
var isHarness = (value) => typeof value === "string" && adapterFor(value) !== void 0;
|
|
28
|
-
function resolveHarness(argv, env) {
|
|
29
|
-
const index = argv.indexOf("--harness");
|
|
30
|
-
if (index !== -1) {
|
|
31
|
-
const value = argv[index + 1];
|
|
32
|
-
return isHarness(value) ? value : "invalid";
|
|
33
|
-
}
|
|
34
|
-
if (isHarness(env.KHALA_MCP_HARNESS)) return env.KHALA_MCP_HARNESS;
|
|
35
|
-
return env.CLAUDE_CODE_SESSION_ID !== void 0 ? "claude" : "codex";
|
|
36
|
-
}
|
|
37
|
-
async function resolveSession(harness, meta, env, context = {}) {
|
|
38
|
-
return resolveSources(adapterFor(harness)?.sessionSources ?? [], meta, env, { ...context, harness });
|
|
39
|
-
}
|
|
40
|
-
async function resolveSessionId(harness, meta, env) {
|
|
41
|
-
return (await resolveSession(harness, meta, env))?.sessionId ?? null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// src/watch.ts
|
|
45
|
-
var MARKER = "monitor.json";
|
|
46
|
-
var OBSERVATION = "monitor-cursor.json";
|
|
47
|
-
var NONCE_PATTERN = /^[a-f0-9-]{36}$/;
|
|
48
|
-
var leaseFile = (files, nonce) => path.join(files.dir, `monitor-${nonce}.json`);
|
|
49
|
-
async function monitorArmed(files) {
|
|
50
|
-
const owner = await readJson(path.join(files.dir, MARKER));
|
|
51
|
-
if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 0 || typeof owner.nonce !== "string" || !NONCE_PATTERN.test(owner.nonce)) return false;
|
|
52
|
-
if ((await readJson(leaseFile(files, owner.nonce)))?.nonce !== owner.nonce) return false;
|
|
53
|
-
try {
|
|
54
|
-
process.kill(owner.pid, 0);
|
|
55
|
-
return true;
|
|
56
|
-
} catch (error) {
|
|
57
|
-
return error.code === "EPERM";
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function mentions(entry, name) {
|
|
61
|
-
if (!name) return false;
|
|
62
|
-
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
|
-
return new RegExp(`(?<![\\p{L}\\p{N}_])@?${escaped}(?![\\p{L}\\p{N}_])`, "iu").test(entry.body);
|
|
64
|
-
}
|
|
65
|
-
function channelLabel(name) {
|
|
66
|
-
return Array.from(name.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, " ").trim()).slice(0, 80).join("");
|
|
67
|
-
}
|
|
68
|
-
async function watchSession(files, io) {
|
|
69
|
-
const nonce = randomUUID();
|
|
70
|
-
let watcher;
|
|
71
|
-
let timer;
|
|
72
|
-
let running;
|
|
73
|
-
let dirty = false;
|
|
74
|
-
let done = false;
|
|
75
|
-
let code = 0;
|
|
76
|
-
let finish;
|
|
77
|
-
const finished = new Promise((resolve) => {
|
|
78
|
-
finish = resolve;
|
|
79
|
-
});
|
|
80
|
-
const stop = (exit = 0) => {
|
|
81
|
-
done = true;
|
|
82
|
-
code = exit;
|
|
83
|
-
watcher?.close();
|
|
84
|
-
clearInterval(timer);
|
|
85
|
-
finish();
|
|
86
|
-
};
|
|
87
|
-
const abort = () => stop();
|
|
88
|
-
try {
|
|
89
|
-
const status = await readStatus(files);
|
|
90
|
-
const initialSession = await readJson(files.session);
|
|
91
|
-
if (!["connected", "send_failed"].includes(status?.state ?? "") || !initialSession && !(await listChannels(files)).length) return 0;
|
|
92
|
-
const initialDelivered = /* @__PURE__ */ new Map();
|
|
93
|
-
const seedBaseline = async (target, session) => {
|
|
94
|
-
initialDelivered.set(target.dir, {
|
|
95
|
-
identity: JSON.stringify([session.roomId, session.userId]),
|
|
96
|
-
cursor: await readCursor(target)
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
if (initialSession) await seedBaseline(files, initialSession);
|
|
100
|
-
for (const channel of await listChannels(files)) {
|
|
101
|
-
const session = await readJson(channel.files.session);
|
|
102
|
-
if (session) await seedBaseline(channel.files, session);
|
|
103
|
-
}
|
|
104
|
-
const evaluate = async () => {
|
|
105
|
-
const ownsSession = async () => {
|
|
106
|
-
const owner = await readJson(path.join(files.dir, MARKER));
|
|
107
|
-
const status2 = await readJson(files.status);
|
|
108
|
-
return owner?.nonce === nonce && !["disconnected", "closed"].includes(status2?.state ?? "");
|
|
109
|
-
};
|
|
110
|
-
if (!await ownsSession()) {
|
|
111
|
-
stop();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
for (const channel of await listChannels(files)) {
|
|
115
|
-
const target = channel.files;
|
|
116
|
-
const session = await readJson(target.session);
|
|
117
|
-
const status2 = await readStatus(target);
|
|
118
|
-
if (!session || session.roomId !== channel.roomId || typeof session.userId !== "string" || !["connected", "send_failed"].includes(status2?.state ?? "")) continue;
|
|
119
|
-
const identity = JSON.stringify([session.roomId, session.userId]);
|
|
120
|
-
if (initialDelivered.get(target.dir)?.identity !== identity) {
|
|
121
|
-
await seedBaseline(target, session);
|
|
122
|
-
}
|
|
123
|
-
const mode = await readListeningMode(target);
|
|
124
|
-
const entries = await readEntries(target);
|
|
125
|
-
const observed = await readJson(path.join(target.dir, OBSERVATION));
|
|
126
|
-
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;
|
|
127
|
-
const baseline = initialDelivered.get(target.dir).cursor;
|
|
128
|
-
const initialCount = baseline.deliveredCount === 0 || entries[baseline.deliveredCount - 1]?.eventId === baseline.lastDeliveredEventId ? baseline.deliveredCount : 0;
|
|
129
|
-
const fresh = entries.slice(Math.max(initialCount, position));
|
|
130
|
-
const latestMode = await readListeningMode(target);
|
|
131
|
-
const latestSession = await readJson(target.session);
|
|
132
|
-
const latestStatus = await readStatus(target);
|
|
133
|
-
if (!await ownsSession()) {
|
|
134
|
-
stop();
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
if (latestSession?.userId !== session.userId || latestSession?.roomId !== session.roomId || !["connected", "send_failed"].includes(latestStatus?.state ?? "")) continue;
|
|
138
|
-
const messages = fresh.filter((entry) => entry.kind === "message" && entry.sender !== session.userId && entry.roomId === session.roomId);
|
|
139
|
-
if (!done && mode !== "async" && latestMode !== "async" && messages.length) {
|
|
140
|
-
const count = messages.filter((entry) => mentions(entry, latestStatus?.displayName)).length;
|
|
141
|
-
io.write(`khala: ${messages.length} new message${messages.length === 1 ? "" : "s"} in #${channelLabel(latestStatus?.channelName ?? channel.channelName ?? "channel")} (${count} mentions you)
|
|
142
|
-
`);
|
|
143
|
-
}
|
|
144
|
-
if (position !== entries.length && !done && await ownsSession() && JSON.stringify(await readJson(target.session)) === JSON.stringify(latestSession)) {
|
|
145
|
-
try {
|
|
146
|
-
await writeJsonAtomic(path.join(target.dir, OBSERVATION), {
|
|
147
|
-
userId: session.userId,
|
|
148
|
-
roomId: session.roomId,
|
|
149
|
-
count: entries.length,
|
|
150
|
-
lastEventId: entries.at(-1)?.eventId ?? null
|
|
151
|
-
});
|
|
152
|
-
} catch (error) {
|
|
153
|
-
if (await readJson(target.session)) throw error;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
const notify = () => {
|
|
159
|
-
if (done) return;
|
|
160
|
-
dirty = true;
|
|
161
|
-
if (running) return;
|
|
162
|
-
running = (async () => {
|
|
163
|
-
do {
|
|
164
|
-
dirty = false;
|
|
165
|
-
await evaluate();
|
|
166
|
-
} while (dirty && !done);
|
|
167
|
-
})().catch(() => stop(1)).finally(() => {
|
|
168
|
-
running = void 0;
|
|
169
|
-
if (dirty && !done) notify();
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
await writeJsonAtomic(leaseFile(files, nonce), { nonce, pid: process.pid });
|
|
173
|
-
await writeJsonAtomic(path.join(files.dir, MARKER), { nonce, pid: process.pid });
|
|
174
|
-
watcher = watchDirectory(files.dir, (_event, filename) => {
|
|
175
|
-
if (filename === null || [MARKER, "inbox.jsonl", "cursor.json", "mode.json", "session.json", "status.json"].includes(String(filename))) notify();
|
|
176
|
-
});
|
|
177
|
-
watcher.on("error", () => stop(1));
|
|
178
|
-
timer = setInterval(notify, 100);
|
|
179
|
-
io.signal.addEventListener("abort", abort, { once: true });
|
|
180
|
-
if (io.signal.aborted) stop();
|
|
181
|
-
else notify();
|
|
182
|
-
await finished;
|
|
183
|
-
await running;
|
|
184
|
-
return code;
|
|
185
|
-
} finally {
|
|
186
|
-
clearInterval(timer);
|
|
187
|
-
watcher?.close();
|
|
188
|
-
io.signal.removeEventListener("abort", abort);
|
|
189
|
-
await fs.unlink(leaseFile(files, nonce)).catch((error) => {
|
|
190
|
-
if (error.code !== "ENOENT") throw error;
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
var WATCH_USAGE = "usage: khala watch [--harness claude|codex|cursor --session <id>]";
|
|
195
|
-
async function run(argv) {
|
|
196
|
-
if (argv.length === 1 && ["--help", "-h"].includes(argv[0])) {
|
|
197
|
-
console.log(WATCH_USAGE);
|
|
198
|
-
return 0;
|
|
199
|
-
}
|
|
200
|
-
const invalid = (reason) => {
|
|
201
|
-
console.error(`khala: ${reason}
|
|
202
|
-
${WATCH_USAGE}`);
|
|
203
|
-
return 1;
|
|
204
|
-
};
|
|
205
|
-
if (argv.length !== 0 && !(argv.length === 2 && argv[0] === "--session") && !(argv.length === 4 && argv[0] === "--harness" && argv[2] === "--session")) return invalid("invalid_arguments");
|
|
206
|
-
const harness = resolveHarness(argv, process.env);
|
|
207
|
-
if (harness === "invalid" || !adapterFor(harness)?.codec) return invalid("invalid_harness");
|
|
208
|
-
const index = argv.indexOf("--session");
|
|
209
|
-
const id = index === -1 ? await resolveSessionId(harness, void 0, process.env) : argv[index + 1];
|
|
210
|
-
if (!id) return invalid("session_unknown");
|
|
211
|
-
if (!SESSION_ID_PATTERN.test(id)) return invalid("invalid_session_id");
|
|
212
|
-
const files = sessionFiles(harness, id, process.env);
|
|
213
|
-
const controller = new AbortController();
|
|
214
|
-
const stop = () => controller.abort();
|
|
215
|
-
process.once("SIGTERM", stop);
|
|
216
|
-
process.once("SIGINT", stop);
|
|
217
|
-
try {
|
|
218
|
-
return await watchSession(files, { write: (line) => {
|
|
219
|
-
process.stdout.write(line);
|
|
220
|
-
}, signal: controller.signal });
|
|
221
|
-
} finally {
|
|
222
|
-
process.removeListener("SIGTERM", stop);
|
|
223
|
-
process.removeListener("SIGINT", stop);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export {
|
|
228
|
-
resolveHarness,
|
|
229
|
-
resolveSession,
|
|
230
|
-
monitorArmed,
|
|
231
|
-
mentions,
|
|
232
|
-
watchSession,
|
|
233
|
-
WATCH_USAGE,
|
|
234
|
-
run
|
|
235
|
-
};
|
package/dist/chunk-NKX3SLS7.mjs
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
|
|
3
|
-
// ../contracts/src/messaging/decode.ts
|
|
4
|
-
var MAX_IDENTIFIER_BYTES = 512;
|
|
5
|
-
var DecodeFailure = class extends Error {
|
|
6
|
-
path;
|
|
7
|
-
code;
|
|
8
|
-
constructor(path, code) {
|
|
9
|
-
super(`${path || "<root>"}: ${code}`);
|
|
10
|
-
this.name = "DecodeFailure";
|
|
11
|
-
this.path = path;
|
|
12
|
-
this.code = code;
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
function fail(path, code) {
|
|
16
|
-
throw new DecodeFailure(path, code);
|
|
17
|
-
}
|
|
18
|
-
function decodeWith(read) {
|
|
19
|
-
try {
|
|
20
|
-
return { ok: true, value: read() };
|
|
21
|
-
} catch (error) {
|
|
22
|
-
if (error instanceof DecodeFailure) return { ok: false, error: { path: error.path, code: error.code } };
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
var join = (path, key) => typeof key === "number" ? `${path}[${key}]` : path ? `${path}.${key}` : key;
|
|
27
|
-
function object(input, path, keys) {
|
|
28
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) fail(path, "not_object");
|
|
29
|
-
const prototype = Object.getPrototypeOf(input);
|
|
30
|
-
if (prototype !== Object.prototype && prototype !== null) fail(path, "not_object");
|
|
31
|
-
const record = input;
|
|
32
|
-
for (const key of Object.keys(record)) if (!keys.includes(key)) fail(join(path, key), "unknown_field");
|
|
33
|
-
for (const key of keys) if (!Object.hasOwn(record, key)) fail(join(path, key), "missing_field");
|
|
34
|
-
return { path, field: (key) => record[key], at: (key) => join(path, key) };
|
|
35
|
-
}
|
|
36
|
-
function array(input, path) {
|
|
37
|
-
if (!Array.isArray(input)) fail(path, "wrong_type");
|
|
38
|
-
return input;
|
|
39
|
-
}
|
|
40
|
-
function elementPath(path, index) {
|
|
41
|
-
return join(path, index);
|
|
42
|
-
}
|
|
43
|
-
function utf8Length(value) {
|
|
44
|
-
let bytes = 0;
|
|
45
|
-
for (const char of value) {
|
|
46
|
-
const code = char.codePointAt(0) ?? 0;
|
|
47
|
-
bytes += code < 128 ? 1 : code < 2048 ? 2 : code < 65536 ? 3 : 4;
|
|
48
|
-
}
|
|
49
|
-
return bytes;
|
|
50
|
-
}
|
|
51
|
-
function isWellFormed(value) {
|
|
52
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
53
|
-
const unit = value.charCodeAt(index);
|
|
54
|
-
if (unit >= 55296 && unit <= 56319) {
|
|
55
|
-
const next = value.charCodeAt(index + 1);
|
|
56
|
-
if (!(next >= 56320 && next <= 57343)) return false;
|
|
57
|
-
index += 1;
|
|
58
|
-
} else if (unit >= 56320 && unit <= 57343) return false;
|
|
59
|
-
}
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
var CONTROL = /[\u0000-\u001f\u007f-\u009f]/;
|
|
63
|
-
var INVISIBLE = /[\u061c\u200b\u200e\u200f\u202a-\u202e\u2060\u2066-\u2069\ufeff]/;
|
|
64
|
-
function text(input, path, maxBytes) {
|
|
65
|
-
if (typeof input !== "string") fail(path, "wrong_type");
|
|
66
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) fail(path, "invalid_limits");
|
|
67
|
-
if (!isWellFormed(input)) fail(path, "malformed_unicode");
|
|
68
|
-
if (utf8Length(input) > maxBytes) fail(path, "too_long");
|
|
69
|
-
if (input.includes("\0")) fail(path, "control_character");
|
|
70
|
-
return input;
|
|
71
|
-
}
|
|
72
|
-
function label(input, path, maxBytes) {
|
|
73
|
-
const value = text(input, path, maxBytes);
|
|
74
|
-
if (CONTROL.test(value)) fail(path, "control_character");
|
|
75
|
-
return value;
|
|
76
|
-
}
|
|
77
|
-
function displayText(input, path, maxBytes) {
|
|
78
|
-
const value = label(input, path, maxBytes);
|
|
79
|
-
if (INVISIBLE.test(value)) fail(path, "control_character");
|
|
80
|
-
return value;
|
|
81
|
-
}
|
|
82
|
-
function identifier(input, path) {
|
|
83
|
-
const value = label(input, path, MAX_IDENTIFIER_BYTES);
|
|
84
|
-
if (value.length === 0) fail(path, "empty");
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
function nullable(input, read) {
|
|
88
|
-
return input === null ? null : read(input);
|
|
89
|
-
}
|
|
90
|
-
function literal(input, path, allowed) {
|
|
91
|
-
if (!allowed.includes(input)) fail(path, typeof input === typeof allowed[0] ? "invalid_value" : "wrong_type");
|
|
92
|
-
return input;
|
|
93
|
-
}
|
|
94
|
-
function version(input, path) {
|
|
95
|
-
if (input !== 1) fail(path, "unsupported_version");
|
|
96
|
-
return 1;
|
|
97
|
-
}
|
|
98
|
-
function safeInteger(input, path) {
|
|
99
|
-
if (typeof input !== "number") fail(path, "wrong_type");
|
|
100
|
-
if (!Number.isSafeInteger(input)) fail(path, "unsafe_integer");
|
|
101
|
-
if (input < 0) fail(path, "invalid_value");
|
|
102
|
-
return input;
|
|
103
|
-
}
|
|
104
|
-
var UTC_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?Z$/;
|
|
105
|
-
function utcTimestamp(input, path) {
|
|
106
|
-
if (typeof input !== "string") fail(path, "wrong_type");
|
|
107
|
-
const match = UTC_TIMESTAMP.exec(input);
|
|
108
|
-
if (!match) fail(path, "invalid_value");
|
|
109
|
-
const [year, month, day, hour, minute, second] = match.slice(1).map(Number);
|
|
110
|
-
const date = /* @__PURE__ */ new Date(0);
|
|
111
|
-
date.setUTCFullYear(year, month - 1, day);
|
|
112
|
-
date.setUTCHours(hour, minute, second, 0);
|
|
113
|
-
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day || date.getUTCHours() !== hour || date.getUTCMinutes() !== minute || date.getUTCSeconds() !== second) fail(path, "invalid_value");
|
|
114
|
-
return input;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ../contracts/src/m1/harness.ts
|
|
118
|
-
var HARNESS_ID = /^[a-z][a-z0-9-]{1,23}$/;
|
|
119
|
-
function isHarnessId(input) {
|
|
120
|
-
return typeof input === "string" && HARNESS_ID.exec(input)?.[0] === input;
|
|
121
|
-
}
|
|
122
|
-
var LEGACY_HARNESSES = ["claude", "codex", "cursor"];
|
|
123
|
-
var HARNESS_REGISTRY = [
|
|
124
|
-
{ id: "claude", displayName: "Claude Code", modelName: "Claude", logoKey: "claude", steer: true, sync: true, idleWake: "default", registered: true },
|
|
125
|
-
{ id: "codex", displayName: "Codex", modelName: "Codex", logoKey: "codex", steer: true, sync: true, idleWake: "default", registered: true },
|
|
126
|
-
{ id: "cursor", displayName: "Cursor", modelName: "Cursor", logoKey: "cursor", steer: true, sync: true, idleWake: "none", registered: true },
|
|
127
|
-
{ id: "opencode", displayName: "OpenCode", modelName: "OpenCode", logoKey: "opencode", steer: true, sync: true, idleWake: "default", registered: true },
|
|
128
|
-
{ id: "copilot", displayName: "Copilot CLI", modelName: "Copilot", logoKey: "copilot", steer: true, sync: true, idleWake: "opt-in", registered: true },
|
|
129
|
-
{ id: "vscode", displayName: "Copilot (VS Code)", modelName: "VSCode", logoKey: "copilot", steer: true, sync: true, idleWake: "opt-in", registered: true },
|
|
130
|
-
{ id: "gemini", displayName: "Gemini CLI", modelName: "Gemini", logoKey: "gemini", steer: true, sync: true, idleWake: "opt-in", registered: true },
|
|
131
|
-
// U29 and U33 establish these capabilities after their spikes.
|
|
132
|
-
{ id: "antigravity", displayName: "Antigravity CLI", modelName: "Antigravity", logoKey: null, steer: false, sync: false, idleWake: "none", registered: true },
|
|
133
|
-
{ id: "qwen", displayName: "Qwen Code", modelName: "Qwen", logoKey: "qwen", steer: true, sync: true, idleWake: "default", registered: true },
|
|
134
|
-
{ id: "muse", displayName: "Muse Code", modelName: "Muse", logoKey: null, steer: false, sync: false, idleWake: "none", registered: true },
|
|
135
|
-
{ id: "generic", displayName: "MCP agent", modelName: "Agent", logoKey: null, steer: false, sync: false, idleWake: "none", registered: true }
|
|
136
|
-
];
|
|
137
|
-
function harnessInfo(id) {
|
|
138
|
-
return HARNESS_REGISTRY.find((row) => row.id === id) ?? {
|
|
139
|
-
id,
|
|
140
|
-
displayName: id.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" "),
|
|
141
|
-
modelName: "Agent",
|
|
142
|
-
logoKey: null,
|
|
143
|
-
steer: false,
|
|
144
|
-
sync: false,
|
|
145
|
-
idleWake: "none",
|
|
146
|
-
registered: false
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ../contracts/src/messaging/agent-names.ts
|
|
151
|
-
var MAX_AGENT_NAME_BYTES = 80;
|
|
152
|
-
var unsafe = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff]/u;
|
|
153
|
-
var reserved = /\b(?:admin(?:istrator)?|system|khala|moderator|owner|human|security|support|official)\b/iu;
|
|
154
|
-
function validateAgentName(input) {
|
|
155
|
-
if (typeof input !== "string") return { ok: false, error: "blank" };
|
|
156
|
-
const name = input.trim().normalize("NFC");
|
|
157
|
-
if (!name) return { ok: false, error: "blank" };
|
|
158
|
-
if (unsafe.test(name)) return { ok: false, error: "invalid_characters" };
|
|
159
|
-
if (new TextEncoder().encode(name).byteLength > MAX_AGENT_NAME_BYTES) return { ok: false, error: "too_long" };
|
|
160
|
-
if (reserved.test(name.normalize("NFKC"))) return { ok: false, error: "reserved" };
|
|
161
|
-
return { ok: true, name };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ../contracts/src/m1/agent-join.ts
|
|
165
|
-
var M1_LABEL_MAX_BYTES = 512;
|
|
166
|
-
var agentConfirmPagePath = (id) => `/agent/confirm?joinId=${encodeURIComponent(id)}`;
|
|
167
|
-
function readHarness(input, path) {
|
|
168
|
-
if (typeof input !== "string") fail(path, "wrong_type");
|
|
169
|
-
if (!isHarnessId(input)) fail(path, "invalid_value");
|
|
170
|
-
return input;
|
|
171
|
-
}
|
|
172
|
-
function readHttpUrl(input, path) {
|
|
173
|
-
if (typeof input !== "string") fail(path, "wrong_type");
|
|
174
|
-
let url;
|
|
175
|
-
try {
|
|
176
|
-
url = new URL(input);
|
|
177
|
-
} catch {
|
|
178
|
-
fail(path, "invalid_value");
|
|
179
|
-
}
|
|
180
|
-
if (url.username || url.password || url.hash || !(url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) fail(path, "invalid_value");
|
|
181
|
-
return input;
|
|
182
|
-
}
|
|
183
|
-
function readMatrixUserId(input, path) {
|
|
184
|
-
const value = identifier(input, path);
|
|
185
|
-
if (!/^@[^\s:]+:\S+$/u.test(value)) fail(path, "invalid_value");
|
|
186
|
-
return value;
|
|
187
|
-
}
|
|
188
|
-
function readRoomId(input, path) {
|
|
189
|
-
const value = identifier(input, path);
|
|
190
|
-
if (!/^!\S+$/u.test(value)) fail(path, "invalid_value");
|
|
191
|
-
return value;
|
|
192
|
-
}
|
|
193
|
-
var validAgentSessionId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u.test(value);
|
|
194
|
-
var validAgentRejoinSecret = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{43}$/u.test(value);
|
|
195
|
-
|
|
196
|
-
export {
|
|
197
|
-
fail,
|
|
198
|
-
decodeWith,
|
|
199
|
-
object,
|
|
200
|
-
array,
|
|
201
|
-
elementPath,
|
|
202
|
-
utf8Length,
|
|
203
|
-
isWellFormed,
|
|
204
|
-
text,
|
|
205
|
-
label,
|
|
206
|
-
displayText,
|
|
207
|
-
identifier,
|
|
208
|
-
nullable,
|
|
209
|
-
literal,
|
|
210
|
-
version,
|
|
211
|
-
safeInteger,
|
|
212
|
-
utcTimestamp,
|
|
213
|
-
reserved,
|
|
214
|
-
validateAgentName,
|
|
215
|
-
isHarnessId,
|
|
216
|
-
LEGACY_HARNESSES,
|
|
217
|
-
HARNESS_REGISTRY,
|
|
218
|
-
harnessInfo,
|
|
219
|
-
M1_LABEL_MAX_BYTES,
|
|
220
|
-
agentConfirmPagePath,
|
|
221
|
-
readHarness,
|
|
222
|
-
readHttpUrl,
|
|
223
|
-
readMatrixUserId,
|
|
224
|
-
readRoomId,
|
|
225
|
-
validAgentSessionId,
|
|
226
|
-
validAgentRejoinSecret
|
|
227
|
-
};
|