khala-cli 0.4.7 → 0.4.8
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 +18 -1
- package/dist/{chunk-25AX7UMF.mjs → chunk-2DHIAFEV.mjs} +5 -5
- package/dist/{chunk-OAFYPBCP.mjs → chunk-2VCAW4GH.mjs} +1 -1
- package/dist/{chunk-LNG3ZZBK.mjs → chunk-54XFVSEC.mjs} +1 -1
- package/dist/{chunk-JEI32DEA.mjs → chunk-5BQTYU66.mjs} +285 -33
- package/dist/{chunk-YIHRHD7W.mjs → chunk-5LIE5GUU.mjs} +1 -1
- package/dist/{chunk-S3FX7VCY.mjs → chunk-5PUQT3YS.mjs} +200 -17
- package/dist/{chunk-APIWKRKG.mjs → chunk-7HBRFY3Z.mjs} +1 -1
- package/dist/{chunk-SU6362YU.mjs → chunk-APMWDF6B.mjs} +1 -1
- package/dist/{chunk-C5SMOUTO.mjs → chunk-BKLW2SPG.mjs} +27 -9
- package/dist/{chunk-WNOIHCCC.mjs → chunk-CBPLCCRZ.mjs} +1 -1
- package/dist/{chunk-IG2NU5J6.mjs → chunk-CJTCU755.mjs} +4 -4
- package/dist/{chunk-4LSCOWWN.mjs → chunk-FH34B3I3.mjs} +6 -8
- package/dist/chunk-FV32AQTC.mjs +40 -0
- package/dist/{chunk-L7L6UNXN.mjs → chunk-H3K4GA2P.mjs} +9 -9
- package/dist/{chunk-3OFKVYOX.mjs → chunk-IQJQ4RF5.mjs} +1 -1
- package/dist/{chunk-TOBQUAPD.mjs → chunk-RNDUFTBF.mjs} +56 -32
- package/dist/{chunk-ODTSGEID.mjs → chunk-VAI7IUI4.mjs} +3 -3
- package/dist/{chunk-OP2HZ7EM.mjs → chunk-VKQ3KUYO.mjs} +16 -2
- package/dist/{chunk-K6EJSZYO.mjs → chunk-VUSV2ADL.mjs} +13 -15
- package/dist/{chunk-7V2YQIRI.mjs → chunk-W6R4UJUQ.mjs} +12 -5
- package/dist/{chunk-6YEYXZZB.mjs → chunk-Z4JF2M5C.mjs} +7 -8
- package/dist/{chunk-W5HS2PQO.mjs → chunk-ZJ4W7P33.mjs} +8 -8
- package/dist/khala.mjs +11 -11
- package/package.json +1 -1
- package/dist/chunk-5RGEO5HN.mjs +0 -142
- package/dist/{chunk-U3MKSPRO.mjs → chunk-NKX3SLS7.mjs} +37 -37
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# khala-cli
|
|
2
2
|
|
|
3
|
-
The `khala` command connects your existing Claude Code, Codex or
|
|
3
|
+
The `khala` command connects your existing Claude Code, Codex, Cursor or OpenCode session to a
|
|
4
4
|
[Khala](https://khala.aiur.team) channel: an MCP server (`khala_join`, `khala_status`,
|
|
5
5
|
`khala_read`, `khala_send`, `khala_event`), delivery hooks that bring channel messages into
|
|
6
6
|
the session, and local channels on this computer. Requires Node 22 or newer.
|
|
@@ -40,6 +40,23 @@ under Settings → MCP). Undo with `npx -y khala-cli install cursor --uninstall`
|
|
|
40
40
|
cannot wake an idle chat; new messages arrive when a chat finishes a turn (Sync) or after a
|
|
41
41
|
tool call (Steer).
|
|
42
42
|
|
|
43
|
+
## OpenCode
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npx -y khala-cli install opencode
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This installs a stable CLI and, when published on npm, pins `khala-opencode` at the same version in the global
|
|
50
|
+
`~/.config/opencode/opencode.json` (or under `XDG_CONFIG_HOME`). The plugin registers
|
|
51
|
+
Khala's MCP server. If the version is unpublished or the registry check fails, it configures
|
|
52
|
+
MCP-only mode (Async, no wake); re-run `khala install opencode` after updating. Existing
|
|
53
|
+
`opencode.jsonc` files require a manual merge or conversion to JSON before retrying. Restart OpenCode after installation. Existing settings and sibling
|
|
54
|
+
plugins are preserved. Undo with `npx -y khala-cli install opencode --uninstall`.
|
|
55
|
+
|
|
56
|
+
For unpublished-build testing only, `KHALA_INSTALL_SPEC` selects a CLI tarball and
|
|
57
|
+
`KHALA_OPENCODE_PLUGIN_SPEC` forces plugin mode with a spec such as
|
|
58
|
+
`file:/tmp/khala-opencode-0.1.0.tgz`.
|
|
59
|
+
|
|
43
60
|
## Then
|
|
44
61
|
|
|
45
62
|
Tell your agent "Join this Khala channel: <link>" and open the confirmation link it returns.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
KHALA_AGENT_VERSION
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5LIE5GUU.mjs";
|
|
5
5
|
import {
|
|
6
6
|
bundle
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-APMWDF6B.mjs";
|
|
8
8
|
import {
|
|
9
9
|
StateError,
|
|
10
10
|
ensureStateDir,
|
|
11
11
|
readJson,
|
|
12
12
|
stateRoot
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CBPLCCRZ.mjs";
|
|
14
14
|
import {
|
|
15
15
|
LISTENING_MODES
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-IQJQ4RF5.mjs";
|
|
17
17
|
import {
|
|
18
18
|
HARNESS_REGISTRY,
|
|
19
19
|
M1_LABEL_MAX_BYTES,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
utcTimestamp,
|
|
38
38
|
utf8Length,
|
|
39
39
|
version
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-NKX3SLS7.mjs";
|
|
41
41
|
|
|
42
42
|
// src/client.ts
|
|
43
43
|
var KhalaClientError = class extends Error {
|
|
@@ -2,7 +2,7 @@ import { createRequire as __khalaCreateRequire } from 'node:module'; const requi
|
|
|
2
2
|
import {
|
|
3
3
|
decodeChannelEvent,
|
|
4
4
|
formatChannelEventLine
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-54XFVSEC.mjs";
|
|
6
6
|
|
|
7
7
|
// src/events/receive.ts
|
|
8
8
|
function toEventInboxEntry(base, rawContent) {
|
|
@@ -2,23 +2,26 @@ import { createRequire as __khalaCreateRequire } from 'node:module'; const requi
|
|
|
2
2
|
import {
|
|
3
3
|
advanceCursor,
|
|
4
4
|
listChannels,
|
|
5
|
+
readActivity,
|
|
5
6
|
readCursor,
|
|
6
7
|
unread
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VKQ3KUYO.mjs";
|
|
8
9
|
import {
|
|
9
10
|
SESSION_ID_PATTERN,
|
|
10
11
|
StateError,
|
|
12
|
+
ensureStateDir,
|
|
11
13
|
readJson,
|
|
12
14
|
stateRoot,
|
|
13
15
|
writeJsonAtomic
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-CBPLCCRZ.mjs";
|
|
15
17
|
import {
|
|
16
18
|
DEFAULT_LISTENING_MODE,
|
|
17
19
|
decodeListeningMode
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-IQJQ4RF5.mjs";
|
|
19
21
|
import {
|
|
20
|
-
HARNESS_REGISTRY
|
|
21
|
-
|
|
22
|
+
HARNESS_REGISTRY,
|
|
23
|
+
isHarnessId
|
|
24
|
+
} from "./chunk-NKX3SLS7.mjs";
|
|
22
25
|
|
|
23
26
|
// src/mode.ts
|
|
24
27
|
async function readListeningMode(files) {
|
|
@@ -88,8 +91,8 @@ function wakeLine(nonce) {
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
// src/wake/idle-wake.ts
|
|
91
|
-
function codexIdleWakeArgv(sessionId) {
|
|
92
|
-
return ["queue", "--thread", sessionId, "--message",
|
|
94
|
+
function codexIdleWakeArgv(sessionId, line) {
|
|
95
|
+
return ["queue", "--thread", sessionId, "--message", line];
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
// src/wake/idle-wake-process.ts
|
|
@@ -159,21 +162,57 @@ function createCodexQueueProcessPort(deps) {
|
|
|
159
162
|
};
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
// src/wake/
|
|
163
|
-
import
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
// src/wake/codex-probe.ts
|
|
166
|
+
import { execFile } from "node:child_process";
|
|
167
|
+
import { access, realpath, stat } from "node:fs/promises";
|
|
168
|
+
import { constants } from "node:fs";
|
|
166
169
|
import path from "node:path";
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
import { promisify } from "node:util";
|
|
171
|
+
var execute = promisify(execFile);
|
|
172
|
+
var probes = /* @__PURE__ */ new Map();
|
|
173
|
+
async function probeCodexQueue(env) {
|
|
174
|
+
let command;
|
|
175
|
+
for (const dir of (env.PATH ?? "").split(path.delimiter).filter(Boolean)) {
|
|
176
|
+
try {
|
|
177
|
+
const candidate = path.join(dir, process.platform === "win32" ? "codex.exe" : "codex");
|
|
178
|
+
await access(candidate, constants.X_OK);
|
|
179
|
+
if (!(await stat(candidate)).isFile()) continue;
|
|
180
|
+
command = await realpath(candidate);
|
|
181
|
+
break;
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!command) return { available: false, reason: "codex_binary_missing" };
|
|
186
|
+
let mtime;
|
|
187
|
+
try {
|
|
188
|
+
mtime = (await stat(command)).mtimeMs;
|
|
189
|
+
} catch {
|
|
190
|
+
return { available: false, reason: "codex_binary_missing" };
|
|
191
|
+
}
|
|
192
|
+
const cached = probes.get(command);
|
|
193
|
+
if (cached?.mtime === mtime) return cached.result;
|
|
194
|
+
const binary = command;
|
|
195
|
+
const result = (async () => {
|
|
196
|
+
try {
|
|
197
|
+
const { stdout } = await execute(binary, ["queue", "--help"], {
|
|
198
|
+
env: scrubbedQueueEnv(env),
|
|
199
|
+
timeout: 5e3,
|
|
200
|
+
killSignal: "SIGKILL",
|
|
201
|
+
maxBuffer: 64 * 1024,
|
|
202
|
+
windowsHide: true
|
|
203
|
+
});
|
|
204
|
+
return /--thread\b/.test(stdout) && /--message\b/.test(stdout) ? { available: true, command: binary } : { available: false, reason: "codex_queue_unavailable" };
|
|
205
|
+
} catch {
|
|
206
|
+
return { available: false, reason: "codex_queue_unavailable" };
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
209
|
+
probes.set(binary, { mtime, result });
|
|
210
|
+
return result;
|
|
175
211
|
}
|
|
176
212
|
|
|
213
|
+
// src/wake/ladder.ts
|
|
214
|
+
import path4 from "node:path";
|
|
215
|
+
|
|
177
216
|
// src/wake/shared/settings.ts
|
|
178
217
|
import * as fs from "node:fs/promises";
|
|
179
218
|
import path2 from "node:path";
|
|
@@ -499,18 +538,20 @@ function createCodexWakeDriver(deps = {}) {
|
|
|
499
538
|
optIn: false,
|
|
500
539
|
minIdleMs: 0,
|
|
501
540
|
deadlineMs: 3e4,
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
async wake(ctx) {
|
|
506
|
-
const
|
|
507
|
-
|
|
541
|
+
verification: "nonce",
|
|
542
|
+
available: async (ctx) => (await (deps.probe ?? probeCodexQueue)(ctx.env)).available,
|
|
543
|
+
unavailableReason: async (ctx) => (await (deps.probe ?? probeCodexQueue)(ctx.env)).reason,
|
|
544
|
+
async wake(ctx, line) {
|
|
545
|
+
const probe = await (deps.probe ?? probeCodexQueue)(ctx.env);
|
|
546
|
+
if (!probe.available || ctx.signal.aborted) return;
|
|
547
|
+
const port = deps.port ?? createCodexQueueProcessPort({ command: probe.command, env: ctx.env });
|
|
548
|
+
const outcome = await port.run(codexIdleWakeArgv(ctx.sessionId, line), ctx.signal);
|
|
508
549
|
if (outcome.status !== "queued") {
|
|
509
|
-
const
|
|
550
|
+
const line2 = JSON.stringify({ ok: false, warning: "codex_queue_failed", status: outcome.status }) + "\n";
|
|
510
551
|
try {
|
|
511
552
|
(deps.stderr ?? ((value) => {
|
|
512
553
|
process.stderr.write(value);
|
|
513
|
-
}))(
|
|
554
|
+
}))(line2);
|
|
514
555
|
} catch {
|
|
515
556
|
}
|
|
516
557
|
}
|
|
@@ -520,7 +561,7 @@ function createCodexWakeDriver(deps = {}) {
|
|
|
520
561
|
var codexWakeDriver = createCodexWakeDriver();
|
|
521
562
|
|
|
522
563
|
// src/harness/codex.ts
|
|
523
|
-
var install = async (flags, deps) => (await import("./chunk-
|
|
564
|
+
var install = async (flags, deps) => (await import("./chunk-5PUQT3YS.mjs")).runCodexInstall(flags, deps);
|
|
524
565
|
var codex = {
|
|
525
566
|
id: "codex",
|
|
526
567
|
// A string metadata id wins even when empty or invalid; validation happens after selection.
|
|
@@ -594,7 +635,7 @@ var cursorCodec = {
|
|
|
594
635
|
};
|
|
595
636
|
|
|
596
637
|
// src/harness/cursor.ts
|
|
597
|
-
var install2 = async (flags, deps) => (await import("./chunk-
|
|
638
|
+
var install2 = async (flags, deps) => (await import("./chunk-5PUQT3YS.mjs")).runCursorInstall(flags, deps);
|
|
598
639
|
var cursor = {
|
|
599
640
|
id: "cursor",
|
|
600
641
|
sessionSources: [{
|
|
@@ -608,6 +649,213 @@ var cursor = {
|
|
|
608
649
|
uninstall: (flags, deps) => install2([...flags, "--uninstall"], deps)
|
|
609
650
|
};
|
|
610
651
|
|
|
652
|
+
// src/harness/codecs/opencode.ts
|
|
653
|
+
var events3 = { "session-start": "start", prompt: "prompt", "post-tool": "tool", "turn-end": "stop", idle: "stop" };
|
|
654
|
+
var opencodeCodec = {
|
|
655
|
+
promptAcceptsContext: true,
|
|
656
|
+
promptDeliversWithoutWake: true,
|
|
657
|
+
suppressOutputErrors: false,
|
|
658
|
+
parse(stdin) {
|
|
659
|
+
try {
|
|
660
|
+
const input = JSON.parse(stdin.replace(/^\uFEFF/u, ""));
|
|
661
|
+
if (!input || typeof input.session_id !== "string" || typeof input.event !== "string" || !Object.hasOwn(events3, input.event)) return null;
|
|
662
|
+
return {
|
|
663
|
+
sessionId: input.session_id,
|
|
664
|
+
event: events3[input.event],
|
|
665
|
+
continuation: false,
|
|
666
|
+
...typeof input.prompt === "string" ? { promptText: input.prompt } : {}
|
|
667
|
+
};
|
|
668
|
+
} catch {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
noop: () => "",
|
|
673
|
+
render: (_kind, frame) => frame
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// src/harness/session-sources.ts
|
|
677
|
+
import path5 from "node:path";
|
|
678
|
+
import { lstat } from "node:fs/promises";
|
|
679
|
+
|
|
680
|
+
// src/harness/proc.ts
|
|
681
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
682
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
683
|
+
import { promisify as promisify2 } from "node:util";
|
|
684
|
+
var execFileAsync = promisify2(execFile2);
|
|
685
|
+
var validPid = (pid) => Number.isSafeInteger(pid) && pid > 0;
|
|
686
|
+
function startToken(value) {
|
|
687
|
+
return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
688
|
+
}
|
|
689
|
+
function parseLinuxStat(pid, text) {
|
|
690
|
+
const open = text.indexOf("(");
|
|
691
|
+
const close = text.lastIndexOf(")");
|
|
692
|
+
if (!validPid(pid) || open < 0 || close < open || Number(text.slice(0, open).trim()) !== pid) return null;
|
|
693
|
+
const fields = text.slice(close + 1).trim().split(/\s+/);
|
|
694
|
+
const ppid = Number(fields[1]);
|
|
695
|
+
const startTime = fields[19];
|
|
696
|
+
if (!Number.isSafeInteger(ppid) || ppid < 0 || !startTime || !/^\d+$/.test(startTime)) return null;
|
|
697
|
+
return { pid, ppid, startTime, command: text.slice(open + 1, close) };
|
|
698
|
+
}
|
|
699
|
+
function parseDarwinPs(pid, text) {
|
|
700
|
+
const match = text.trim().match(/^(\d+)\s+([A-Za-z]{3}\s+[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s+(.+)$/);
|
|
701
|
+
if (!validPid(pid) || !match) return null;
|
|
702
|
+
return { pid, ppid: Number(match[1]), startTime: startToken(match[2]), command: match[3] };
|
|
703
|
+
}
|
|
704
|
+
function csvFields(line) {
|
|
705
|
+
const fields = [];
|
|
706
|
+
let value = "";
|
|
707
|
+
let quoted = false;
|
|
708
|
+
for (let i = 0; i < line.length; i++) {
|
|
709
|
+
const char = line[i];
|
|
710
|
+
if (char === '"') {
|
|
711
|
+
if (quoted && line[i + 1] === '"') {
|
|
712
|
+
value += '"';
|
|
713
|
+
i++;
|
|
714
|
+
} else quoted = !quoted;
|
|
715
|
+
} else if (char === "," && !quoted) {
|
|
716
|
+
fields.push(value);
|
|
717
|
+
value = "";
|
|
718
|
+
} else value += char;
|
|
719
|
+
}
|
|
720
|
+
if (quoted) return null;
|
|
721
|
+
fields.push(value);
|
|
722
|
+
return fields;
|
|
723
|
+
}
|
|
724
|
+
function parseWindowsCsv(text, pid) {
|
|
725
|
+
if (!validPid(pid)) return null;
|
|
726
|
+
const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/).filter((line) => line.trim() && !line.startsWith("#"));
|
|
727
|
+
const headers = csvFields(lines[0] ?? "");
|
|
728
|
+
if (!headers) return null;
|
|
729
|
+
for (const line of lines.slice(1)) {
|
|
730
|
+
const values = csvFields(line);
|
|
731
|
+
if (!values) continue;
|
|
732
|
+
const field = (name) => values[headers.indexOf(name)] ?? "";
|
|
733
|
+
if (Number(field("ProcessId")) !== pid) continue;
|
|
734
|
+
const ppid = Number(field("ParentProcessId"));
|
|
735
|
+
const startTime = startToken(field("CreationDate"));
|
|
736
|
+
const command = field("Name");
|
|
737
|
+
if (!field("ParentProcessId") || !Number.isSafeInteger(ppid) || ppid < 0 || !startTime || !command) return null;
|
|
738
|
+
return { pid, ppid, startTime, command };
|
|
739
|
+
}
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
async function readProcess(pid) {
|
|
743
|
+
if (!validPid(pid)) return null;
|
|
744
|
+
try {
|
|
745
|
+
if (process.platform === "linux") return parseLinuxStat(pid, await readFile3(`/proc/${pid}/stat`, "utf8"));
|
|
746
|
+
if (process.platform === "darwin") {
|
|
747
|
+
const { stdout } = await execFileAsync("ps", ["-o", "ppid=,lstart=,comm=", "-p", String(pid)], { env: { ...process.env, LC_ALL: "C" }, timeout: 5e3 });
|
|
748
|
+
return parseDarwinPs(pid, stdout);
|
|
749
|
+
}
|
|
750
|
+
if (process.platform === "win32") {
|
|
751
|
+
const script = `Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' | Select-Object ProcessId,ParentProcessId,@{Name='CreationDate';Expression={$_.CreationDate.ToUniversalTime().ToString('o')}},Name | ConvertTo-Csv -NoTypeInformation`;
|
|
752
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: 5e3 });
|
|
753
|
+
return parseWindowsCsv(stdout, pid);
|
|
754
|
+
}
|
|
755
|
+
} catch {
|
|
756
|
+
}
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
async function* walkAncestors(pid, read = readProcess) {
|
|
760
|
+
const seen = /* @__PURE__ */ new Set([pid]);
|
|
761
|
+
let current = await read(pid);
|
|
762
|
+
while (current && validPid(current.ppid) && !seen.has(current.ppid)) {
|
|
763
|
+
seen.add(current.ppid);
|
|
764
|
+
current = await read(current.ppid);
|
|
765
|
+
if (current) yield current;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async function nearestNonShellAncestor(pid, read = readProcess) {
|
|
769
|
+
for await (const ancestor of walkAncestors(pid, read)) {
|
|
770
|
+
const name = ancestor.command.split(/[\\/]/).pop()?.replace(/^-/, "").toLowerCase() ?? "";
|
|
771
|
+
if (!/^(?:sh|bash|dash|zsh|fish|ksh|csh|tcsh|ash|cmd(?:\.exe)?|powershell(?:\.exe)?|pwsh(?:\.exe)?)$/.test(name)) return ancestor;
|
|
772
|
+
}
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/harness/session-sources.ts
|
|
777
|
+
var SESSION_UNKNOWN_HINT = "Send the agent one message first, then retry.";
|
|
778
|
+
var mappingDir = (harness, env) => path5.join(stateRoot(env), harness, ".by-pid");
|
|
779
|
+
async function recordHookSession(harness, sessionId, env, options = {}) {
|
|
780
|
+
if (!SESSION_ID_PATTERN.test(sessionId)) return;
|
|
781
|
+
const parent = await nearestNonShellAncestor(options.pid ?? process.pid, options.readProcess ?? readProcess);
|
|
782
|
+
if (!parent) return;
|
|
783
|
+
const dir = mappingDir(harness, env);
|
|
784
|
+
await ensureStateDir(dir);
|
|
785
|
+
const file = path5.join(dir, `${parent.pid}.json`);
|
|
786
|
+
const at = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
787
|
+
const previous = await readJson(file);
|
|
788
|
+
if (previous?.startTime === parent.startTime && typeof previous.at === "string" && previous.at > at) return;
|
|
789
|
+
await writeJsonAtomic(file, {
|
|
790
|
+
sessionId,
|
|
791
|
+
startTime: parent.startTime,
|
|
792
|
+
at,
|
|
793
|
+
...options.workspace !== void 0 ? { workspace: options.workspace } : {}
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
var hookMapSource = {
|
|
797
|
+
kind: "hook-map",
|
|
798
|
+
rejoinable: () => true,
|
|
799
|
+
async resolve(_meta, env, context) {
|
|
800
|
+
const dir = mappingDir(context.harness, env);
|
|
801
|
+
try {
|
|
802
|
+
await lstat(dir);
|
|
803
|
+
} catch (error) {
|
|
804
|
+
if (error.code === "ENOENT") return null;
|
|
805
|
+
throw error;
|
|
806
|
+
}
|
|
807
|
+
await ensureStateDir(dir);
|
|
808
|
+
const parent = await nearestNonShellAncestor(context.pid ?? process.pid, context.readProcess ?? readProcess);
|
|
809
|
+
if (!parent) return null;
|
|
810
|
+
const entry = await readJson(path5.join(dir, `${parent.pid}.json`));
|
|
811
|
+
if (entry?.startTime === parent.startTime && typeof entry.sessionId === "string" && SESSION_ID_PATTERN.test(entry.sessionId) && typeof entry.at === "string" && Number.isFinite(Date.parse(entry.at))) return entry.sessionId;
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
var processSource = {
|
|
816
|
+
kind: "process",
|
|
817
|
+
rejoinable: () => false,
|
|
818
|
+
async resolve(_meta, _env, context) {
|
|
819
|
+
for await (const parent of walkAncestors(context.pid ?? process.pid, context.readProcess ?? readProcess)) {
|
|
820
|
+
return `proc-${parent.pid}-${parent.startTime}`;
|
|
821
|
+
}
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
async function resolveSources(sources, meta, env, context) {
|
|
826
|
+
for (const source of sources) {
|
|
827
|
+
const value = await source.resolve(meta, env, context);
|
|
828
|
+
if (value === void 0 || value === null) continue;
|
|
829
|
+
return typeof value === "string" && SESSION_ID_PATTERN.test(value) ? { sessionId: value, rejoinable: source.rejoinable(value) } : null;
|
|
830
|
+
}
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/harness/opencode.ts
|
|
835
|
+
var install3 = async (flags, deps) => (await import("./chunk-5PUQT3YS.mjs")).runOpenCodeInstall(flags, deps);
|
|
836
|
+
var opencode = {
|
|
837
|
+
id: "opencode",
|
|
838
|
+
sessionSources: [{ kind: "meta", resolve: (meta) => meta?.khala_session, rejoinable: () => true }, hookMapSource],
|
|
839
|
+
codec: opencodeCodec,
|
|
840
|
+
restoreAtStartup: false,
|
|
841
|
+
install: install3,
|
|
842
|
+
uninstall: (flags, deps) => install3([...flags, "--uninstall"], deps)
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// src/harness/generic.ts
|
|
846
|
+
var generic = {
|
|
847
|
+
id: "generic",
|
|
848
|
+
sessionSources: [
|
|
849
|
+
{ kind: "env", resolve: (_meta, env) => env.KHALA_SESSION_ID, rejoinable: () => true },
|
|
850
|
+
processSource
|
|
851
|
+
],
|
|
852
|
+
codec: void 0,
|
|
853
|
+
restoreAtStartup: true
|
|
854
|
+
};
|
|
855
|
+
function genericAdapter(id) {
|
|
856
|
+
return { ...generic, id };
|
|
857
|
+
}
|
|
858
|
+
|
|
611
859
|
// src/harness/index.ts
|
|
612
860
|
function createAdapterRegistry(adapters) {
|
|
613
861
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -618,17 +866,21 @@ function createAdapterRegistry(adapters) {
|
|
|
618
866
|
}
|
|
619
867
|
return Object.freeze({ resolve: (id) => byId.get(id) });
|
|
620
868
|
}
|
|
621
|
-
var ADAPTERS = Object.freeze([claude, codex, cursor]);
|
|
869
|
+
var ADAPTERS = Object.freeze([claude, codex, cursor, opencode, generic]);
|
|
622
870
|
var registry = createAdapterRegistry(ADAPTERS);
|
|
623
|
-
|
|
871
|
+
function adapterFor(id) {
|
|
872
|
+
return registry.resolve(id) ?? (isHarnessId(id) ? genericAdapter(id) : void 0);
|
|
873
|
+
}
|
|
624
874
|
|
|
625
875
|
export {
|
|
626
876
|
readListeningMode,
|
|
627
877
|
applyListeningMode,
|
|
628
|
-
|
|
629
|
-
writeActivity,
|
|
878
|
+
readWakeState,
|
|
630
879
|
settleAttempts,
|
|
631
880
|
createWakeLadder,
|
|
632
881
|
CURSOR_WORKSPACE_ENV,
|
|
882
|
+
SESSION_UNKNOWN_HINT,
|
|
883
|
+
recordHookSession,
|
|
884
|
+
resolveSources,
|
|
633
885
|
adapterFor
|
|
634
886
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
bundle
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-APMWDF6B.mjs";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
7
|
var KHALA_AGENT_VERSION = bundle?.version ?? "0.0.0";
|