khala-cli 0.4.9 → 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/dist/THIRD_PARTY_NOTICES.txt +28 -0
- package/dist/{chunk-NIX5F4LT.mjs → chunk-2AMWQ7RB.mjs} +330 -66
- package/dist/{chunk-PAJ4JJPV.mjs → chunk-3E5DH3Y6.mjs} +43 -6
- package/dist/{chunk-NGSYOVQ4.mjs → chunk-4BP7V3OP.mjs} +2 -2
- package/dist/{chunk-TEFOQXBK.mjs → chunk-4BZQ4GVW.mjs} +3 -3
- package/dist/{chunk-ITFD3JZS.mjs → chunk-5JSGA6OB.mjs} +424 -78
- package/dist/{chunk-QKWEZXR3.mjs → chunk-6AQVW5W7.mjs} +12 -4
- package/dist/{chunk-UK5GJO4Y.mjs → chunk-6J2PUM7L.mjs} +6 -5
- package/dist/{chunk-P3YIN7G7.mjs → chunk-6SUV3EW6.mjs} +11 -10
- package/dist/{chunk-GSZADJMW.mjs → chunk-ARIDZOTI.mjs} +309 -81
- package/dist/chunk-C2WEDMAF.mjs +15 -0
- package/dist/{chunk-DJM23SIU.mjs → chunk-CHC4VZF5.mjs} +7 -8
- package/dist/{chunk-BLCRVRIK.mjs → chunk-CKLCR5G4.mjs} +456 -87
- package/dist/{chunk-7R3EIDGR.mjs → chunk-EVT2DWNZ.mjs} +1 -1
- package/dist/{chunk-KMLXOPBQ.mjs → chunk-FJVVSKZ3.mjs} +4 -2
- package/dist/{chunk-3KDOTR54.mjs → chunk-G3NMB4PY.mjs} +1 -1
- package/dist/{chunk-SSO5RFOX.mjs → chunk-HHEZVPRR.mjs} +1 -1
- package/dist/{chunk-BV45NK7K.mjs → chunk-JKOJQTFT.mjs} +100 -6
- package/dist/{chunk-T7IL7V7Q.mjs → chunk-NDMFD5IM.mjs} +13 -10
- package/dist/{chunk-TANCKTDK.mjs → chunk-OEV5DFLW.mjs} +70 -5
- package/dist/{chunk-BEO7WIIM.mjs → chunk-OIZ4R5H7.mjs} +28 -8
- package/dist/{chunk-FZEH2IXZ.mjs → chunk-PJKI7SRI.mjs} +176 -26
- package/dist/{chunk-NPSQNCOY.mjs → chunk-PN25BUQ4.mjs} +13 -4
- package/dist/{chunk-3R6HFSLL.mjs → chunk-QDPJ4VT2.mjs} +92 -38
- package/dist/{chunk-FO5GXCST.mjs → chunk-SRZTHCXP.mjs} +5 -5
- package/dist/{chunk-LBT47HLZ.mjs → chunk-TSK3HJEG.mjs} +10 -9
- package/dist/{chunk-6BO3JSUP.mjs → chunk-UX34IVN5.mjs} +2 -2
- package/dist/{chunk-V4F3SYKW.mjs → chunk-VAC3JTAV.mjs} +25 -17
- package/dist/{chunk-PDZ5Z5ZE.mjs → chunk-VDZK4LW3.mjs} +24 -9
- package/dist/chunk-W6HM6F2U.mjs +901 -0
- package/dist/{chunk-WDOHRV3D.mjs → chunk-WPNMWNCL.mjs} +14 -14
- package/dist/chunk-WUDXHF2Q.mjs +14 -0
- package/dist/{chunk-XBQDIKB5.mjs → chunk-WZJ6DQJV.mjs} +9 -8
- package/dist/{chunk-ANSR4IAW.mjs → chunk-XR625T3I.mjs} +2 -2
- package/dist/{chunk-K7V7X6WU.mjs → chunk-ZSKLNZWZ.mjs} +21 -4
- package/dist/iterm2_send.py +56 -0
- package/dist/khala.mjs +11 -11
- 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 +1 -1
- package/dist/chunk-47XNLAJG.mjs +0 -38
- package/dist/chunk-XUCWSYVS.mjs +0 -148
- package/dist/web/assets/local-CFzRHZpB.js +0 -9
- package/dist/web/assets/local-ciypUFoK.css +0 -1
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
readPane
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6AQVW5W7.mjs";
|
|
5
5
|
import {
|
|
6
6
|
CURSOR_DEFAULT_SESSION,
|
|
7
7
|
CURSOR_WORKSPACE_ENV,
|
|
8
8
|
createMuseMonitorDriver,
|
|
9
|
+
createQwenSocketDriver,
|
|
9
10
|
cursorSessionId,
|
|
10
11
|
driverAllowed,
|
|
11
12
|
museCliPath,
|
|
12
13
|
museMonitorInstruction,
|
|
13
14
|
museStopWakeText,
|
|
14
15
|
museWatchCommand,
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
qwenInboundAllowed,
|
|
17
|
+
qwenSession,
|
|
18
|
+
readListeningMode,
|
|
19
|
+
readWakeSettings,
|
|
20
|
+
verifyQwenTranscript
|
|
21
|
+
} from "./chunk-2AMWQ7RB.mjs";
|
|
17
22
|
import {
|
|
18
23
|
listChannels,
|
|
19
24
|
readActivity,
|
|
20
25
|
readCursor,
|
|
21
|
-
unread
|
|
22
|
-
|
|
26
|
+
unread,
|
|
27
|
+
wakesAgent
|
|
28
|
+
} from "./chunk-OEV5DFLW.mjs";
|
|
23
29
|
import {
|
|
24
30
|
HARNESS_REGISTRY,
|
|
25
31
|
SESSION_ID_PATTERN,
|
|
@@ -27,25 +33,29 @@ import {
|
|
|
27
33
|
ensureStateDir,
|
|
28
34
|
isHarnessId,
|
|
29
35
|
nearestNonShellAncestor,
|
|
36
|
+
openSessionDir,
|
|
30
37
|
readJson,
|
|
31
38
|
readProcess,
|
|
39
|
+
readProcessArguments,
|
|
32
40
|
stateRoot,
|
|
33
41
|
walkAncestors,
|
|
34
42
|
writeJsonAtomic
|
|
35
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-PN25BUQ4.mjs";
|
|
36
44
|
import {
|
|
45
|
+
CODEX_IDLE_WAKE_NOTICE,
|
|
37
46
|
cancelAttempt,
|
|
38
47
|
failAttempt,
|
|
39
48
|
newNonce,
|
|
40
49
|
readWakeState,
|
|
41
50
|
recordAttempt,
|
|
51
|
+
resetWakeDriver,
|
|
42
52
|
settleAttempts,
|
|
43
53
|
wakeLine,
|
|
44
54
|
withWakeLock
|
|
45
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-ZSKLNZWZ.mjs";
|
|
46
56
|
import {
|
|
47
57
|
init_define_KHALA_BUNDLE
|
|
48
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-EVT2DWNZ.mjs";
|
|
49
59
|
|
|
50
60
|
// src/harness/index.ts
|
|
51
61
|
init_define_KHALA_BUNDLE();
|
|
@@ -55,7 +65,14 @@ init_define_KHALA_BUNDLE();
|
|
|
55
65
|
|
|
56
66
|
// src/harness/codecs/claude-style.ts
|
|
57
67
|
init_define_KHALA_BUNDLE();
|
|
58
|
-
|
|
68
|
+
|
|
69
|
+
// src/harness/adapter.ts
|
|
70
|
+
init_define_KHALA_BUNDLE();
|
|
71
|
+
var STOP_NUDGE = "Khala: channel messages are waiting. Call khala_read.";
|
|
72
|
+
|
|
73
|
+
// src/harness/codecs/claude-style.ts
|
|
74
|
+
var events = { SessionStart: "start", UserPromptSubmit: "prompt", PostToolUse: "tool", Stop: "stop", StopFailure: "limit" };
|
|
75
|
+
var LIMIT_ERRORS = ["rate_limit", "billing_error"];
|
|
59
76
|
var names = { prompt: "UserPromptSubmit", tool: "PostToolUse", stop: "Stop" };
|
|
60
77
|
var claudeStyleCodec = {
|
|
61
78
|
promptDeliversWithoutWake: true,
|
|
@@ -64,7 +81,7 @@ var claudeStyleCodec = {
|
|
|
64
81
|
parse(stdin) {
|
|
65
82
|
try {
|
|
66
83
|
const input = JSON.parse(stdin);
|
|
67
|
-
if (!input || typeof input.session_id !== "string" || typeof input.hook_event_name !== "string" || !Object.hasOwn(events, input.hook_event_name) || input.stop_hook_active !== void 0 && typeof input.stop_hook_active !== "boolean") return null;
|
|
84
|
+
if (!input || typeof input.session_id !== "string" || typeof input.hook_event_name !== "string" || !Object.hasOwn(events, input.hook_event_name) || input.stop_hook_active !== void 0 && typeof input.stop_hook_active !== "boolean" || input.hook_event_name === "StopFailure" && !LIMIT_ERRORS.includes(input.error)) return null;
|
|
68
85
|
return {
|
|
69
86
|
sessionId: input.session_id,
|
|
70
87
|
event: events[input.hook_event_name],
|
|
@@ -78,10 +95,11 @@ var claudeStyleCodec = {
|
|
|
78
95
|
}
|
|
79
96
|
},
|
|
80
97
|
noop: () => "",
|
|
81
|
-
render(kind, frame) {
|
|
82
|
-
return JSON.stringify(kind === "stop" ? { decision: "block", reason:
|
|
98
|
+
render(kind, frame, stopContext = false) {
|
|
99
|
+
return JSON.stringify(kind === "stop" && !stopContext ? { decision: "block", reason: STOP_NUDGE } : { hookSpecificOutput: { hookEventName: names[kind], additionalContext: frame } }) + "\n";
|
|
83
100
|
}
|
|
84
101
|
};
|
|
102
|
+
var claudeCodec = { ...claudeStyleCodec, stopContextFloor: "2.1.307" };
|
|
85
103
|
|
|
86
104
|
// src/wake/terminal/driver.ts
|
|
87
105
|
init_define_KHALA_BUNDLE();
|
|
@@ -145,7 +163,7 @@ var runTerminalCommand = async (command, argv, env, signal) => {
|
|
|
145
163
|
const { stdout } = await exec(command, [...argv], { env, signal, timeout: 5e3, maxBuffer: 1024 * 1024, shell: false });
|
|
146
164
|
return stdout;
|
|
147
165
|
};
|
|
148
|
-
async function ownsTerminal(pid, tty, env, signal,
|
|
166
|
+
async function ownsTerminal(pid, tty, env, signal, run2 = runTerminalCommand, platform = process.platform) {
|
|
149
167
|
if (!Number.isSafeInteger(pid) || pid <= 0 || !/^\/dev\/(?:pts\/\d+|tty[^\s/]+)$/.test(tty)) return false;
|
|
150
168
|
try {
|
|
151
169
|
if (platform === "linux") {
|
|
@@ -160,7 +178,7 @@ async function ownsTerminal(pid, tty, env, signal, run = runTerminalCommand, pla
|
|
|
160
178
|
return ttyNr !== 0 && ttyNr === encoded && pgid > 0 && pgid === tpgid;
|
|
161
179
|
}
|
|
162
180
|
if (platform === "darwin") {
|
|
163
|
-
const output = await
|
|
181
|
+
const output = await run2("ps", ["-o", "tty=,pgid=,tpgid=", "-p", String(pid)], { ...env, LC_ALL: "C" }, signal);
|
|
164
182
|
const match = /^\s*(\S+)\s+(\d+)\s+(\d+)\s*$/.exec(output);
|
|
165
183
|
return !!match && `/dev/${match[1]}` === tty && Number(match[2]) > 0 && match[2] === match[3];
|
|
166
184
|
}
|
|
@@ -174,8 +192,8 @@ init_define_KHALA_BUNDLE();
|
|
|
174
192
|
function tmuxArgv(pane, argv) {
|
|
175
193
|
return [...pane.socket ? ["-S", pane.socket] : [], ...argv];
|
|
176
194
|
}
|
|
177
|
-
async function inspectTmux(pane,
|
|
178
|
-
const query = await
|
|
195
|
+
async function inspectTmux(pane, run2, env, signal, readProcess2) {
|
|
196
|
+
const query = await run2("tmux", tmuxArgv(pane, [
|
|
179
197
|
"display-message",
|
|
180
198
|
"-p",
|
|
181
199
|
"-t",
|
|
@@ -187,7 +205,7 @@ async function inspectTmux(pane, run, env, signal, readProcess2) {
|
|
|
187
205
|
const pid = Number(fields[0]), cursorX = Number(fields[3]), cursorY = Number(fields[4]);
|
|
188
206
|
if (!Number.isSafeInteger(pid) || pid <= 0 || !Number.isSafeInteger(cursorX) || cursorX < 0 || !Number.isSafeInteger(cursorY) || cursorY < 0 || !fields[5]) return null;
|
|
189
207
|
if (pid !== pane.agentPid && !(await ancestors(pane.agentPid, readProcess2)).some((parent) => parent.pid === pid)) return null;
|
|
190
|
-
const line = await
|
|
208
|
+
const line = await run2("tmux", tmuxArgv(pane, [
|
|
191
209
|
"capture-pane",
|
|
192
210
|
"-p",
|
|
193
211
|
"-e",
|
|
@@ -200,21 +218,21 @@ async function inspectTmux(pane, run, env, signal, readProcess2) {
|
|
|
200
218
|
]), env, signal);
|
|
201
219
|
return { tty: fields[5], cursorX, cursorY, line };
|
|
202
220
|
}
|
|
203
|
-
async function sendTmux(pane, text, enter,
|
|
204
|
-
await
|
|
221
|
+
async function sendTmux(pane, text, enter, run2, env, signal) {
|
|
222
|
+
await run2("tmux", tmuxArgv(pane, ["send-keys", "-t", pane.paneId, ...enter ? ["Enter"] : ["-l", text]]), env, signal);
|
|
205
223
|
}
|
|
206
224
|
|
|
207
225
|
// src/wake/terminal/wezterm.ts
|
|
208
226
|
init_define_KHALA_BUNDLE();
|
|
209
|
-
async function inspectWezterm(pane,
|
|
210
|
-
const rows = JSON.parse(await
|
|
227
|
+
async function inspectWezterm(pane, run2, env, signal) {
|
|
228
|
+
const rows = JSON.parse(await run2("wezterm", ["cli", "list", "--format", "json"], env, signal));
|
|
211
229
|
if (!Array.isArray(rows)) return { reason: "wezterm_pane_missing" };
|
|
212
230
|
const row = rows.find((value) => value && typeof value === "object" && String(value.pane_id) === pane.paneId);
|
|
213
231
|
if (!row) return { reason: "wezterm_pane_missing" };
|
|
214
232
|
if (typeof row.tty_name !== "string" || !row.tty_name) return { reason: "wezterm_tty_unavailable" };
|
|
215
233
|
if (!Number.isSafeInteger(row.cursor_x) || row.cursor_x < 0 || !Number.isSafeInteger(row.cursor_y) || row.cursor_y < 0)
|
|
216
234
|
return { reason: "wezterm_cursor_unavailable" };
|
|
217
|
-
const line = await
|
|
235
|
+
const line = await run2("wezterm", [
|
|
218
236
|
"cli",
|
|
219
237
|
"get-text",
|
|
220
238
|
"--pane-id",
|
|
@@ -228,8 +246,67 @@ async function inspectWezterm(pane, run, env, signal) {
|
|
|
228
246
|
const captured = line.replace(/\r\n(?:\x1b(?:\[[0-9;:]*m|\(B))*\n$/, "").replace(/\x1b\(B/g, "");
|
|
229
247
|
return { view: { tty: row.tty_name, cursorX: row.cursor_x, cursorY: row.cursor_y, line: captured } };
|
|
230
248
|
}
|
|
231
|
-
async function sendWezterm(pane, text, enter,
|
|
232
|
-
await
|
|
249
|
+
async function sendWezterm(pane, text, enter, run2, env, signal) {
|
|
250
|
+
await run2("wezterm", ["cli", "send-text", "--pane-id", pane.paneId, "--no-paste", enter ? "\r" : text], env, signal);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/wake/terminal/kitty.ts
|
|
254
|
+
init_define_KHALA_BUNDLE();
|
|
255
|
+
function kittyArgv(pane, args) {
|
|
256
|
+
if (!pane.socket?.startsWith("unix:") || pane.socket.length <= 5 || pane.socket.includes("\0")) throw new Error("kitty_unix_socket_required");
|
|
257
|
+
return ["@", "--to", pane.socket, ...args];
|
|
258
|
+
}
|
|
259
|
+
async function inspectKitty(pane, run2, env, signal, read) {
|
|
260
|
+
try {
|
|
261
|
+
const windows = JSON.parse(await run2("kitten", kittyArgv(pane, ["ls"]), env, signal));
|
|
262
|
+
const matches = windows.flatMap((os2) => os2.tabs.flatMap((tab) => tab.windows)).filter((window) => String(window.id) === pane.paneId);
|
|
263
|
+
if (matches.length !== 1) return { reason: "kitty_window_missing" };
|
|
264
|
+
const foreground = matches[0].foreground_processes;
|
|
265
|
+
if (!Array.isArray(foreground) || foreground.length === 0) return { reason: "kitty_foreground_unavailable" };
|
|
266
|
+
for (const process2 of foreground) {
|
|
267
|
+
if (!Number.isSafeInteger(process2.pid) || process2.pid <= 0 || process2.pid !== pane.agentPid && !(await ancestors(process2.pid, read)).some((parent) => parent.pid === pane.agentPid)) return { reason: "kitty_window_not_owned" };
|
|
268
|
+
}
|
|
269
|
+
const ttyName = (await run2("ps", ["-o", "tty=", "-p", String(foreground[0].pid)], { ...env, LC_ALL: "C" }, signal)).trim();
|
|
270
|
+
if (!/^(?:pts\/\d+|tty[^\s/]+)$/.test(ttyName)) return { reason: "kitty_tty_unavailable" };
|
|
271
|
+
const screen = await run2("kitten", kittyArgv(pane, ["get-text", "--match", `id:${pane.paneId}`, "--extent", "screen", "--ansi", "--add-cursor"]), env, signal);
|
|
272
|
+
const cursor2 = /\x1b\[\?25[hl]\x1b\[(\d+);(\d+)H(?:\x1b\[\?12[hl]|\x1b\[[1-6] q)\n?$/.exec(screen);
|
|
273
|
+
if (!cursor2) return { reason: "kitty_cursor_unavailable" };
|
|
274
|
+
const cursorY = Number(cursor2[1]) - 1, cursorX = Number(cursor2[2]) - 1;
|
|
275
|
+
const rows = screen.slice(0, cursor2.index).split("\n");
|
|
276
|
+
if (!Number.isSafeInteger(cursorX) || cursorX < 0 || !Number.isSafeInteger(cursorY) || cursorY < 0 || cursorY >= rows.length)
|
|
277
|
+
return { reason: "kitty_cursor_unavailable" };
|
|
278
|
+
const styling = rows.slice(0, cursorY).join("\n").match(/\x1b\[[0-9;:]*m/g)?.join("") ?? "";
|
|
279
|
+
return { view: { tty: `/dev/${ttyName}`, cursorX, cursorY, line: styling + rows[cursorY] } };
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return { reason: error instanceof Error && error.message === "kitty_unix_socket_required" ? error.message : "kitty_probe_failed" };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function sendKitty(pane, text, enter, run2, env, signal) {
|
|
285
|
+
await run2("kitten", kittyArgv(pane, ["send-text", "--match", `id:${pane.paneId}`, "--", enter ? "\\r" : text.replace(/\\/g, "\\\\")]), env, signal);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/wake/terminal/iterm2.ts
|
|
289
|
+
init_define_KHALA_BUNDLE();
|
|
290
|
+
import { fileURLToPath } from "node:url";
|
|
291
|
+
function iterm2ScriptPath() {
|
|
292
|
+
return fileURLToPath(new URL("./iterm2_send.py", import.meta.url));
|
|
293
|
+
}
|
|
294
|
+
async function inspectIterm2(pane, run2, env, signal) {
|
|
295
|
+
try {
|
|
296
|
+
const view = JSON.parse(await run2("python3", [iterm2ScriptPath(), pane.paneId], env, signal));
|
|
297
|
+
if (view.reason) return { reason: view.reason };
|
|
298
|
+
if (typeof view.tty !== "string" || !/^\/dev\/tty[^\s/]+$/.test(view.tty)) return { reason: "iterm2_tty_unavailable" };
|
|
299
|
+
if (typeof view.line !== "string" || !Number.isSafeInteger(view.cursorX) || view.cursorX < 0 || !Number.isSafeInteger(view.cursorY) || view.cursorY < 0) return { reason: "iterm2_cursor_unavailable" };
|
|
300
|
+
return { view };
|
|
301
|
+
} catch {
|
|
302
|
+
return { reason: "iterm2_python_api_unavailable" };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async function sendIterm2(pane, text, enter, run2, env, signal, expected) {
|
|
306
|
+
if (!expected) return "skipped";
|
|
307
|
+
const result = JSON.parse(await run2("python3", [iterm2ScriptPath(), pane.paneId, enter ? "\r" : text, JSON.stringify(expected)], env, signal));
|
|
308
|
+
if (result.status === "not_empty") return "skipped";
|
|
309
|
+
if (result.status !== "sent") throw new Error("iterm2_send_failed");
|
|
233
310
|
}
|
|
234
311
|
|
|
235
312
|
// src/wake/terminal/driver.ts
|
|
@@ -237,7 +314,7 @@ function promptPrefix(captured, cursorColumn) {
|
|
|
237
314
|
return captured.replace(/\x1b\[[0-9;]*m/g, "").replace(/\r?\n$/, "").slice(0, cursorColumn).padEnd(cursorColumn, " ");
|
|
238
315
|
}
|
|
239
316
|
function createTerminalWakeDriver(guard, deps = {}) {
|
|
240
|
-
const
|
|
317
|
+
const run2 = deps.run ?? runTerminalCommand;
|
|
241
318
|
const read = deps.readProcess ?? readProcess;
|
|
242
319
|
const platform = deps.platform ?? process.platform;
|
|
243
320
|
const probe = async (ctx, empty, cleanup = false) => {
|
|
@@ -247,15 +324,16 @@ function createTerminalWakeDriver(guard, deps = {}) {
|
|
|
247
324
|
if (!cleanup && !await driverAllowed(stateRoot(ctx.env), ctx.harness, "terminal", true)) return { reason: "terminal_consent_required" };
|
|
248
325
|
if (!cleanup && (await readWakeState(ctx.files.dir)).terminal?.disabled) return { reason: "nonce_timeout" };
|
|
249
326
|
const pane = await readPane(ctx.files);
|
|
250
|
-
if (!pane) return { reason: ctx.harness === "codex" && (ctx.env.TMUX || ctx.env.WEZTERM_PANE) ? "terminal_capture_pending_prompt" : "no remote-control API" };
|
|
327
|
+
if (!pane) return { reason: ctx.env.ITERM_SESSION_ID && !/^w\d+t\d+p\d+:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(ctx.env.ITERM_SESSION_ID) ? "iterm2_session_id_invalid" : ctx.harness === "codex" && (ctx.env.TMUX || ctx.env.WEZTERM_PANE || ctx.env.KITTY_WINDOW_ID || ctx.env.ITERM_SESSION_ID) ? "terminal_capture_pending_prompt" : ctx.env.ITERM_SESSION_ID ? "iterm2_session_id_invalid" : "no remote-control API" };
|
|
251
328
|
const activity = await readActivity(ctx.files);
|
|
252
329
|
if (!cleanup && (activity.state !== "idle" || ctx.now - Date.parse(activity.updatedAt) < 3e4)) return { reason: "terminal_not_idle" };
|
|
253
330
|
const agent = await read(pane.agentPid);
|
|
254
331
|
if (!agent || pane.agentStartTime !== void 0 && pane.agentStartTime !== agent.startTime) return { reason: "terminal_agent_exited" };
|
|
255
|
-
|
|
332
|
+
if (pane.kind === "iterm2" && platform !== "darwin") return { reason: "iterm2_requires_macos" };
|
|
333
|
+
const inspection = pane.kind === "tmux" ? { view: await inspectTmux(pane, run2, ctx.env, ctx.signal, read) ?? void 0 } : pane.kind === "kitty" ? await inspectKitty(pane, run2, ctx.env, ctx.signal, read) : pane.kind === "iterm2" ? await inspectIterm2(pane, run2, ctx.env, ctx.signal) : await inspectWezterm(pane, run2, ctx.env, ctx.signal);
|
|
256
334
|
const view = inspection.view;
|
|
257
335
|
if (!view) return { reason: inspection.reason ?? "terminal_pane_unsafe" };
|
|
258
|
-
if (!await (deps.ownsTerminal ?? ownsTerminal)(pane.agentPid, view.tty, ctx.env, ctx.signal,
|
|
336
|
+
if (!await (deps.ownsTerminal ?? ownsTerminal)(pane.agentPid, view.tty, ctx.env, ctx.signal, run2, platform))
|
|
259
337
|
return { reason: "terminal_pane_not_owned" };
|
|
260
338
|
if (empty && !isEmptyPrompt(view.line, view.cursorX, guard)) return { reason: "terminal_prompt_not_empty" };
|
|
261
339
|
return { pane, view };
|
|
@@ -284,8 +362,8 @@ function createTerminalWakeDriver(guard, deps = {}) {
|
|
|
284
362
|
if (!ready.pane || ctx.signal.aborted) return "skipped";
|
|
285
363
|
const beforeSend = await readActivity(ctx.files);
|
|
286
364
|
if (beforeSend.state !== "idle" || beforeSend.updatedAt !== activity.updatedAt) return "skipped";
|
|
287
|
-
const send = ready.pane.kind === "tmux" ? sendTmux : sendWezterm;
|
|
288
|
-
await send(ready.pane, line, false,
|
|
365
|
+
const send = ready.pane.kind === "tmux" ? sendTmux : ready.pane.kind === "kitty" ? sendKitty : ready.pane.kind === "iterm2" ? sendIterm2 : sendWezterm;
|
|
366
|
+
if (await send(ready.pane, line, false, run2, ctx.env, ctx.signal, ready.view) === "skipped") return "skipped";
|
|
289
367
|
const sameComposer = (final2) => {
|
|
290
368
|
if (!final2.pane || !final2.view || final2.pane.agentPid !== ready.pane.agentPid || final2.pane.agentStartTime !== ready.pane.agentStartTime || final2.pane.capturedAt !== ready.pane.capturedAt || final2.view.tty !== ready.view.tty || final2.pane.kind !== ready.pane.kind || final2.pane.paneId !== ready.pane.paneId || final2.pane.socket !== ready.pane.socket) return false;
|
|
291
369
|
const text = final2.view.line.replace(/\x1b\[[0-9;]*m/g, "").replace(/[ \r\n]+$/, "");
|
|
@@ -296,7 +374,7 @@ function createTerminalWakeDriver(guard, deps = {}) {
|
|
|
296
374
|
const current2 = await safeProbe(cleanupCtx, false, true);
|
|
297
375
|
if (!sameComposer(current2)) return;
|
|
298
376
|
if (current2.pane.kind === "tmux") {
|
|
299
|
-
await
|
|
377
|
+
await run2("tmux", tmuxArgv(current2.pane, [
|
|
300
378
|
"send-keys",
|
|
301
379
|
"-t",
|
|
302
380
|
current2.pane.paneId,
|
|
@@ -305,7 +383,7 @@ function createTerminalWakeDriver(guard, deps = {}) {
|
|
|
305
383
|
"BSpace"
|
|
306
384
|
]), ctx.env, cleanupCtx.signal);
|
|
307
385
|
} else {
|
|
308
|
-
await
|
|
386
|
+
await send(current2.pane, "\x7F".repeat(line.length), false, run2, ctx.env, cleanupCtx.signal, current2.view);
|
|
309
387
|
}
|
|
310
388
|
};
|
|
311
389
|
try {
|
|
@@ -328,7 +406,13 @@ function createTerminalWakeDriver(guard, deps = {}) {
|
|
|
328
406
|
}
|
|
329
407
|
return;
|
|
330
408
|
}
|
|
331
|
-
await send(final.pane, line, true,
|
|
409
|
+
if (await send(final.pane, line, true, run2, ctx.env, ctx.signal, final.view) === "skipped") {
|
|
410
|
+
try {
|
|
411
|
+
await cleanup();
|
|
412
|
+
} finally {
|
|
413
|
+
await failAttempt(ctx.files.dir, nonce, ctx.now + 500);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
332
416
|
}
|
|
333
417
|
};
|
|
334
418
|
}
|
|
@@ -354,7 +438,7 @@ var emptyPrompt = { pattern: /^❯[\u00a0 ]?(Try ".*")?$/u, cursorColumn: 2 };
|
|
|
354
438
|
var claude = {
|
|
355
439
|
id: "claude",
|
|
356
440
|
sessionSources: [{ kind: "env", resolve: (_meta, env) => env.CLAUDE_CODE_SESSION_ID, rejoinable: () => true }],
|
|
357
|
-
codec:
|
|
441
|
+
codec: claudeCodec,
|
|
358
442
|
restoreAtStartup: true,
|
|
359
443
|
watcherStatus: true,
|
|
360
444
|
emptyPrompt,
|
|
@@ -444,9 +528,10 @@ function createCodexQueueProcessPort(deps) {
|
|
|
444
528
|
// src/wake/codex-probe.ts
|
|
445
529
|
init_define_KHALA_BUNDLE();
|
|
446
530
|
import { execFile as execFile2 } from "node:child_process";
|
|
447
|
-
import { access, realpath, stat as stat2 } from "node:fs/promises";
|
|
531
|
+
import { access, mkdtemp, realpath, rm, stat as stat2 } from "node:fs/promises";
|
|
448
532
|
import { constants } from "node:fs";
|
|
449
533
|
import path2 from "node:path";
|
|
534
|
+
import os from "node:os";
|
|
450
535
|
import { promisify as promisify2 } from "node:util";
|
|
451
536
|
var execute = promisify2(execFile2);
|
|
452
537
|
var probes = /* @__PURE__ */ new Map();
|
|
@@ -473,9 +558,18 @@ async function probeCodexQueue(env) {
|
|
|
473
558
|
if (cached?.mtime === mtime) return cached.result;
|
|
474
559
|
const binary = command;
|
|
475
560
|
const result = (async () => {
|
|
561
|
+
let probeHome;
|
|
476
562
|
try {
|
|
563
|
+
probeHome = await mkdtemp(path2.join(os.tmpdir(), "khala-codex-probe-"));
|
|
477
564
|
const { stdout } = await execute(binary, ["queue", "--help"], {
|
|
478
|
-
env:
|
|
565
|
+
env: {
|
|
566
|
+
...scrubbedQueueEnv(env),
|
|
567
|
+
HOME: probeHome,
|
|
568
|
+
CODEX_HOME: probeHome,
|
|
569
|
+
XDG_CONFIG_HOME: probeHome,
|
|
570
|
+
XDG_DATA_HOME: probeHome,
|
|
571
|
+
XDG_STATE_HOME: probeHome
|
|
572
|
+
},
|
|
479
573
|
timeout: 5e3,
|
|
480
574
|
killSignal: "SIGKILL",
|
|
481
575
|
maxBuffer: 64 * 1024,
|
|
@@ -484,6 +578,8 @@ async function probeCodexQueue(env) {
|
|
|
484
578
|
return /--thread\b/.test(stdout) && /--message\b/.test(stdout) ? { available: true, command: binary } : { available: false, reason: "codex_queue_unavailable" };
|
|
485
579
|
} catch {
|
|
486
580
|
return { available: false, reason: "codex_queue_unavailable" };
|
|
581
|
+
} finally {
|
|
582
|
+
if (probeHome) await rm(probeHome, { recursive: true, force: true });
|
|
487
583
|
}
|
|
488
584
|
})();
|
|
489
585
|
probes.set(binary, { mtime, result });
|
|
@@ -515,14 +611,8 @@ function createWakeLadder(deps) {
|
|
|
515
611
|
};
|
|
516
612
|
const evaluate = async () => {
|
|
517
613
|
try {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
harness: deps.harness,
|
|
521
|
-
sessionId: deps.sessionId,
|
|
522
|
-
env: deps.env ?? process.env,
|
|
523
|
-
signal: controller.signal,
|
|
524
|
-
now: now()
|
|
525
|
-
});
|
|
614
|
+
const verificationCtx = { files: deps.files, harness: deps.harness, sessionId: deps.sessionId, env: deps.env ?? process.env, signal: controller.signal, now: now() };
|
|
615
|
+
for (const driver2 of deps.drivers) await driver2.verify?.(verificationCtx);
|
|
526
616
|
if (deps.drivers.some((driver2) => driver2.verification !== "none")) await settleAttempts(deps.files.dir, { now: now(), activity: await readActivity(deps.files) });
|
|
527
617
|
const driverStates = await readWakeState(deps.files.dir);
|
|
528
618
|
for (const [id, state] of Object.entries(driverStates)) {
|
|
@@ -539,10 +629,12 @@ function createWakeLadder(deps) {
|
|
|
539
629
|
const session = await readJson(channel.files.session);
|
|
540
630
|
const metadata = channel.legacy ? null : await readJson(path3.join(channel.files.dir, "channel.json"));
|
|
541
631
|
currentIdentities.set(channel.key, JSON.stringify([session?.roomId ?? channel.roomId, session?.userId, metadata?.joinedAt]));
|
|
542
|
-
|
|
632
|
+
const mode = await readListeningMode(channel.files);
|
|
633
|
+
if (mode === "async") continue;
|
|
634
|
+
const you = (await readJson(channel.files.status))?.displayName;
|
|
543
635
|
eligible.push({
|
|
544
636
|
channel,
|
|
545
|
-
counts: { messages: (await unread(channel.files)).entries.filter((entry) => entry.
|
|
637
|
+
counts: { messages: (await unread(channel.files)).entries.filter((entry) => entry.sender !== session?.userId && wakesAgent(entry, mode, you)).length },
|
|
546
638
|
cursor: await readCursor(channel.files)
|
|
547
639
|
});
|
|
548
640
|
}
|
|
@@ -608,9 +700,9 @@ function createWakeLadder(deps) {
|
|
|
608
700
|
const attemptAt = now();
|
|
609
701
|
const nonce = newNonce();
|
|
610
702
|
if (driver.verification !== "none") {
|
|
611
|
-
await recordAttempt(deps.files.dir, { nonce, driver: driver.id, at: attemptAt, deadline: attemptAt + driver.deadlineMs, activityUpdatedAt: activity.updatedAt, ...driver.startsActivity ? { startsActivity: true } : {} });
|
|
703
|
+
await recordAttempt(deps.files.dir, { nonce, driver: driver.id, at: attemptAt, deadline: attemptAt + driver.deadlineMs, activityUpdatedAt: activity.updatedAt, ...driver.startsActivity ? { startsActivity: true } : {}, ...driver.verification === "transcript" ? { verification: "transcript" } : {} });
|
|
612
704
|
if (!await stillEligible()) {
|
|
613
|
-
await
|
|
705
|
+
await cancelAttempt(deps.files.dir, nonce);
|
|
614
706
|
return;
|
|
615
707
|
}
|
|
616
708
|
}
|
|
@@ -707,7 +799,7 @@ var codexWakeDriver = createCodexWakeDriver();
|
|
|
707
799
|
|
|
708
800
|
// src/harness/codex.ts
|
|
709
801
|
var emptyPrompt2 = { pattern: /^› ?(Ask Codex to do anything)?$/u, cursorColumn: 2 };
|
|
710
|
-
var install = async (flags, deps) => (await import("./chunk-
|
|
802
|
+
var install = async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runCodexInstall(flags, deps);
|
|
711
803
|
var codex = {
|
|
712
804
|
id: "codex",
|
|
713
805
|
// A string metadata id wins even when empty or invalid; validation happens after selection.
|
|
@@ -739,11 +831,11 @@ async function recordHookSession(harness, sessionId, env, options = {}) {
|
|
|
739
831
|
if (!parent) return;
|
|
740
832
|
const dir = mappingDir(harness, env);
|
|
741
833
|
await ensureStateDir(dir);
|
|
742
|
-
const
|
|
834
|
+
const file3 = path4.join(dir, `${parent.pid}.json`);
|
|
743
835
|
const at = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
744
|
-
const previous = await readJson(
|
|
836
|
+
const previous = await readJson(file3);
|
|
745
837
|
if (previous?.startTime === parent.startTime && typeof previous.at === "string" && previous.at > at) return;
|
|
746
|
-
await writeJsonAtomic(
|
|
838
|
+
await writeJsonAtomic(file3, {
|
|
747
839
|
sessionId,
|
|
748
840
|
startTime: parent.startTime,
|
|
749
841
|
at,
|
|
@@ -789,7 +881,7 @@ async function resolveSources(sources, meta, env, context) {
|
|
|
789
881
|
}
|
|
790
882
|
|
|
791
883
|
// src/harness/muse.ts
|
|
792
|
-
var install2 = async (flags, deps) => (await import("./chunk-
|
|
884
|
+
var install2 = async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runMuseInstall(flags, deps);
|
|
793
885
|
var muse = {
|
|
794
886
|
id: "muse",
|
|
795
887
|
sessionSources: [{ kind: "env", resolve: (_meta, env) => env.MUSE_SESSION_ID, rejoinable: () => true }, hookMapSource],
|
|
@@ -844,7 +936,7 @@ function createCopilotCodec(event) {
|
|
|
844
936
|
},
|
|
845
937
|
noop: () => "{}\n",
|
|
846
938
|
render(kind, frame) {
|
|
847
|
-
return JSON.stringify(kind === "stop" ? { decision: "block", reason:
|
|
939
|
+
return JSON.stringify(kind === "stop" ? { decision: "block", reason: STOP_NUDGE } : kind === "tool" ? { additionalContext: frame } : {}) + "\n";
|
|
848
940
|
}
|
|
849
941
|
};
|
|
850
942
|
}
|
|
@@ -852,10 +944,10 @@ var copilotCodec = createCopilotCodec();
|
|
|
852
944
|
|
|
853
945
|
// src/harness/copilot.ts
|
|
854
946
|
var emptyPrompt3 = { pattern: /^❯ ?$/u, cursorColumn: 2 };
|
|
855
|
-
var install3 = async (flags, deps) => (await import("./chunk-
|
|
947
|
+
var install3 = async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runCopilotInstall(flags, deps);
|
|
856
948
|
var copilot = {
|
|
857
949
|
id: "copilot",
|
|
858
|
-
sessionSources: [hookMapSource],
|
|
950
|
+
sessionSources: [{ kind: "env", resolve: (_meta, env) => env.COPILOT_AGENT_SESSION_ID, rejoinable: () => true }, hookMapSource],
|
|
859
951
|
codec: copilotCodec,
|
|
860
952
|
restoreAtStartup: true,
|
|
861
953
|
install: install3,
|
|
@@ -895,12 +987,12 @@ var cursorCodec = {
|
|
|
895
987
|
noop: (kind) => JSON.stringify(kind === "prompt" ? { continue: true } : {}) + "\n",
|
|
896
988
|
render(kind, frame) {
|
|
897
989
|
if (kind === "prompt") return cursorCodec.noop(kind);
|
|
898
|
-
return JSON.stringify(kind === "stop" ? { followup_message:
|
|
990
|
+
return JSON.stringify(kind === "stop" ? { followup_message: STOP_NUDGE } : { additional_context: frame }) + "\n";
|
|
899
991
|
}
|
|
900
992
|
};
|
|
901
993
|
|
|
902
994
|
// src/harness/cursor.ts
|
|
903
|
-
var install4 = async (flags, deps) => (await import("./chunk-
|
|
995
|
+
var install4 = async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runCursorInstall(flags, deps);
|
|
904
996
|
var cursor = {
|
|
905
997
|
id: "cursor",
|
|
906
998
|
sessionSources: [{
|
|
@@ -914,6 +1006,56 @@ var cursor = {
|
|
|
914
1006
|
uninstall: (flags, deps) => install4([...flags, "--uninstall"], deps)
|
|
915
1007
|
};
|
|
916
1008
|
|
|
1009
|
+
// src/harness/qwen.ts
|
|
1010
|
+
init_define_KHALA_BUNDLE();
|
|
1011
|
+
import path5 from "node:path";
|
|
1012
|
+
var qwenCodec = {
|
|
1013
|
+
...claudeStyleCodec,
|
|
1014
|
+
parse(stdin) {
|
|
1015
|
+
try {
|
|
1016
|
+
if (JSON.parse(stdin)?.agent_id !== void 0) return null;
|
|
1017
|
+
} catch {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
return claudeStyleCodec.parse(stdin);
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
function createQwenBackgroundDriver(platform = process.platform) {
|
|
1024
|
+
return {
|
|
1025
|
+
id: "background-shell",
|
|
1026
|
+
rung: 2,
|
|
1027
|
+
optIn: false,
|
|
1028
|
+
minIdleMs: 0,
|
|
1029
|
+
deadlineMs: 3e4,
|
|
1030
|
+
verification: "none",
|
|
1031
|
+
async available(ctx) {
|
|
1032
|
+
const settings = await readWakeSettings(stateRoot(ctx.env));
|
|
1033
|
+
return platform === "win32" && !settings.off["qwen/socket"] && !settings.off["qwen/background-shell"] && await qwenInboundAllowed(ctx.env) && await (await import("./chunk-CHC4VZF5.mjs")).monitorArmed(ctx.files);
|
|
1034
|
+
},
|
|
1035
|
+
async unavailableReason(ctx) {
|
|
1036
|
+
if (!await qwenInboundAllowed(ctx.env)) return "qwen_held";
|
|
1037
|
+
return platform === "win32" ? "qwen_watcher_missing" : "driver_missing";
|
|
1038
|
+
},
|
|
1039
|
+
// Qwen itself owns the background tool. Khala never launches or injects input.
|
|
1040
|
+
wake: () => "skipped"
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
var qwen = {
|
|
1044
|
+
id: "qwen",
|
|
1045
|
+
codec: qwenCodec,
|
|
1046
|
+
restoreAtStartup: true,
|
|
1047
|
+
sessionSources: [{ kind: "meta", resolve: (meta) => meta?.khala_session, rejoinable: () => true }, { kind: "env", resolve: (_meta, env) => qwenSession(env), rejoinable: () => true }, hookMapSource],
|
|
1048
|
+
install: async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runQwenInstall(flags, deps),
|
|
1049
|
+
uninstall: async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runQwenInstall([...flags, "--uninstall"], deps),
|
|
1050
|
+
wakeLadder: [createQwenSocketDriver(), createQwenBackgroundDriver()],
|
|
1051
|
+
async verifyWake(stdin, files, now) {
|
|
1052
|
+
const input = JSON.parse(stdin);
|
|
1053
|
+
if (typeof input.transcript_path !== "string") return;
|
|
1054
|
+
await writeJsonAtomic(path5.join(files.dir, "qwen-transcript.json"), { path: input.transcript_path });
|
|
1055
|
+
if (input.hook_event_name === "Stop") await verifyQwenTranscript(files.dir, input.transcript_path, now);
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
|
|
917
1059
|
// src/harness/gemini.ts
|
|
918
1060
|
init_define_KHALA_BUNDLE();
|
|
919
1061
|
|
|
@@ -942,13 +1084,13 @@ var geminiCodec = {
|
|
|
942
1084
|
},
|
|
943
1085
|
noop: (kind) => JSON.stringify(kind === "stop" ? { decision: "allow" } : {}) + "\n",
|
|
944
1086
|
render(kind, frame) {
|
|
945
|
-
return JSON.stringify(kind === "stop" ? { decision: "deny", reason:
|
|
1087
|
+
return JSON.stringify(kind === "stop" ? { decision: "deny", reason: STOP_NUDGE } : { hookSpecificOutput: { hookEventName: names2[kind], additionalContext: frame } }) + "\n";
|
|
946
1088
|
}
|
|
947
1089
|
};
|
|
948
1090
|
|
|
949
1091
|
// src/harness/gemini.ts
|
|
950
1092
|
var emptyPrompt4 = { pattern: /^ >(?: {3}Type your message or @path\/to\/file)?$/u, cursorColumn: 3 };
|
|
951
|
-
var install5 = async (flags, deps) => (await import("./chunk-
|
|
1093
|
+
var install5 = async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runGeminiInstall(flags, deps);
|
|
952
1094
|
var gemini = {
|
|
953
1095
|
id: "gemini",
|
|
954
1096
|
sessionSources: [{ kind: "env", resolve: (_meta, env) => env.GEMINI_SESSION_ID, rejoinable: () => true }, hookMapSource],
|
|
@@ -961,6 +1103,207 @@ var gemini = {
|
|
|
961
1103
|
wakeWarningName: "gemini"
|
|
962
1104
|
};
|
|
963
1105
|
|
|
1106
|
+
// src/harness/antigravity.ts
|
|
1107
|
+
init_define_KHALA_BUNDLE();
|
|
1108
|
+
|
|
1109
|
+
// src/harness/codecs/antigravity.ts
|
|
1110
|
+
init_define_KHALA_BUNDLE();
|
|
1111
|
+
var antigravityCodec = {
|
|
1112
|
+
promptDeliversWithoutWake: false,
|
|
1113
|
+
promptAcceptsContext: false,
|
|
1114
|
+
suppressOutputErrors: false,
|
|
1115
|
+
parse(stdin) {
|
|
1116
|
+
try {
|
|
1117
|
+
const input = JSON.parse(stdin.replace(/^\uFEFF/u, ""));
|
|
1118
|
+
if (!input || typeof input.conversationId !== "string") return null;
|
|
1119
|
+
let event;
|
|
1120
|
+
if (input.khalaHookEvent === "PreInvocation" && Number.isInteger(input.invocationNum) && input.invocationNum >= 0)
|
|
1121
|
+
event = input.invocationNum === 0 ? "prompt" : "tool";
|
|
1122
|
+
else if (input.khalaHookEvent === "Stop") event = "stop";
|
|
1123
|
+
else return null;
|
|
1124
|
+
return {
|
|
1125
|
+
sessionId: input.conversationId,
|
|
1126
|
+
event,
|
|
1127
|
+
continuation: false,
|
|
1128
|
+
...typeof input.transcriptPath === "string" ? { transcriptPath: input.transcriptPath } : {},
|
|
1129
|
+
...typeof input.workspacePaths?.[0] === "string" ? { workspace: input.workspacePaths[0] } : {}
|
|
1130
|
+
};
|
|
1131
|
+
} catch {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
noop: (kind) => JSON.stringify(kind === "stop" ? { decision: "stop" } : {}) + "\n",
|
|
1136
|
+
render: (kind, frame) => JSON.stringify(kind === "stop" ? { decision: "continue", reason: STOP_NUDGE } : { injectSteps: [{ ephemeralMessage: frame }] }) + "\n"
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
// src/wake/antigravity.ts
|
|
1140
|
+
init_define_KHALA_BUNDLE();
|
|
1141
|
+
import * as fs from "node:fs/promises";
|
|
1142
|
+
import { constants as constants2 } from "node:fs";
|
|
1143
|
+
import path6 from "node:path";
|
|
1144
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
1145
|
+
var file = "antigravity-wake.json";
|
|
1146
|
+
var localAddress = (value) => typeof value === "string" && /^(?:localhost|127\.0\.0\.1|\[::1\]):([0-9]{1,5})$/u.test(value) && Number(value.slice(value.lastIndexOf(":") + 1)) > 0 && Number(value.slice(value.lastIndexOf(":") + 1)) <= 65535;
|
|
1147
|
+
async function credentials(files, sessionId) {
|
|
1148
|
+
let value;
|
|
1149
|
+
try {
|
|
1150
|
+
await ensureStateDir(files.dir);
|
|
1151
|
+
const handle = await fs.open(path6.join(files.dir, file), constants2.O_RDONLY | constants2.O_NONBLOCK | constants2.O_NOFOLLOW);
|
|
1152
|
+
try {
|
|
1153
|
+
const stat4 = await handle.stat();
|
|
1154
|
+
if (!stat4.isFile() || stat4.size > 16 * 1024 || process.platform !== "win32" && (stat4.mode & 63) !== 0 || typeof process.getuid === "function" && stat4.uid !== process.getuid()) return null;
|
|
1155
|
+
value = JSON.parse(await handle.readFile("utf8"));
|
|
1156
|
+
} finally {
|
|
1157
|
+
await handle.close();
|
|
1158
|
+
}
|
|
1159
|
+
} catch {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
return value && value.sessionId === sessionId && localAddress(value.address) && typeof value.token === "string" && value.token.length > 0 && !/[\r\n\0]/u.test(value.token) && typeof value.command === "string" && (path6.isAbsolute(value.command) || value.command === "agy") ? value : null;
|
|
1163
|
+
}
|
|
1164
|
+
async function stableAgyCommand(env) {
|
|
1165
|
+
for (const dir of (env.PATH ?? "").split(path6.delimiter).filter(Boolean)) {
|
|
1166
|
+
for (const name of process.platform === "win32" ? ["agy.exe", "agy.cmd", "agy"] : ["agy"]) {
|
|
1167
|
+
const candidate = path6.resolve(dir, name);
|
|
1168
|
+
try {
|
|
1169
|
+
await fs.access(candidate, constants2.X_OK);
|
|
1170
|
+
if ((await fs.stat(candidate)).isFile()) return candidate;
|
|
1171
|
+
} catch {
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return env.ANTIGRAVITY_AGENTAPI_EXE || "agy";
|
|
1176
|
+
}
|
|
1177
|
+
async function registerAntigravityWake(env) {
|
|
1178
|
+
const sessionId = env.ANTIGRAVITY_CONVERSATION_ID;
|
|
1179
|
+
const address = env.ANTIGRAVITY_LS_ADDRESS;
|
|
1180
|
+
const token = env.ANTIGRAVITY_CSRF_TOKEN;
|
|
1181
|
+
const command = await stableAgyCommand(env);
|
|
1182
|
+
if (!sessionId || !SESSION_ID_PATTERN.test(sessionId) || !localAddress(address) || !token || /[\r\n\0]/u.test(token) || !(path6.isAbsolute(command) || command === "agy")) throw new Error("antigravity_registration_unavailable");
|
|
1183
|
+
const files = await openSessionDir("antigravity", sessionId, env);
|
|
1184
|
+
await withWakeLock(files.dir, "antigravity-credentials.lock", async () => {
|
|
1185
|
+
await writeJsonAtomic(path6.join(files.dir, file), { sessionId, address, token, command });
|
|
1186
|
+
await resetWakeDriver(files.dir, "antigravity-native");
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
var run = (command, argv, env, signal) => new Promise((resolve) => {
|
|
1190
|
+
execFile3(command, [...argv], { env, signal, timeout: 1e4, maxBuffer: 64 * 1024, windowsHide: true }, (error) => resolve(!error));
|
|
1191
|
+
});
|
|
1192
|
+
function createAntigravityWakeDriver(execute2 = run) {
|
|
1193
|
+
return {
|
|
1194
|
+
id: "antigravity-native",
|
|
1195
|
+
rung: 1,
|
|
1196
|
+
optIn: true,
|
|
1197
|
+
minIdleMs: 0,
|
|
1198
|
+
deadlineMs: 3e4,
|
|
1199
|
+
verification: "nonce",
|
|
1200
|
+
async available(ctx) {
|
|
1201
|
+
const value = await credentials(ctx.files, ctx.sessionId);
|
|
1202
|
+
return !!value && !value.rejected;
|
|
1203
|
+
},
|
|
1204
|
+
async unavailableReason(ctx) {
|
|
1205
|
+
const value = await credentials(ctx.files, ctx.sessionId);
|
|
1206
|
+
return value?.rejected ? "antigravity_credentials_rejected" : "antigravity_credentials_missing";
|
|
1207
|
+
},
|
|
1208
|
+
async wake(ctx, line) {
|
|
1209
|
+
if (!line.startsWith(CODEX_IDLE_WAKE_NOTICE + " ") || !/^\(k-[0-9a-f]{8}\)$/u.test(line.slice(CODEX_IDLE_WAKE_NOTICE.length + 1))) throw new Error("invalid_wake_line");
|
|
1210
|
+
const value = await credentials(ctx.files, ctx.sessionId);
|
|
1211
|
+
if (!value || value.rejected || ctx.signal.aborted || (await readActivity(ctx.files)).state !== "idle") return "skipped";
|
|
1212
|
+
let accepted = false;
|
|
1213
|
+
try {
|
|
1214
|
+
accepted = await execute2(
|
|
1215
|
+
value.command,
|
|
1216
|
+
["agentapi", "send-message", ctx.sessionId, line],
|
|
1217
|
+
{ ...ctx.env, ANTIGRAVITY_LS_ADDRESS: value.address, ANTIGRAVITY_CSRF_TOKEN: value.token },
|
|
1218
|
+
ctx.signal
|
|
1219
|
+
);
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
1222
|
+
if (!accepted && !ctx.signal.aborted) {
|
|
1223
|
+
await withWakeLock(ctx.files.dir, "antigravity-credentials.lock", async () => {
|
|
1224
|
+
const current = await credentials(ctx.files, ctx.sessionId);
|
|
1225
|
+
if (current?.token === value.token && current.address === value.address) await writeJsonAtomic(path6.join(ctx.files.dir, file), { ...value, rejected: true });
|
|
1226
|
+
});
|
|
1227
|
+
return "skipped";
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
async function antigravityPromptText(input) {
|
|
1233
|
+
if (!input.transcriptPath) return void 0;
|
|
1234
|
+
let handle;
|
|
1235
|
+
try {
|
|
1236
|
+
handle = await fs.open(input.transcriptPath, constants2.O_RDONLY | constants2.O_NONBLOCK);
|
|
1237
|
+
const stat4 = await handle.stat();
|
|
1238
|
+
if (!stat4.isFile()) return void 0;
|
|
1239
|
+
const length = Math.min(stat4.size, 64 * 1024), start = stat4.size - length;
|
|
1240
|
+
const buffer = Buffer.alloc(length);
|
|
1241
|
+
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
1242
|
+
let tail = buffer.subarray(0, bytesRead).toString("utf8");
|
|
1243
|
+
if (start > 0) tail = tail.slice(tail.indexOf("\n") + 1);
|
|
1244
|
+
return tail.split("\n").flatMap((line) => {
|
|
1245
|
+
try {
|
|
1246
|
+
const step = JSON.parse(line);
|
|
1247
|
+
if (typeof step.content !== "string") return [];
|
|
1248
|
+
if (step.source === "USER_EXPLICIT" && step.type === "USER_INPUT") {
|
|
1249
|
+
const nonce = step.content.match(/\(k-([0-9a-f]{8})\)$/u)?.[1];
|
|
1250
|
+
return nonce && wakeLine(nonce) === step.content ? [step.content] : [];
|
|
1251
|
+
}
|
|
1252
|
+
if (step.source !== "SYSTEM" || step.type !== "SYSTEM_MESSAGE") return [];
|
|
1253
|
+
return step.content.split(CODEX_IDLE_WAKE_NOTICE + " ").slice(1).flatMap((suffix) => {
|
|
1254
|
+
const nonce = suffix.match(/^\(k-[0-9a-f]{8}\)/u)?.[0];
|
|
1255
|
+
return nonce ? [CODEX_IDLE_WAKE_NOTICE + " " + nonce] : [];
|
|
1256
|
+
});
|
|
1257
|
+
} catch {
|
|
1258
|
+
return [];
|
|
1259
|
+
}
|
|
1260
|
+
}).join("\n") || void 0;
|
|
1261
|
+
} catch {
|
|
1262
|
+
return void 0;
|
|
1263
|
+
} finally {
|
|
1264
|
+
await handle?.close().catch(() => {
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/harness/antigravity.ts
|
|
1270
|
+
var emptyPrompt5 = { pattern: /^>$/u, cursorColumn: 2 };
|
|
1271
|
+
var install6 = async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runAntigravityInstall(flags, deps);
|
|
1272
|
+
var antigravity = {
|
|
1273
|
+
id: "antigravity",
|
|
1274
|
+
restoreAtStartup: true,
|
|
1275
|
+
codec: antigravityCodec,
|
|
1276
|
+
sessionSources: [
|
|
1277
|
+
{ kind: "meta", resolve: (meta) => meta?.["antigravity.google/conversation_id"], rejoinable: () => true },
|
|
1278
|
+
{ kind: "env", resolve: (_meta, env) => env.ANTIGRAVITY_CONVERSATION_ID, rejoinable: () => true },
|
|
1279
|
+
{
|
|
1280
|
+
kind: "process",
|
|
1281
|
+
rejoinable: () => true,
|
|
1282
|
+
async resolve(_meta, _env, context) {
|
|
1283
|
+
const read = context.readProcess ?? readProcess;
|
|
1284
|
+
for await (const parent of walkAncestors(context.pid ?? process.pid, read)) {
|
|
1285
|
+
if (!/^agy(?:\.exe)?$/iu.test(parent.command.split(/[\\/]/u).pop() ?? "")) continue;
|
|
1286
|
+
const args = await (context.readArguments ?? readProcessArguments)(parent.pid);
|
|
1287
|
+
if (!args || (await read(parent.pid))?.startTime !== parent.startTime) return null;
|
|
1288
|
+
for (let i = 1; i < args.length; i++) {
|
|
1289
|
+
if (args[i] === "--") break;
|
|
1290
|
+
if (args[i] === "--conversation") return args[i + 1] ?? "";
|
|
1291
|
+
if (args[i]?.startsWith("--conversation=")) return args[i].slice("--conversation=".length);
|
|
1292
|
+
}
|
|
1293
|
+
return null;
|
|
1294
|
+
}
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
],
|
|
1299
|
+
install: install6,
|
|
1300
|
+
uninstall: (flags, deps) => install6([...flags, "--uninstall"], deps),
|
|
1301
|
+
emptyPrompt: emptyPrompt5,
|
|
1302
|
+
wakeLadder: [createAntigravityWakeDriver(), createTerminalWakeDriver(emptyPrompt5)],
|
|
1303
|
+
wakeWarningName: "antigravity",
|
|
1304
|
+
hookPromptText: antigravityPromptText
|
|
1305
|
+
};
|
|
1306
|
+
|
|
964
1307
|
// src/harness/opencode.ts
|
|
965
1308
|
init_define_KHALA_BUNDLE();
|
|
966
1309
|
|
|
@@ -987,13 +1330,13 @@ var opencodeCodec = {
|
|
|
987
1330
|
}
|
|
988
1331
|
},
|
|
989
1332
|
noop: () => "",
|
|
990
|
-
render: (
|
|
1333
|
+
render: (kind, frame) => kind === "stop" ? STOP_NUDGE : frame
|
|
991
1334
|
};
|
|
992
1335
|
|
|
993
1336
|
// src/wake/opencode.ts
|
|
994
1337
|
init_define_KHALA_BUNDLE();
|
|
995
|
-
import
|
|
996
|
-
var
|
|
1338
|
+
import path7 from "node:path";
|
|
1339
|
+
var file2 = "opencode-wake.json";
|
|
997
1340
|
async function budget(files) {
|
|
998
1341
|
const pairs = await Promise.all((await listChannels(files)).map(async (channel) => [channel.key, (await readCursor(channel.files)).deliveredCount]));
|
|
999
1342
|
return JSON.stringify(pairs.sort(([a], [b]) => a.localeCompare(b)));
|
|
@@ -1007,20 +1350,20 @@ var opencodeWakeDriver = {
|
|
|
1007
1350
|
deadlineMs: 3e3,
|
|
1008
1351
|
verification: "nonce",
|
|
1009
1352
|
async available(ctx) {
|
|
1010
|
-
const prior = await readJson(
|
|
1353
|
+
const prior = await readJson(path7.join(ctx.files.dir, file2));
|
|
1011
1354
|
return canAttempt(prior, await budget(ctx.files), ctx.now);
|
|
1012
1355
|
},
|
|
1013
1356
|
async wake(ctx, line) {
|
|
1014
1357
|
return withWakeLock(ctx.files.dir, "opencode-native.lock", async () => {
|
|
1015
1358
|
if (ctx.signal.aborted || (await readActivity(ctx.files)).state !== "idle") return "skipped";
|
|
1016
1359
|
const key = await budget(ctx.files);
|
|
1017
|
-
const prior = await readJson(
|
|
1360
|
+
const prior = await readJson(path7.join(ctx.files.dir, file2));
|
|
1018
1361
|
if (!canAttempt(prior, key, ctx.now)) return "skipped";
|
|
1019
1362
|
const channels = [];
|
|
1020
1363
|
for (const channel of await listChannels(ctx.files)) {
|
|
1021
1364
|
if (await readListeningMode(channel.files) !== "async") channels.push(channel.key);
|
|
1022
1365
|
}
|
|
1023
|
-
await writeJsonAtomic(
|
|
1366
|
+
await writeJsonAtomic(path7.join(ctx.files.dir, file2), {
|
|
1024
1367
|
line,
|
|
1025
1368
|
at: ctx.now,
|
|
1026
1369
|
budget: key,
|
|
@@ -1033,7 +1376,7 @@ var opencodeWakeDriver = {
|
|
|
1033
1376
|
};
|
|
1034
1377
|
async function pollOpenCodeWake(files, io, replay = false) {
|
|
1035
1378
|
if (replay) return withWakeLock(files.dir, "opencode-native.lock", async () => {
|
|
1036
|
-
const pending = await readJson(
|
|
1379
|
+
const pending = await readJson(path7.join(files.dir, file2));
|
|
1037
1380
|
const activity = await readActivity(files);
|
|
1038
1381
|
await settleAttempts(files.dir, { now: io.now().getTime(), activity });
|
|
1039
1382
|
if (!pending || activity.state !== "idle" || !await driverAllowed(stateRoot(io.env), "opencode", opencodeWakeDriver.id, false) || (await readWakeState(files.dir))[opencodeWakeDriver.id]?.disabled) return;
|
|
@@ -1049,13 +1392,13 @@ async function pollOpenCodeWake(files, io, replay = false) {
|
|
|
1049
1392
|
activityUpdatedAt: activity.updatedAt
|
|
1050
1393
|
});
|
|
1051
1394
|
const line = wakeLine(nonce);
|
|
1052
|
-
await writeJsonAtomic(
|
|
1395
|
+
await writeJsonAtomic(path7.join(files.dir, file2), { ...pending, line, at });
|
|
1053
1396
|
return line;
|
|
1054
1397
|
});
|
|
1055
1398
|
const ladder = createWakeLadder({
|
|
1056
1399
|
files,
|
|
1057
1400
|
harness: "opencode",
|
|
1058
|
-
sessionId:
|
|
1401
|
+
sessionId: path7.basename(files.dir),
|
|
1059
1402
|
drivers: [opencodeWakeDriver],
|
|
1060
1403
|
env: io.env,
|
|
1061
1404
|
now: () => io.now().getTime(),
|
|
@@ -1067,31 +1410,33 @@ async function pollOpenCodeWake(files, io, replay = false) {
|
|
|
1067
1410
|
await ladder.stop();
|
|
1068
1411
|
}
|
|
1069
1412
|
return withWakeLock(files.dir, "opencode-native.lock", async () => {
|
|
1070
|
-
const pending = await readJson(
|
|
1413
|
+
const pending = await readJson(path7.join(files.dir, file2));
|
|
1071
1414
|
if (!pending || pending.delivered || io.now().getTime() > pending.at + opencodeWakeDriver.deadlineMs || pending.budget !== await budget(files) || (await readActivity(files)).state !== "idle" || !await driverAllowed(stateRoot(io.env), "opencode", opencodeWakeDriver.id, false) || (await readWakeState(files.dir))[opencodeWakeDriver.id]?.disabled) return;
|
|
1072
1415
|
let eligible = false;
|
|
1073
1416
|
for (const channel of await listChannels(files)) {
|
|
1074
|
-
|
|
1417
|
+
const mode = await readListeningMode(channel.files);
|
|
1418
|
+
if (mode === "async") continue;
|
|
1075
1419
|
const session = await readJson(channel.files.session);
|
|
1076
|
-
|
|
1420
|
+
const you = (await readJson(channel.files.status))?.displayName;
|
|
1421
|
+
if ((await unread(channel.files)).entries.some((entry) => entry.sender !== session?.userId && wakesAgent(entry, mode, you))) eligible = true;
|
|
1077
1422
|
}
|
|
1078
1423
|
if (!eligible) return;
|
|
1079
|
-
await writeJsonAtomic(
|
|
1424
|
+
await writeJsonAtomic(path7.join(files.dir, file2), { ...pending, delivered: true });
|
|
1080
1425
|
return pending.line;
|
|
1081
1426
|
});
|
|
1082
1427
|
}
|
|
1083
1428
|
|
|
1084
1429
|
// src/harness/opencode.ts
|
|
1085
|
-
var
|
|
1430
|
+
var install7 = async (flags, deps) => (await import("./chunk-CKLCR5G4.mjs")).runOpenCodeInstall(flags, deps);
|
|
1086
1431
|
var opencode = {
|
|
1087
1432
|
id: "opencode",
|
|
1088
1433
|
sessionSources: [{ kind: "meta", resolve: (meta) => meta?.khala_session, rejoinable: () => true }, hookMapSource],
|
|
1089
1434
|
codec: opencodeCodec,
|
|
1090
|
-
restoreAtStartup:
|
|
1435
|
+
restoreAtStartup: true,
|
|
1091
1436
|
wakeLadder: [opencodeWakeDriver],
|
|
1092
1437
|
pollIdleWake: pollOpenCodeWake,
|
|
1093
|
-
install:
|
|
1094
|
-
uninstall: (flags, deps) =>
|
|
1438
|
+
install: install7,
|
|
1439
|
+
uninstall: (flags, deps) => install7([...flags, "--uninstall"], deps)
|
|
1095
1440
|
};
|
|
1096
1441
|
|
|
1097
1442
|
// src/harness/generic.ts
|
|
@@ -1119,7 +1464,7 @@ function createAdapterRegistry(adapters) {
|
|
|
1119
1464
|
}
|
|
1120
1465
|
return Object.freeze({ resolve: (id) => byId.get(id) });
|
|
1121
1466
|
}
|
|
1122
|
-
var ADAPTERS = Object.freeze([claude, codex, cursor, opencode, muse, gemini, copilot, generic]);
|
|
1467
|
+
var ADAPTERS = Object.freeze([claude, codex, cursor, opencode, muse, gemini, antigravity, copilot, qwen, generic]);
|
|
1123
1468
|
var registry = createAdapterRegistry(ADAPTERS);
|
|
1124
1469
|
function adapterFor(id) {
|
|
1125
1470
|
return registry.resolve(id) ?? (isHarnessId(id) ? genericAdapter(id) : void 0);
|
|
@@ -1128,6 +1473,7 @@ function adapterFor(id) {
|
|
|
1128
1473
|
export {
|
|
1129
1474
|
createCopilotCodec,
|
|
1130
1475
|
createWakeLadder,
|
|
1476
|
+
registerAntigravityWake,
|
|
1131
1477
|
SESSION_UNKNOWN_HINT,
|
|
1132
1478
|
recordHookSession,
|
|
1133
1479
|
resolveSources,
|