khala-cli 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-LN3G2LJV.mjs → chunk-6LC66VHK.mjs} +5 -5
- package/dist/{chunk-ENPBK5W2.mjs → chunk-7G2G4E4U.mjs} +84 -35
- package/dist/{chunk-LQ6KNUNN.mjs → chunk-EW22K3JO.mjs} +393 -160
- package/dist/{chunk-GE3RXO2X.mjs → chunk-EXHCVFVH.mjs} +2 -2
- package/dist/{chunk-MU4FUENP.mjs → chunk-H46OG22B.mjs} +20 -5
- package/dist/{chunk-LHAMBQK7.mjs → chunk-HB3UGIZG.mjs} +3 -3
- package/dist/{chunk-DC3OX6F4.mjs → chunk-IR3VN2ID.mjs} +1 -1
- package/dist/{chunk-XEYKJMZL.mjs → chunk-KCJFSVDN.mjs} +54 -3
- package/dist/{chunk-6OADQWV7.mjs → chunk-MBLP7LQJ.mjs} +93 -32
- package/dist/{chunk-ZRFXZK72.mjs → chunk-OLZ62X7S.mjs} +1 -1
- package/dist/{chunk-ZKDDKUUA.mjs → chunk-PXKGKVGW.mjs} +1 -1
- package/dist/{chunk-AK74S7EN.mjs → chunk-RSDOTTZQ.mjs} +2 -2
- package/dist/{chunk-7CVU5OZ7.mjs → chunk-SQXDATEC.mjs} +1 -1
- package/dist/chunk-T7YRVE43.mjs +238 -0
- package/dist/{chunk-OS2JLDL7.mjs → chunk-TZKEQIHP.mjs} +4 -2
- package/dist/{chunk-PJ3E3IVH.mjs → chunk-V4Y26O4R.mjs} +7 -7
- package/dist/{chunk-FA6Z65DW.mjs → chunk-V7M27QLT.mjs} +6 -4
- package/dist/{chunk-MZS7XFMC.mjs → chunk-Y2DRTRBJ.mjs} +2 -2
- package/dist/{chunk-FUU775QM.mjs → chunk-Y4WVILEJ.mjs} +14 -1
- package/dist/khala.mjs +10 -10
- package/dist/web/assets/local-C1vz22ps.js +9 -0
- package/dist/web/assets/local-DdjwMERJ.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-N5VYKQMR.mjs +0 -108
- package/dist/web/assets/local-Bk8hMIT-.js +0 -9
- package/dist/web/assets/local-vdvavyq0.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
ensureHelper
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HB3UGIZG.mjs";
|
|
5
5
|
import {
|
|
6
6
|
KhalaClientError,
|
|
7
7
|
LOCAL_LONG_POLL_MAX_S,
|
|
@@ -16,19 +16,19 @@ import {
|
|
|
16
16
|
decodeLocalNameContent,
|
|
17
17
|
decodeLocalSendResult,
|
|
18
18
|
localRoomPath
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-TZKEQIHP.mjs";
|
|
20
|
+
import "./chunk-OLZ62X7S.mjs";
|
|
21
21
|
import "./chunk-5GLTKWJ5.mjs";
|
|
22
22
|
import {
|
|
23
23
|
CHANNEL_EVENT_TYPE,
|
|
24
24
|
memberRenameContent
|
|
25
25
|
} from "./chunk-P5CP32X2.mjs";
|
|
26
|
-
import "./chunk-
|
|
26
|
+
import "./chunk-KCJFSVDN.mjs";
|
|
27
27
|
import {
|
|
28
28
|
LISTENING_MODE_COMMAND_TYPE
|
|
29
29
|
} from "./chunk-AUJEUKVM.mjs";
|
|
30
30
|
import "./chunk-SN3STAO2.mjs";
|
|
31
|
-
import "./chunk-
|
|
31
|
+
import "./chunk-PXKGKVGW.mjs";
|
|
32
32
|
|
|
33
33
|
// src/local/session.ts
|
|
34
34
|
import { randomUUID } from "node:crypto";
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
+
listChannels,
|
|
3
4
|
readCursor,
|
|
4
5
|
readEntries,
|
|
5
6
|
readListeningMode
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-T7YRVE43.mjs";
|
|
7
8
|
import {
|
|
8
9
|
CURSOR_WORKSPACE_ENV,
|
|
9
10
|
cursorSessionId
|
|
10
11
|
} from "./chunk-VUE2DDYY.mjs";
|
|
11
12
|
import {
|
|
13
|
+
SESSION_ID_PATTERN,
|
|
12
14
|
readJson,
|
|
13
15
|
readStatus,
|
|
14
16
|
sessionFiles,
|
|
15
17
|
writeJsonAtomic
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-KCJFSVDN.mjs";
|
|
17
19
|
import {
|
|
18
20
|
HARNESSES
|
|
19
21
|
} from "./chunk-SN3STAO2.mjs";
|
|
@@ -67,6 +69,7 @@ function channelLabel(name) {
|
|
|
67
69
|
async function watchSession(files, io) {
|
|
68
70
|
const nonce = randomUUID();
|
|
69
71
|
let watcher;
|
|
72
|
+
let timer;
|
|
70
73
|
let running;
|
|
71
74
|
let dirty = false;
|
|
72
75
|
let done = false;
|
|
@@ -79,47 +82,79 @@ async function watchSession(files, io) {
|
|
|
79
82
|
done = true;
|
|
80
83
|
code = exit;
|
|
81
84
|
watcher?.close();
|
|
85
|
+
clearInterval(timer);
|
|
82
86
|
finish();
|
|
83
87
|
};
|
|
84
88
|
const abort = () => stop();
|
|
85
89
|
try {
|
|
86
|
-
const session = await readJson(files.session);
|
|
87
90
|
const status = await readStatus(files);
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
const initialSession = await readJson(files.session);
|
|
92
|
+
if (!["connected", "send_failed"].includes(status?.state ?? "") || !initialSession && !(await listChannels(files)).length) return 0;
|
|
93
|
+
const initialDelivered = /* @__PURE__ */ new Map();
|
|
94
|
+
const seedBaseline = async (target, session) => {
|
|
95
|
+
initialDelivered.set(target.dir, {
|
|
96
|
+
identity: JSON.stringify([session.roomId, session.userId]),
|
|
97
|
+
cursor: await readCursor(target)
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
if (initialSession) await seedBaseline(files, initialSession);
|
|
101
|
+
for (const channel of await listChannels(files)) {
|
|
102
|
+
const session = await readJson(channel.files.session);
|
|
103
|
+
if (session) await seedBaseline(channel.files, session);
|
|
104
|
+
}
|
|
90
105
|
const evaluate = async () => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
const mode = await readListeningMode(files);
|
|
99
|
-
const entries = await readEntries(files);
|
|
100
|
-
const observed = await readJson(path.join(files.dir, OBSERVATION));
|
|
101
|
-
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;
|
|
102
|
-
const fresh = entries.slice(Math.max(initialDelivered, position));
|
|
103
|
-
const latestMode = await readListeningMode(files);
|
|
104
|
-
const latestSession = await readJson(files.session);
|
|
105
|
-
const latestStatus = await readStatus(files);
|
|
106
|
-
const latestOwner = await readJson(path.join(files.dir, MARKER));
|
|
107
|
-
if (latestOwner?.nonce !== nonce || latestSession?.userId !== session.userId || latestSession?.roomId !== session.roomId || !["connected", "send_failed"].includes(latestStatus?.state ?? "")) {
|
|
106
|
+
const ownsSession = async () => {
|
|
107
|
+
const owner = await readJson(path.join(files.dir, MARKER));
|
|
108
|
+
const status2 = await readJson(files.status);
|
|
109
|
+
return owner?.nonce === nonce && !["disconnected", "closed"].includes(status2?.state ?? "");
|
|
110
|
+
};
|
|
111
|
+
if (!await ownsSession()) {
|
|
108
112
|
stop();
|
|
109
113
|
return;
|
|
110
114
|
}
|
|
111
|
-
for (const
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
+
for (const channel of await listChannels(files)) {
|
|
116
|
+
const target = channel.files;
|
|
117
|
+
const session = await readJson(target.session);
|
|
118
|
+
const status2 = await readStatus(target);
|
|
119
|
+
if (!session || session.roomId !== channel.roomId || typeof session.userId !== "string" || !["connected", "send_failed"].includes(status2?.state ?? "")) continue;
|
|
120
|
+
const identity = JSON.stringify([session.roomId, session.userId]);
|
|
121
|
+
if (initialDelivered.get(target.dir)?.identity !== identity) {
|
|
122
|
+
await seedBaseline(target, session);
|
|
123
|
+
}
|
|
124
|
+
const mode = await readListeningMode(target);
|
|
125
|
+
const entries = await readEntries(target);
|
|
126
|
+
const observed = await readJson(path.join(target.dir, OBSERVATION));
|
|
127
|
+
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;
|
|
128
|
+
const baseline = initialDelivered.get(target.dir).cursor;
|
|
129
|
+
const initialCount = baseline.deliveredCount === 0 || entries[baseline.deliveredCount - 1]?.eventId === baseline.lastDeliveredEventId ? baseline.deliveredCount : 0;
|
|
130
|
+
const fresh = entries.slice(Math.max(initialCount, position));
|
|
131
|
+
const latestMode = await readListeningMode(target);
|
|
132
|
+
const latestSession = await readJson(target.session);
|
|
133
|
+
const latestStatus = await readStatus(target);
|
|
134
|
+
if (!await ownsSession()) {
|
|
135
|
+
stop();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (latestSession?.userId !== session.userId || latestSession?.roomId !== session.roomId || !["connected", "send_failed"].includes(latestStatus?.state ?? "")) continue;
|
|
139
|
+
const messages = fresh.filter((entry) => entry.kind === "message" && entry.sender !== session.userId && entry.roomId === session.roomId);
|
|
140
|
+
if (!done && mode !== "async" && latestMode !== "async" && messages.length) {
|
|
141
|
+
const count = messages.filter((entry) => mentions(entry, latestStatus?.displayName)).length;
|
|
142
|
+
io.write(`khala: ${messages.length} new message${messages.length === 1 ? "" : "s"} in #${channelLabel(latestStatus?.channelName ?? channel.channelName ?? "channel")} (${count} mentions you)
|
|
115
143
|
`);
|
|
144
|
+
}
|
|
145
|
+
if (position !== entries.length && !done && await ownsSession() && JSON.stringify(await readJson(target.session)) === JSON.stringify(latestSession)) {
|
|
146
|
+
try {
|
|
147
|
+
await writeJsonAtomic(path.join(target.dir, OBSERVATION), {
|
|
148
|
+
userId: session.userId,
|
|
149
|
+
roomId: session.roomId,
|
|
150
|
+
count: entries.length,
|
|
151
|
+
lastEventId: entries.at(-1)?.eventId ?? null
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (await readJson(target.session)) throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
116
157
|
}
|
|
117
|
-
if (position !== entries.length && !done && (await readJson(path.join(files.dir, MARKER)))?.nonce === nonce) await writeJsonAtomic(path.join(files.dir, OBSERVATION), {
|
|
118
|
-
userId: session.userId,
|
|
119
|
-
roomId: session.roomId,
|
|
120
|
-
count: entries.length,
|
|
121
|
-
lastEventId: entries.at(-1)?.eventId ?? null
|
|
122
|
-
});
|
|
123
158
|
};
|
|
124
159
|
const notify = () => {
|
|
125
160
|
if (done) return;
|
|
@@ -141,6 +176,7 @@ async function watchSession(files, io) {
|
|
|
141
176
|
if (filename === null || [MARKER, "inbox.jsonl", "cursor.json", "mode.json", "session.json", "status.json"].includes(String(filename))) notify();
|
|
142
177
|
});
|
|
143
178
|
watcher.on("error", () => stop(1));
|
|
179
|
+
timer = setInterval(notify, 100);
|
|
144
180
|
io.signal.addEventListener("abort", abort, { once: true });
|
|
145
181
|
if (io.signal.aborted) stop();
|
|
146
182
|
else notify();
|
|
@@ -148,6 +184,7 @@ async function watchSession(files, io) {
|
|
|
148
184
|
await running;
|
|
149
185
|
return code;
|
|
150
186
|
} finally {
|
|
187
|
+
clearInterval(timer);
|
|
151
188
|
watcher?.close();
|
|
152
189
|
io.signal.removeEventListener("abort", abort);
|
|
153
190
|
await fs.unlink(leaseFile(files, nonce)).catch((error) => {
|
|
@@ -155,13 +192,24 @@ async function watchSession(files, io) {
|
|
|
155
192
|
});
|
|
156
193
|
}
|
|
157
194
|
}
|
|
195
|
+
var WATCH_USAGE = "usage: khala watch [--harness claude|codex|cursor --session <id>]";
|
|
158
196
|
async function run(argv) {
|
|
159
|
-
if (argv.length
|
|
197
|
+
if (argv.length === 1 && ["--help", "-h"].includes(argv[0])) {
|
|
198
|
+
console.log(WATCH_USAGE);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
const invalid = (reason) => {
|
|
202
|
+
console.error(`khala: ${reason}
|
|
203
|
+
${WATCH_USAGE}`);
|
|
204
|
+
return 1;
|
|
205
|
+
};
|
|
206
|
+
if (argv.length !== 0 && !(argv.length === 2 && argv[0] === "--session") && !(argv.length === 4 && argv[0] === "--harness" && argv[2] === "--session")) return invalid("invalid_arguments");
|
|
160
207
|
const harness = resolveHarness(argv, process.env);
|
|
161
|
-
if (harness === "invalid")
|
|
208
|
+
if (harness === "invalid") return invalid("invalid_harness");
|
|
162
209
|
const index = argv.indexOf("--session");
|
|
163
210
|
const id = index === -1 ? resolveSessionId(harness, void 0, process.env) : argv[index + 1];
|
|
164
|
-
if (!id)
|
|
211
|
+
if (!id) return invalid("session_unknown");
|
|
212
|
+
if (!SESSION_ID_PATTERN.test(id)) return invalid("invalid_session_id");
|
|
165
213
|
const files = sessionFiles(harness, id, process.env);
|
|
166
214
|
const controller = new AbortController();
|
|
167
215
|
const stop = () => controller.abort();
|
|
@@ -183,5 +231,6 @@ export {
|
|
|
183
231
|
monitorArmed,
|
|
184
232
|
mentions,
|
|
185
233
|
watchSession,
|
|
234
|
+
WATCH_USAGE,
|
|
186
235
|
run
|
|
187
236
|
};
|