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
|
@@ -5,20 +5,20 @@ import {
|
|
|
5
5
|
reportReady,
|
|
6
6
|
requestJoin,
|
|
7
7
|
saveHostedUsername
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VAI7IUI4.mjs";
|
|
9
9
|
import {
|
|
10
10
|
createEventKeyFilter,
|
|
11
11
|
isWakeEntry,
|
|
12
12
|
toEventInboxEntry
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2VCAW4GH.mjs";
|
|
14
14
|
import {
|
|
15
15
|
KhalaClientError,
|
|
16
16
|
LOCAL_OWNER_USER_ID
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2DHIAFEV.mjs";
|
|
18
18
|
import {
|
|
19
19
|
KHALA_AGENT_VERSION
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-5LIE5GUU.mjs";
|
|
21
|
+
import "./chunk-APMWDF6B.mjs";
|
|
22
22
|
import "./chunk-5GLTKWJ5.mjs";
|
|
23
23
|
import {
|
|
24
24
|
CHANNEL_EVENT_TYPE,
|
|
@@ -26,21 +26,20 @@ import {
|
|
|
26
26
|
formatChannelEventLine,
|
|
27
27
|
memberRenameContent,
|
|
28
28
|
statusFor
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-54XFVSEC.mjs";
|
|
30
30
|
import {
|
|
31
31
|
monitorArmed,
|
|
32
32
|
resolveHarness,
|
|
33
33
|
resolveSession
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import {
|
|
36
|
-
SESSION_UNKNOWN_HINT
|
|
37
|
-
} from "./chunk-5RGEO5HN.mjs";
|
|
34
|
+
} from "./chunk-FH34B3I3.mjs";
|
|
38
35
|
import {
|
|
36
|
+
SESSION_UNKNOWN_HINT,
|
|
39
37
|
adapterFor,
|
|
40
38
|
applyListeningMode,
|
|
41
39
|
createWakeLadder,
|
|
42
|
-
readListeningMode
|
|
43
|
-
|
|
40
|
+
readListeningMode,
|
|
41
|
+
readWakeState
|
|
42
|
+
} from "./chunk-5BQTYU66.mjs";
|
|
44
43
|
import {
|
|
45
44
|
appendInbox,
|
|
46
45
|
channelKey,
|
|
@@ -48,7 +47,7 @@ import {
|
|
|
48
47
|
migrateLegacy,
|
|
49
48
|
resolveChannel,
|
|
50
49
|
unreadCount
|
|
51
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-VKQ3KUYO.mjs";
|
|
52
51
|
import {
|
|
53
52
|
StateError,
|
|
54
53
|
TERMINAL_SESSION_DETAILS,
|
|
@@ -64,10 +63,10 @@ import {
|
|
|
64
63
|
sessionFiles,
|
|
65
64
|
writeJoinFile,
|
|
66
65
|
writeStateFile
|
|
67
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-CBPLCCRZ.mjs";
|
|
68
67
|
import {
|
|
69
68
|
decodeListeningModeCommand
|
|
70
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-IQJQ4RF5.mjs";
|
|
71
70
|
import {
|
|
72
71
|
displayText,
|
|
73
72
|
harnessInfo,
|
|
@@ -75,8 +74,8 @@ import {
|
|
|
75
74
|
label,
|
|
76
75
|
utf8Length,
|
|
77
76
|
validAgentRejoinSecret
|
|
78
|
-
} from "./chunk-
|
|
79
|
-
import "./chunk-
|
|
77
|
+
} from "./chunk-NKX3SLS7.mjs";
|
|
78
|
+
import "./chunk-7HBRFY3Z.mjs";
|
|
80
79
|
|
|
81
80
|
// src/mcp/main.ts
|
|
82
81
|
import { Console } from "node:console";
|
|
@@ -524,14 +523,17 @@ function errorCode(error) {
|
|
|
524
523
|
return typeof code === "string" && ERROR_CODES.includes(code) ? code : "internal_error";
|
|
525
524
|
}
|
|
526
525
|
function createKhalaTools(input) {
|
|
526
|
+
const sessionProperties = input.harness === "opencode" ? { khala_session: { type: "string", minLength: 1, description: "Session ID stamped by the OpenCode plugin." } } : {};
|
|
527
|
+
const validSession = (args) => !Object.hasOwn(args, "khala_session") || input.harness === "opencode" && typeof args.khala_session === "string" && args.khala_session.length > 0;
|
|
528
|
+
const sessionMeta = (args, meta) => input.harness === "opencode" && Object.hasOwn(args, "khala_session") ? { ...meta, khala_session: args.khala_session } : meta;
|
|
527
529
|
function tool(name, description, properties, required, validate, invoke, render = JSON.stringify) {
|
|
528
530
|
return {
|
|
529
531
|
name,
|
|
530
|
-
definition: () => ({ name, description, inputSchema: { type: "object", properties, required, additionalProperties: false } }),
|
|
532
|
+
definition: () => ({ name, description, inputSchema: { type: "object", properties: { ...properties, ...sessionProperties }, required, additionalProperties: false } }),
|
|
531
533
|
async call(args, context) {
|
|
532
|
-
if (!hasOnly(args, Object.keys(properties)) || !validate(args)) return failure(context.id, -32602, "Invalid params");
|
|
534
|
+
if (!hasOnly(args, Object.keys({ ...properties, ...sessionProperties })) || !validSession(args) || !validate(args)) return failure(context.id, -32602, "Invalid params");
|
|
533
535
|
try {
|
|
534
|
-
const client = await input.clientFor(context.meta);
|
|
536
|
+
const client = await input.clientFor(sessionMeta(args, context.meta));
|
|
535
537
|
if (client === null) return success(context.id, toolError("session_unknown", { hint: SESSION_UNKNOWN_HINT }));
|
|
536
538
|
const structuredContent = await invoke(client, args);
|
|
537
539
|
return success(context.id, { content: [{ type: "text", text: render(structuredContent) }], structuredContent });
|
|
@@ -553,7 +555,7 @@ function createKhalaTools(input) {
|
|
|
553
555
|
type: "string",
|
|
554
556
|
minLength: 1,
|
|
555
557
|
maxLength: 40,
|
|
556
|
-
description: "Optional and ignored: Khala names you <OwnerUsername>-<
|
|
558
|
+
description: "Optional and ignored: Khala names you <OwnerUsername>-<Model>, and your owner can rename you."
|
|
557
559
|
} },
|
|
558
560
|
["link"],
|
|
559
561
|
(args) => typeof args.link === "string" && (!Object.hasOwn(args, "label") || typeof args.label === "string" && args.label.trim().length > 0 && [...args.label].length <= 40),
|
|
@@ -601,15 +603,16 @@ function createKhalaTools(input) {
|
|
|
601
603
|
description: 'Select channel by name or channel ID; required when joined to more than one channel. Post a compact progress event (PR, CI, ticket status) into the Khala channel. Events are progress signals, not channel messages: they never wake other agents. Pass Khala JSON as "event", or a raw Aiur bus event, wake record or alert as "aiur".',
|
|
602
604
|
inputSchema: {
|
|
603
605
|
type: "object",
|
|
604
|
-
properties: { channel: channelProperty, event: { type: "object" }, aiur: { type: "object" }, ticketPrefix: { type: "string", minLength: 0, maxLength: 16 } },
|
|
606
|
+
properties: { ...sessionProperties, channel: channelProperty, event: { type: "object" }, aiur: { type: "object" }, ticketPrefix: { type: "string", minLength: 0, maxLength: 16 } },
|
|
605
607
|
required: [],
|
|
606
608
|
additionalProperties: false,
|
|
607
609
|
oneOf: [{ required: ["event"], not: { required: ["aiur"] } }, { required: ["aiur"], not: { required: ["event"] } }]
|
|
608
610
|
}
|
|
609
611
|
}),
|
|
610
612
|
async call(args, context) {
|
|
611
|
-
if (!validChannel(args)) return failure(context.id, -32602, "Invalid params");
|
|
613
|
+
if (!validSession(args) || !validChannel(args)) return failure(context.id, -32602, "Invalid params");
|
|
612
614
|
const { channel, ...eventArgs } = args;
|
|
615
|
+
if (input.harness === "opencode") delete eventArgs.khala_session;
|
|
613
616
|
const resolved = resolveEventInput(eventArgs);
|
|
614
617
|
if (resolved.kind === "invalid") {
|
|
615
618
|
const error = { error: "invalid_event", path: resolved.path, code: resolved.code };
|
|
@@ -619,7 +622,7 @@ function createKhalaTools(input) {
|
|
|
619
622
|
return success(context.id, { content: [{ type: "text", text: "Skipped channel event." }], structuredContent: { skipped: true } });
|
|
620
623
|
}
|
|
621
624
|
try {
|
|
622
|
-
const client = await input.clientFor(context.meta);
|
|
625
|
+
const client = await input.clientFor(sessionMeta(args, context.meta));
|
|
623
626
|
if (client === null) return success(context.id, toolError("session_unknown", { hint: SESSION_UNKNOWN_HINT }));
|
|
624
627
|
const sent = await (channel === void 0 ? client.sendChannelEvent(resolved.content) : client.sendChannelEvent(resolved.content, channel));
|
|
625
628
|
return success(context.id, { content: [{ type: "text", text: `Posted channel event: ${resolved.content.body}` }], structuredContent: sent });
|
|
@@ -643,7 +646,7 @@ import * as fs from "node:fs/promises";
|
|
|
643
646
|
import { createHash, randomBytes } from "node:crypto";
|
|
644
647
|
|
|
645
648
|
// src/transport.ts
|
|
646
|
-
var startChannelSession = async (creds, options) => creds.transport === "local" ? (await import("./chunk-
|
|
649
|
+
var startChannelSession = async (creds, options) => creds.transport === "local" ? (await import("./chunk-ZJ4W7P33.mjs")).createLocalSession(creds) : (await import("./chunk-CJTCU755.mjs")).createAgentMatrixSession(creds, options);
|
|
647
650
|
|
|
648
651
|
// src/sender.ts
|
|
649
652
|
function localpart(userId) {
|
|
@@ -680,6 +683,8 @@ function errorDetail(error, fallback) {
|
|
|
680
683
|
return ["unsafe_state_dir", "storage_failed"].includes(failure2.message) ? failure2.message : fallback;
|
|
681
684
|
}
|
|
682
685
|
function createKhalaAgentClient(options) {
|
|
686
|
+
const asyncOnly = !adapterFor(options.harness)?.codec;
|
|
687
|
+
const defaultMode = asyncOnly ? "async" : "sync";
|
|
683
688
|
const dir = resolveStateDir(options.harness, options.sessionId, options.env);
|
|
684
689
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
685
690
|
const fetchDeps = options.fetch ? { fetch: options.fetch } : {};
|
|
@@ -968,8 +973,9 @@ function createKhalaAgentClient(options) {
|
|
|
968
973
|
if (!decoded.ok || decoded.value.agent !== session.userId) return;
|
|
969
974
|
attempt.appends = attempt.appends.then(async () => {
|
|
970
975
|
if (!current(attempt)) return;
|
|
971
|
-
|
|
972
|
-
await
|
|
976
|
+
const mode = asyncOnly ? "async" : decoded.value.mode;
|
|
977
|
+
await applyListeningMode(attempt.files, mode, { changedBy: "owner", eventId: command.eventId }, now);
|
|
978
|
+
await publishMode(attempt, session, credentials.roomId, mode);
|
|
973
979
|
}).catch(async () => {
|
|
974
980
|
if (current(attempt)) await setStatus(attempt, "disconnected", "internal_error").catch(() => {
|
|
975
981
|
});
|
|
@@ -981,7 +987,10 @@ function createKhalaAgentClient(options) {
|
|
|
981
987
|
await wait(session.waitForInvite(credentials.roomId, options.inviteTimeoutMs ?? 12e4));
|
|
982
988
|
await wait(session.join(credentials.roomId));
|
|
983
989
|
if (!current(attempt)) return;
|
|
984
|
-
if (
|
|
990
|
+
if (asyncOnly) {
|
|
991
|
+
await writeStateFile(channelDir, "mode.json", { mode: "async" });
|
|
992
|
+
await publishMode(attempt, session, credentials.roomId, "async");
|
|
993
|
+
} else if (session.listeningMode) await writeStateFile(channelDir, "mode.json", { mode: session.listeningMode(credentials.roomId) });
|
|
985
994
|
else await removeStateFile(channelDir, "mode.json");
|
|
986
995
|
attempt.joined = true;
|
|
987
996
|
attempt.status.channelName = session.roomName(credentials.roomId) ?? credentials.roomId;
|
|
@@ -1169,7 +1178,7 @@ function createKhalaAgentClient(options) {
|
|
|
1169
1178
|
link: attempt.link,
|
|
1170
1179
|
state: "joining",
|
|
1171
1180
|
unread: 0,
|
|
1172
|
-
listeningMode:
|
|
1181
|
+
listeningMode: defaultMode
|
|
1173
1182
|
});
|
|
1174
1183
|
}
|
|
1175
1184
|
}
|
|
@@ -1180,11 +1189,11 @@ function createKhalaAgentClient(options) {
|
|
|
1180
1189
|
...single?.detail !== void 0 ? { detail: single.detail } : channel === void 0 && status.detail ? { detail: status.detail } : {},
|
|
1181
1190
|
...single ? {
|
|
1182
1191
|
channelName: single.channel,
|
|
1183
|
-
...single.you !== void 0 ? { displayName: single.you } : {},
|
|
1192
|
+
...single.you !== void 0 ? { displayName: single.you, you: single.you } : {},
|
|
1184
1193
|
...single.agentUserId !== void 0 ? { agentUserId: single.agentUserId } : {}
|
|
1185
1194
|
} : {},
|
|
1186
1195
|
unread: items.reduce((sum, item) => sum + item.unread, 0),
|
|
1187
|
-
...single ? { listeningMode: single.listeningMode } : items.length === 0 ? { listeningMode:
|
|
1196
|
+
...single ? { listeningMode: single.listeningMode } : items.length === 0 ? { listeningMode: defaultMode } : {},
|
|
1188
1197
|
channels: items
|
|
1189
1198
|
};
|
|
1190
1199
|
},
|
|
@@ -1328,7 +1337,22 @@ function createRealClientFactory(env, deps = {}) {
|
|
|
1328
1337
|
async status(channel) {
|
|
1329
1338
|
await initialize();
|
|
1330
1339
|
const status = await (channel === void 0 ? client.status() : client.status(channel));
|
|
1331
|
-
|
|
1340
|
+
let wakeDrivers;
|
|
1341
|
+
if (drivers?.length) {
|
|
1342
|
+
try {
|
|
1343
|
+
const states = await readWakeState(files.dir);
|
|
1344
|
+
wakeDrivers = await Promise.all(drivers.map(async (driver) => {
|
|
1345
|
+
const ctx = { files, harness, sessionId, env, now: Date.now(), signal: new AbortController().signal };
|
|
1346
|
+
const available = !states[driver.id]?.disabled && await driver.available(ctx);
|
|
1347
|
+
const reason = states[driver.id]?.disabled ? states[driver.id]?.reason : available ? void 0 : await driver.unavailableReason?.(ctx);
|
|
1348
|
+
return { id: driver.id, available, ...reason ? { reason } : {} };
|
|
1349
|
+
}));
|
|
1350
|
+
} catch {
|
|
1351
|
+
wakeDrivers = drivers.map((driver) => ({ id: driver.id, available: false, reason: "wake_status_unavailable" }));
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
const withWake = wakeDrivers ? { ...status, wakeDrivers } : status;
|
|
1355
|
+
return adapter?.watcherStatus && ["connected", "send_failed"].includes(status.state) ? { ...withWake, watcherArmed: await monitorArmed(files) } : withWake;
|
|
1332
1356
|
},
|
|
1333
1357
|
async read(limit, before, channel) {
|
|
1334
1358
|
await initialize();
|
|
@@ -4,18 +4,18 @@ import {
|
|
|
4
4
|
checkName,
|
|
5
5
|
isDefaultAgentName,
|
|
6
6
|
restartHelper
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2DHIAFEV.mjs";
|
|
8
8
|
import {
|
|
9
9
|
StateError,
|
|
10
10
|
readJson,
|
|
11
11
|
stateRoot,
|
|
12
12
|
writeJsonAtomic
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CBPLCCRZ.mjs";
|
|
14
14
|
import {
|
|
15
15
|
harnessInfo,
|
|
16
16
|
isHarnessId,
|
|
17
17
|
validateAgentName
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NKX3SLS7.mjs";
|
|
19
19
|
|
|
20
20
|
// src/local/identity.ts
|
|
21
21
|
import * as fs from "node:fs/promises";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
stateKey,
|
|
12
12
|
writeJsonAtomic,
|
|
13
13
|
writeStateFile
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-CBPLCCRZ.mjs";
|
|
15
15
|
|
|
16
16
|
// src/inbox.ts
|
|
17
17
|
import * as fs from "node:fs/promises";
|
|
@@ -202,6 +202,18 @@ function resolveChannel(list, ref) {
|
|
|
202
202
|
return { ok: false, code: matches.length ? "channel_ambiguous" : "channel_unknown", channels };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// src/activity.ts
|
|
206
|
+
import path2 from "node:path";
|
|
207
|
+
async function readActivity(files) {
|
|
208
|
+
const activity = await readJson(path2.join(files.dir, "activity.json"));
|
|
209
|
+
return activity && ["idle", "busy"].includes(activity.state) && typeof activity.updatedAt === "string" && Number.isFinite(Date.parse(activity.updatedAt)) ? activity : { state: "busy", updatedAt: (/* @__PURE__ */ new Date(0)).toISOString() };
|
|
210
|
+
}
|
|
211
|
+
async function writeActivity(files, state, now = () => /* @__PURE__ */ new Date()) {
|
|
212
|
+
const activity = { state, updatedAt: now().toISOString() };
|
|
213
|
+
await writeJsonAtomic(path2.join(files.dir, "activity.json"), activity);
|
|
214
|
+
return activity;
|
|
215
|
+
}
|
|
216
|
+
|
|
205
217
|
export {
|
|
206
218
|
readEntries,
|
|
207
219
|
readCursor,
|
|
@@ -212,5 +224,7 @@ export {
|
|
|
212
224
|
channelKey,
|
|
213
225
|
listChannels,
|
|
214
226
|
migrateLegacy,
|
|
215
|
-
resolveChannel
|
|
227
|
+
resolveChannel,
|
|
228
|
+
readActivity,
|
|
229
|
+
writeActivity
|
|
216
230
|
};
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
isWakeEntry
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
recordHookSession
|
|
8
|
-
} from "./chunk-5RGEO5HN.mjs";
|
|
4
|
+
} from "./chunk-2VCAW4GH.mjs";
|
|
5
|
+
import "./chunk-54XFVSEC.mjs";
|
|
9
6
|
import {
|
|
10
7
|
adapterFor,
|
|
11
|
-
readActivity,
|
|
12
8
|
readListeningMode,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
9
|
+
recordHookSession,
|
|
10
|
+
settleAttempts
|
|
11
|
+
} from "./chunk-5BQTYU66.mjs";
|
|
16
12
|
import {
|
|
17
13
|
advanceCursor,
|
|
18
14
|
listChannels,
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
readActivity,
|
|
16
|
+
unread,
|
|
17
|
+
writeActivity
|
|
18
|
+
} from "./chunk-VKQ3KUYO.mjs";
|
|
21
19
|
import {
|
|
22
20
|
StateError,
|
|
23
21
|
readStatus,
|
|
24
22
|
sessionFiles
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-CBPLCCRZ.mjs";
|
|
24
|
+
import "./chunk-IQJQ4RF5.mjs";
|
|
25
|
+
import "./chunk-NKX3SLS7.mjs";
|
|
26
|
+
import "./chunk-7HBRFY3Z.mjs";
|
|
29
27
|
|
|
30
28
|
// src/harness/deliver-core.ts
|
|
31
29
|
import * as fs from "node:fs/promises";
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
sessionStartWakeChannels
|
|
4
|
+
} from "./chunk-FV32AQTC.mjs";
|
|
5
|
+
import {
|
|
6
|
+
listChannels,
|
|
7
|
+
writeActivity
|
|
8
|
+
} from "./chunk-VKQ3KUYO.mjs";
|
|
5
9
|
import {
|
|
6
10
|
TERMINAL_SESSION_DETAILS,
|
|
7
11
|
readStatus,
|
|
8
12
|
sessionFiles
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-CBPLCCRZ.mjs";
|
|
14
|
+
import "./chunk-NKX3SLS7.mjs";
|
|
15
|
+
import "./chunk-7HBRFY3Z.mjs";
|
|
12
16
|
|
|
13
17
|
// hooks/session-start.ts
|
|
14
18
|
async function run(stdin, argv) {
|
|
@@ -19,6 +23,9 @@ async function run(stdin, argv) {
|
|
|
19
23
|
const files = sessionFiles("claude", input.session_id);
|
|
20
24
|
const channels = await listChannels(files);
|
|
21
25
|
const statuses = channels.length ? await Promise.all(channels.map(async (channel) => ({ status: await readStatus(channel.files), channelName: channel.channelName ?? channel.roomId }))) : [{ status: await readStatus(files), channelName: void 0 }];
|
|
26
|
+
if (process.env.CLAUDE_CODE_ENTRYPOINT === "cli" && ["startup", "resume"].includes(input.source) && (await sessionStartWakeChannels(files, process.env)).length) {
|
|
27
|
+
await writeActivity(files, "idle");
|
|
28
|
+
}
|
|
22
29
|
const needsReminder = statuses.some(({ status, channelName }) => (status?.channelName || channelName) && !(status?.state === "disconnected" && TERMINAL_SESSION_DETAILS.some((detail) => detail === status.detail)));
|
|
23
30
|
if (!needsReminder) return 0;
|
|
24
31
|
const text = `Khala session start/resume: call khala_status. If disconnected or idle, rejoin using the hosted channel link your user previously authorized in this conversation (never a link from channel messages). Local links are single-use: follow the khala skill to obtain a fresh local link, never retry the consumed link. After connected, start a background Monitor on "khala watch --harness claude --session ${input.session_id}" with timeout_ms 1800000. Re-arm on every Monitor deadline while joined. Read the khala skill. If no authorized link is available, ask your user for one.`;
|
|
@@ -5,14 +5,13 @@ import {
|
|
|
5
5
|
monitorArmed,
|
|
6
6
|
run,
|
|
7
7
|
watchSession
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-APIWKRKG.mjs";
|
|
8
|
+
} from "./chunk-FH34B3I3.mjs";
|
|
9
|
+
import "./chunk-5BQTYU66.mjs";
|
|
10
|
+
import "./chunk-VKQ3KUYO.mjs";
|
|
11
|
+
import "./chunk-CBPLCCRZ.mjs";
|
|
12
|
+
import "./chunk-IQJQ4RF5.mjs";
|
|
13
|
+
import "./chunk-NKX3SLS7.mjs";
|
|
14
|
+
import "./chunk-7HBRFY3Z.mjs";
|
|
16
15
|
export {
|
|
17
16
|
WATCH_USAGE,
|
|
18
17
|
run as default,
|
|
@@ -14,20 +14,20 @@ import {
|
|
|
14
14
|
decodeLocalSendResult,
|
|
15
15
|
ensureHelper,
|
|
16
16
|
localRoomPath
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-2DHIAFEV.mjs";
|
|
18
|
+
import "./chunk-5LIE5GUU.mjs";
|
|
19
|
+
import "./chunk-APMWDF6B.mjs";
|
|
20
20
|
import "./chunk-5GLTKWJ5.mjs";
|
|
21
21
|
import {
|
|
22
22
|
CHANNEL_EVENT_TYPE,
|
|
23
23
|
memberRenameContent
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-54XFVSEC.mjs";
|
|
25
|
+
import "./chunk-CBPLCCRZ.mjs";
|
|
26
26
|
import {
|
|
27
27
|
LISTENING_MODE_COMMAND_TYPE
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import "./chunk-
|
|
30
|
-
import "./chunk-
|
|
28
|
+
} from "./chunk-IQJQ4RF5.mjs";
|
|
29
|
+
import "./chunk-NKX3SLS7.mjs";
|
|
30
|
+
import "./chunk-7HBRFY3Z.mjs";
|
|
31
31
|
|
|
32
32
|
// src/local/session.ts
|
|
33
33
|
import { randomUUID } from "node:crypto";
|
package/dist/khala.mjs
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
KHALA_AGENT_VERSION
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-5LIE5GUU.mjs";
|
|
6
|
+
import "./chunk-APMWDF6B.mjs";
|
|
7
|
+
import "./chunk-7HBRFY3Z.mjs";
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
var HOOK_NAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
11
11
|
var STDIN_CAP = 1024 * 1024;
|
|
12
|
-
var USAGE = "usage: khala mcp | khala watch [--harness claude|codex|cursor --session <id>] | khala hook <name> | khala local <command> | khala install codex | khala install cursor | khala --version";
|
|
12
|
+
var USAGE = "usage: khala mcp | khala watch [--harness claude|codex|cursor --session <id>] | khala hook <name> | khala local <command> | khala install codex | khala install cursor | khala install opencode | khala install mcp --print [--harness <id>] | khala --version";
|
|
13
13
|
async function readStdin() {
|
|
14
14
|
if (process.stdin.isTTY) return "";
|
|
15
15
|
const chunks = [];
|
|
@@ -78,14 +78,14 @@ async function runCli(argv, modules) {
|
|
|
78
78
|
|
|
79
79
|
// src/cli-bundle.ts
|
|
80
80
|
var hooks = {
|
|
81
|
-
"session-start": () => import("./chunk-
|
|
82
|
-
deliver: () => import("./chunk-
|
|
83
|
-
"claude-wake": () => import("./chunk-
|
|
81
|
+
"session-start": () => import("./chunk-W6R4UJUQ.mjs"),
|
|
82
|
+
deliver: () => import("./chunk-VUSV2ADL.mjs"),
|
|
83
|
+
"claude-wake": () => import("./chunk-BKLW2SPG.mjs")
|
|
84
84
|
};
|
|
85
85
|
process.exitCode = await runCli(process.argv.slice(2), {
|
|
86
|
-
mcp: () => () => import("./chunk-
|
|
87
|
-
watch: () => () => import("./chunk-
|
|
88
|
-
local: () => () => import("./chunk-
|
|
89
|
-
install: () => () => import("./chunk-
|
|
86
|
+
mcp: () => () => import("./chunk-RNDUFTBF.mjs"),
|
|
87
|
+
watch: () => () => import("./chunk-Z4JF2M5C.mjs"),
|
|
88
|
+
local: () => () => import("./chunk-H3K4GA2P.mjs"),
|
|
89
|
+
install: () => () => import("./chunk-5PUQT3YS.mjs"),
|
|
90
90
|
hook: (name) => Object.hasOwn(hooks, name) ? hooks[name] : void 0
|
|
91
91
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "khala-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Khala channels for Claude Code, Codex and Cursor sessions: the khala MCP server, delivery hooks and local channels.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/aiur-team/khala/tree/main/packages/agent#readme",
|
package/dist/chunk-5RGEO5HN.mjs
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
SESSION_ID_PATTERN,
|
|
4
|
-
ensureStateDir,
|
|
5
|
-
readJson,
|
|
6
|
-
stateRoot,
|
|
7
|
-
writeJsonAtomic
|
|
8
|
-
} from "./chunk-WNOIHCCC.mjs";
|
|
9
|
-
|
|
10
|
-
// src/harness/session-sources.ts
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
|
|
13
|
-
// src/harness/proc.ts
|
|
14
|
-
import { execFile } from "node:child_process";
|
|
15
|
-
import { readFile } from "node:fs/promises";
|
|
16
|
-
import { promisify } from "node:util";
|
|
17
|
-
var execFileAsync = promisify(execFile);
|
|
18
|
-
var validPid = (pid) => Number.isSafeInteger(pid) && pid > 0;
|
|
19
|
-
function startToken(value) {
|
|
20
|
-
return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
21
|
-
}
|
|
22
|
-
function parseLinuxStat(pid, text) {
|
|
23
|
-
const open = text.indexOf("(");
|
|
24
|
-
const close = text.lastIndexOf(")");
|
|
25
|
-
if (!validPid(pid) || open < 0 || close < open || Number(text.slice(0, open).trim()) !== pid) return null;
|
|
26
|
-
const fields = text.slice(close + 1).trim().split(/\s+/);
|
|
27
|
-
const ppid = Number(fields[1]);
|
|
28
|
-
const startTime = fields[19];
|
|
29
|
-
if (!Number.isSafeInteger(ppid) || ppid < 0 || !startTime || !/^\d+$/.test(startTime)) return null;
|
|
30
|
-
return { pid, ppid, startTime, command: text.slice(open + 1, close) };
|
|
31
|
-
}
|
|
32
|
-
function parseDarwinPs(pid, text) {
|
|
33
|
-
const match = text.trim().match(/^(\d+)\s+([A-Za-z]{3}\s+[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s+(.+)$/);
|
|
34
|
-
if (!validPid(pid) || !match) return null;
|
|
35
|
-
return { pid, ppid: Number(match[1]), startTime: startToken(match[2]), command: match[3] };
|
|
36
|
-
}
|
|
37
|
-
function csvFields(line) {
|
|
38
|
-
const fields = [];
|
|
39
|
-
let value = "";
|
|
40
|
-
let quoted = false;
|
|
41
|
-
for (let i = 0; i < line.length; i++) {
|
|
42
|
-
const char = line[i];
|
|
43
|
-
if (char === '"') {
|
|
44
|
-
if (quoted && line[i + 1] === '"') {
|
|
45
|
-
value += '"';
|
|
46
|
-
i++;
|
|
47
|
-
} else quoted = !quoted;
|
|
48
|
-
} else if (char === "," && !quoted) {
|
|
49
|
-
fields.push(value);
|
|
50
|
-
value = "";
|
|
51
|
-
} else value += char;
|
|
52
|
-
}
|
|
53
|
-
if (quoted) return null;
|
|
54
|
-
fields.push(value);
|
|
55
|
-
return fields;
|
|
56
|
-
}
|
|
57
|
-
function parseWindowsCsv(text, pid) {
|
|
58
|
-
if (!validPid(pid)) return null;
|
|
59
|
-
const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/).filter((line) => line.trim() && !line.startsWith("#"));
|
|
60
|
-
const headers = csvFields(lines[0] ?? "");
|
|
61
|
-
if (!headers) return null;
|
|
62
|
-
for (const line of lines.slice(1)) {
|
|
63
|
-
const values = csvFields(line);
|
|
64
|
-
if (!values) continue;
|
|
65
|
-
const field = (name) => values[headers.indexOf(name)] ?? "";
|
|
66
|
-
if (Number(field("ProcessId")) !== pid) continue;
|
|
67
|
-
const ppid = Number(field("ParentProcessId"));
|
|
68
|
-
const startTime = startToken(field("CreationDate"));
|
|
69
|
-
const command = field("Name");
|
|
70
|
-
if (!field("ParentProcessId") || !Number.isSafeInteger(ppid) || ppid < 0 || !startTime || !command) return null;
|
|
71
|
-
return { pid, ppid, startTime, command };
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
async function readProcess(pid) {
|
|
76
|
-
if (!validPid(pid)) return null;
|
|
77
|
-
try {
|
|
78
|
-
if (process.platform === "linux") return parseLinuxStat(pid, await readFile(`/proc/${pid}/stat`, "utf8"));
|
|
79
|
-
if (process.platform === "darwin") {
|
|
80
|
-
const { stdout } = await execFileAsync("ps", ["-o", "ppid=,lstart=,comm=", "-p", String(pid)], { env: { ...process.env, LC_ALL: "C" }, timeout: 5e3 });
|
|
81
|
-
return parseDarwinPs(pid, stdout);
|
|
82
|
-
}
|
|
83
|
-
if (process.platform === "win32") {
|
|
84
|
-
const script = `Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' | Select-Object ProcessId,ParentProcessId,@{Name='CreationDate';Expression={$_.CreationDate.ToUniversalTime().ToString('o')}},Name | ConvertTo-Csv -NoTypeInformation`;
|
|
85
|
-
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: 5e3 });
|
|
86
|
-
return parseWindowsCsv(stdout, pid);
|
|
87
|
-
}
|
|
88
|
-
} catch {
|
|
89
|
-
}
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
async function* walkAncestors(pid, read = readProcess) {
|
|
93
|
-
const seen = /* @__PURE__ */ new Set([pid]);
|
|
94
|
-
let current = await read(pid);
|
|
95
|
-
while (current && validPid(current.ppid) && !seen.has(current.ppid)) {
|
|
96
|
-
seen.add(current.ppid);
|
|
97
|
-
current = await read(current.ppid);
|
|
98
|
-
if (current) yield current;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async function nearestNonShellAncestor(pid, read = readProcess) {
|
|
102
|
-
for await (const ancestor of walkAncestors(pid, read)) {
|
|
103
|
-
const name = ancestor.command.split(/[\\/]/).pop()?.replace(/^-/, "").toLowerCase() ?? "";
|
|
104
|
-
if (!/^(?:sh|bash|dash|zsh|fish|ksh|csh|tcsh|ash|cmd(?:\.exe)?|powershell(?:\.exe)?|pwsh(?:\.exe)?)$/.test(name)) return ancestor;
|
|
105
|
-
}
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/harness/session-sources.ts
|
|
110
|
-
var SESSION_UNKNOWN_HINT = "Send the agent one message first, then retry.";
|
|
111
|
-
var mappingDir = (harness, env) => path.join(stateRoot(env), harness, ".by-pid");
|
|
112
|
-
async function recordHookSession(harness, sessionId, env, options = {}) {
|
|
113
|
-
if (!SESSION_ID_PATTERN.test(sessionId)) return;
|
|
114
|
-
const parent = await nearestNonShellAncestor(options.pid ?? process.pid, options.readProcess ?? readProcess);
|
|
115
|
-
if (!parent) return;
|
|
116
|
-
const dir = mappingDir(harness, env);
|
|
117
|
-
await ensureStateDir(dir);
|
|
118
|
-
const file = path.join(dir, `${parent.pid}.json`);
|
|
119
|
-
const at = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
120
|
-
const previous = await readJson(file);
|
|
121
|
-
if (previous?.startTime === parent.startTime && typeof previous.at === "string" && previous.at > at) return;
|
|
122
|
-
await writeJsonAtomic(file, {
|
|
123
|
-
sessionId,
|
|
124
|
-
startTime: parent.startTime,
|
|
125
|
-
at,
|
|
126
|
-
...options.workspace !== void 0 ? { workspace: options.workspace } : {}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
async function resolveSources(sources, meta, env, context) {
|
|
130
|
-
for (const source of sources) {
|
|
131
|
-
const value = await source.resolve(meta, env, context);
|
|
132
|
-
if (value === void 0 || value === null) continue;
|
|
133
|
-
return typeof value === "string" && SESSION_ID_PATTERN.test(value) ? { sessionId: value, rejoinable: source.rejoinable(value) } : null;
|
|
134
|
-
}
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export {
|
|
139
|
-
SESSION_UNKNOWN_HINT,
|
|
140
|
-
recordHookSession,
|
|
141
|
-
resolveSources
|
|
142
|
-
};
|