khala-cli 0.4.18 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-H2X64ISL.mjs → chunk-3FE4RPWL.mjs} +3 -3
- package/dist/{chunk-43THPTMD.mjs → chunk-3QGEQ5O3.mjs} +7 -7
- package/dist/{chunk-QYVSNP2P.mjs → chunk-3T4IW4UK.mjs} +1 -1
- package/dist/{chunk-RWT4I2XJ.mjs → chunk-5LYU3EBH.mjs} +23 -25
- package/dist/{chunk-3RCRNCG3.mjs → chunk-5RVB6IGF.mjs} +140 -5
- package/dist/{chunk-RJZ6FAFC.mjs → chunk-67OAHOGN.mjs} +3 -3
- package/dist/{chunk-KCWHDC7S.mjs → chunk-7W2T2NMP.mjs} +8 -10
- package/dist/{chunk-4WHRO5ZU.mjs → chunk-7XMZMPOS.mjs} +9 -7
- package/dist/{chunk-HOKMRMNP.mjs → chunk-7ZXUEYBO.mjs} +2 -2
- package/dist/{chunk-SRUPGV2G.mjs → chunk-B6XLPIBP.mjs} +11 -13
- package/dist/{chunk-UA6DEWBA.mjs → chunk-BLFHQ3UR.mjs} +1 -1
- package/dist/{chunk-VK7ZSKQZ.mjs → chunk-DXN26TW6.mjs} +8 -7
- package/dist/{chunk-OZ5C2ZMB.mjs → chunk-EPDG2YED.mjs} +10 -12
- package/dist/chunk-ETDBDXF7.mjs +17 -0
- package/dist/{chunk-BG4JGKQG.mjs → chunk-G5LBKJ6U.mjs} +2 -2
- package/dist/{chunk-XXKDMHR2.mjs → chunk-ISDL5A5F.mjs} +2 -2
- package/dist/{chunk-JXV5S7FC.mjs → chunk-JRYM36PH.mjs} +433 -129
- package/dist/{chunk-IESMCVXV.mjs → chunk-JVAABYLJ.mjs} +1 -1
- package/dist/chunk-M6ALNS6W.mjs +72 -0
- package/dist/chunk-MM3IW62G.mjs +18 -0
- package/dist/{chunk-YAXWRS5U.mjs → chunk-OFRG3VRT.mjs} +2 -2
- package/dist/{chunk-TYRNBET6.mjs → chunk-OTCVNFPR.mjs} +2 -2
- package/dist/chunk-PVNW7ZNL.mjs +25 -0
- package/dist/{chunk-FDSHLUCQ.mjs → chunk-QRZ6OXXP.mjs} +44 -22
- package/dist/{chunk-FCOL5HBH.mjs → chunk-SPKLSGNX.mjs} +10 -12
- package/dist/{chunk-6ZL6OFW4.mjs → chunk-SZY4CAHX.mjs} +5 -5
- package/dist/{chunk-5IZ3WN3S.mjs → chunk-TFWEBW7T.mjs} +57 -55
- package/dist/{chunk-ELI3YCFF.mjs → chunk-TRLHWJAV.mjs} +19 -81
- package/dist/{chunk-LOGBYBPA.mjs → chunk-UEGSIZ4N.mjs} +1 -1
- package/dist/{chunk-4IOLQB34.mjs → chunk-VTEE4QUB.mjs} +1 -1
- package/dist/{chunk-GVIPYERE.mjs → chunk-WM3RISV5.mjs} +3 -3
- package/dist/{chunk-WM75XLND.mjs → chunk-XXEYRJSF.mjs} +6 -6
- package/dist/{chunk-MTDLNIUY.mjs → chunk-ZYGP76PU.mjs} +1 -1
- package/dist/khala.mjs +11 -11
- package/dist/web/assets/{local-3eMi9596.js → local-SRORuW7r.js} +2 -2
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/chunk-4FUYRD3G.mjs +0 -27
- package/dist/chunk-AU2FBBUY.mjs +0 -20
- package/dist/chunk-EBKZETYL.mjs +0 -142
- package/dist/chunk-KXLGRISD.mjs +0 -19
- package/dist/chunk-LNDZI73C.mjs +0 -323
package/dist/web/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32-B9Ubw1Dx.png" />
|
|
11
11
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16-D5waNSZD.png" />
|
|
12
12
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-Du9V6qit.png" />
|
|
13
|
-
<script type="module" crossorigin src="/assets/local-
|
|
13
|
+
<script type="module" crossorigin src="/assets/local-SRORuW7r.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/local-NETsM4LU.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "khala-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.20",
|
|
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-4FUYRD3G.mjs
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
WAKE_USAGE,
|
|
4
|
-
cli_default,
|
|
5
|
-
consentLine,
|
|
6
|
-
runWake,
|
|
7
|
-
sessionIds,
|
|
8
|
-
setWake
|
|
9
|
-
} from "./chunk-WM75XLND.mjs";
|
|
10
|
-
import "./chunk-VK7ZSKQZ.mjs";
|
|
11
|
-
import "./chunk-RWT4I2XJ.mjs";
|
|
12
|
-
import "./chunk-HOKMRMNP.mjs";
|
|
13
|
-
import "./chunk-JXV5S7FC.mjs";
|
|
14
|
-
import "./chunk-EBKZETYL.mjs";
|
|
15
|
-
import "./chunk-LNDZI73C.mjs";
|
|
16
|
-
import "./chunk-3RCRNCG3.mjs";
|
|
17
|
-
import "./chunk-4IOLQB34.mjs";
|
|
18
|
-
import "./chunk-LOGBYBPA.mjs";
|
|
19
|
-
import "./chunk-QYVSNP2P.mjs";
|
|
20
|
-
export {
|
|
21
|
-
WAKE_USAGE,
|
|
22
|
-
consentLine,
|
|
23
|
-
cli_default as default,
|
|
24
|
-
runWake,
|
|
25
|
-
sessionIds,
|
|
26
|
-
setWake
|
|
27
|
-
};
|
package/dist/chunk-AU2FBBUY.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
resolveHarness,
|
|
4
|
-
resolveSession,
|
|
5
|
-
resolveSessionId
|
|
6
|
-
} from "./chunk-BG4JGKQG.mjs";
|
|
7
|
-
import "./chunk-RWT4I2XJ.mjs";
|
|
8
|
-
import "./chunk-HOKMRMNP.mjs";
|
|
9
|
-
import "./chunk-JXV5S7FC.mjs";
|
|
10
|
-
import "./chunk-EBKZETYL.mjs";
|
|
11
|
-
import "./chunk-LNDZI73C.mjs";
|
|
12
|
-
import "./chunk-3RCRNCG3.mjs";
|
|
13
|
-
import "./chunk-4IOLQB34.mjs";
|
|
14
|
-
import "./chunk-LOGBYBPA.mjs";
|
|
15
|
-
import "./chunk-QYVSNP2P.mjs";
|
|
16
|
-
export {
|
|
17
|
-
resolveHarness,
|
|
18
|
-
resolveSession,
|
|
19
|
-
resolveSessionId
|
|
20
|
-
};
|
package/dist/chunk-EBKZETYL.mjs
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
decodeWith,
|
|
4
|
-
fail,
|
|
5
|
-
literal,
|
|
6
|
-
object,
|
|
7
|
-
readMatrixUserId,
|
|
8
|
-
version
|
|
9
|
-
} from "./chunk-4IOLQB34.mjs";
|
|
10
|
-
import {
|
|
11
|
-
init_define_KHALA_BUNDLE
|
|
12
|
-
} from "./chunk-QYVSNP2P.mjs";
|
|
13
|
-
|
|
14
|
-
// ../contracts/src/m1/listening-mode.ts
|
|
15
|
-
init_define_KHALA_BUNDLE();
|
|
16
|
-
|
|
17
|
-
// ../listener/src/index.ts
|
|
18
|
-
init_define_KHALA_BUNDLE();
|
|
19
|
-
|
|
20
|
-
// ../listener/src/modes.ts
|
|
21
|
-
init_define_KHALA_BUNDLE();
|
|
22
|
-
var DEFAULT_LISTENING_MODE = "sync";
|
|
23
|
-
|
|
24
|
-
// ../listener/src/decision.ts
|
|
25
|
-
init_define_KHALA_BUNDLE();
|
|
26
|
-
function decideDelivery(input) {
|
|
27
|
-
if (input.event === "start") return { deliverModes: [], markIdleOnly: false };
|
|
28
|
-
if (input.event === "stop" && input.continuation) return { deliverModes: [], markIdleOnly: true };
|
|
29
|
-
const steerOnly = input.event === "tool" || input.event === "prompt" && input.harness === "claude" && input.busy && input.busyAgeMs !== null && input.busyAgeMs < 12e4 && !input.interruptedSinceBusy;
|
|
30
|
-
return { deliverModes: steerOnly ? ["steer"] : ["steer", "sync"], markIdleOnly: false };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ../listener/src/frame.ts
|
|
34
|
-
init_define_KHALA_BUNDLE();
|
|
35
|
-
var MAX_FRAME_BYTES = 64 * 1024;
|
|
36
|
-
var MAX_FRAME_MESSAGES = 50;
|
|
37
|
-
var encoder = new TextEncoder();
|
|
38
|
-
var utf8Length = (value) => encoder.encode(value).byteLength;
|
|
39
|
-
function selectFrame(entries, render, truncatedSuffix, budget = MAX_FRAME_BYTES, truncate = true) {
|
|
40
|
-
const rendered = [];
|
|
41
|
-
let consumedCount = 0;
|
|
42
|
-
for (let i = 0; i < entries.length; i++) {
|
|
43
|
-
const entry = entries[i];
|
|
44
|
-
if (rendered.length === MAX_FRAME_MESSAGES) break;
|
|
45
|
-
if (utf8Length(render([...rendered, entry])) > budget) {
|
|
46
|
-
if (rendered.length || !truncate) break;
|
|
47
|
-
const body = Array.from(entry.body);
|
|
48
|
-
let low = 0, high = body.length;
|
|
49
|
-
while (low < high) {
|
|
50
|
-
const middle = Math.ceil((low + high) / 2);
|
|
51
|
-
const candidate = { ...entry, body: body.slice(0, middle).join("") + truncatedSuffix };
|
|
52
|
-
if (utf8Length(render([candidate])) <= budget) low = middle;
|
|
53
|
-
else high = middle - 1;
|
|
54
|
-
}
|
|
55
|
-
const truncated = { ...entry, body: body.slice(0, low).join("") + truncatedSuffix };
|
|
56
|
-
if (utf8Length(render([truncated])) > budget) throw new Error("frame_metadata_too_large");
|
|
57
|
-
rendered.push(truncated);
|
|
58
|
-
consumedCount = i + 1;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
rendered.push(entry);
|
|
62
|
-
consumedCount = i + 1;
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
frame: rendered.length ? render(rendered) : null,
|
|
66
|
-
consumed: entries.slice(0, consumedCount)
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ../listener/src/codecs/claude-style.ts
|
|
71
|
-
init_define_KHALA_BUNDLE();
|
|
72
|
-
var events = { SessionStart: "start", UserPromptSubmit: "prompt", PostToolUse: "tool", Stop: "stop", StopFailure: "limit" };
|
|
73
|
-
var LIMIT_ERRORS = ["rate_limit", "billing_error"];
|
|
74
|
-
var names = { prompt: "UserPromptSubmit", tool: "PostToolUse", stop: "Stop" };
|
|
75
|
-
function createClaudeStyleCodec(stopNudge) {
|
|
76
|
-
return {
|
|
77
|
-
promptDeliversWithoutWake: true,
|
|
78
|
-
promptAcceptsContext: true,
|
|
79
|
-
suppressOutputErrors: false,
|
|
80
|
-
parse(stdin) {
|
|
81
|
-
try {
|
|
82
|
-
const input = JSON.parse(stdin);
|
|
83
|
-
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;
|
|
84
|
-
return {
|
|
85
|
-
sessionId: input.session_id,
|
|
86
|
-
event: events[input.hook_event_name],
|
|
87
|
-
continuation: input.stop_hook_active === true,
|
|
88
|
-
...typeof input.cwd === "string" ? { workspace: input.cwd } : {},
|
|
89
|
-
...typeof input.transcript_path === "string" ? { transcriptPath: input.transcript_path } : {},
|
|
90
|
-
...typeof input.prompt === "string" ? { promptText: input.prompt } : {}
|
|
91
|
-
};
|
|
92
|
-
} catch {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
noop: () => "",
|
|
97
|
-
render(kind, frame, stopContext = false) {
|
|
98
|
-
return JSON.stringify(kind === "stop" && !stopContext ? { decision: "block", reason: stopNudge } : { hookSpecificOutput: { hookEventName: names[kind], additionalContext: frame } }) + "\n";
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ../contracts/src/m1/listening-mode.ts
|
|
104
|
-
var LISTENING_MODES = ["steer", "sync", "async", "mute"];
|
|
105
|
-
function decodeListeningMode(input) {
|
|
106
|
-
return decodeWith(() => literal(input, "", LISTENING_MODES));
|
|
107
|
-
}
|
|
108
|
-
var LISTENING_MODE_COMMAND_TYPE = "com.khala.listening_mode.v1";
|
|
109
|
-
var LISTENING_MODE_MEMBER_KEY = "com.khala.listening_mode";
|
|
110
|
-
function decodeListeningModeCommand(input) {
|
|
111
|
-
return decodeWith(() => {
|
|
112
|
-
const hasLock = typeof input === "object" && input !== null && Object.hasOwn(input, "locked");
|
|
113
|
-
const r = object(input, "", hasLock ? ["v", "agent", "mode", "locked"] : ["v", "agent", "mode"]);
|
|
114
|
-
if (hasLock && typeof r.field("locked") !== "boolean") fail(r.at("locked"), "wrong_type");
|
|
115
|
-
return {
|
|
116
|
-
v: version(r.field("v"), r.at("v")),
|
|
117
|
-
agent: readMatrixUserId(r.field("agent"), r.at("agent")),
|
|
118
|
-
mode: literal(r.field("mode"), r.at("mode"), LISTENING_MODES),
|
|
119
|
-
...hasLock ? { locked: r.field("locked") } : {}
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
function memberListeningMode(content) {
|
|
124
|
-
const value = typeof content === "object" && content !== null ? content[LISTENING_MODE_MEMBER_KEY] : void 0;
|
|
125
|
-
if (value === void 0) return DEFAULT_LISTENING_MODE;
|
|
126
|
-
const decoded = decodeListeningMode(value);
|
|
127
|
-
return decoded.ok ? decoded.value : "async";
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export {
|
|
131
|
-
DEFAULT_LISTENING_MODE,
|
|
132
|
-
decideDelivery,
|
|
133
|
-
MAX_FRAME_BYTES,
|
|
134
|
-
selectFrame,
|
|
135
|
-
createClaudeStyleCodec,
|
|
136
|
-
LISTENING_MODES,
|
|
137
|
-
decodeListeningMode,
|
|
138
|
-
LISTENING_MODE_COMMAND_TYPE,
|
|
139
|
-
LISTENING_MODE_MEMBER_KEY,
|
|
140
|
-
decodeListeningModeCommand,
|
|
141
|
-
memberListeningMode
|
|
142
|
-
};
|
package/dist/chunk-KXLGRISD.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
ADAPTERS,
|
|
4
|
-
adapterFor,
|
|
5
|
-
createAdapterRegistry
|
|
6
|
-
} from "./chunk-RWT4I2XJ.mjs";
|
|
7
|
-
import "./chunk-HOKMRMNP.mjs";
|
|
8
|
-
import "./chunk-JXV5S7FC.mjs";
|
|
9
|
-
import "./chunk-EBKZETYL.mjs";
|
|
10
|
-
import "./chunk-LNDZI73C.mjs";
|
|
11
|
-
import "./chunk-3RCRNCG3.mjs";
|
|
12
|
-
import "./chunk-4IOLQB34.mjs";
|
|
13
|
-
import "./chunk-LOGBYBPA.mjs";
|
|
14
|
-
import "./chunk-QYVSNP2P.mjs";
|
|
15
|
-
export {
|
|
16
|
-
ADAPTERS,
|
|
17
|
-
adapterFor,
|
|
18
|
-
createAdapterRegistry
|
|
19
|
-
};
|
package/dist/chunk-LNDZI73C.mjs
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
decodeChannelEvent,
|
|
4
|
-
formatChannelEventLine
|
|
5
|
-
} from "./chunk-3RCRNCG3.mjs";
|
|
6
|
-
import {
|
|
7
|
-
StateError,
|
|
8
|
-
channelFiles,
|
|
9
|
-
channelsDir,
|
|
10
|
-
ensureStateDir,
|
|
11
|
-
filesForDir,
|
|
12
|
-
readJson,
|
|
13
|
-
removeStateFile,
|
|
14
|
-
stateKey,
|
|
15
|
-
writeJsonAtomic,
|
|
16
|
-
writeStateFile
|
|
17
|
-
} from "./chunk-4IOLQB34.mjs";
|
|
18
|
-
import {
|
|
19
|
-
init_define_KHALA_BUNDLE
|
|
20
|
-
} from "./chunk-QYVSNP2P.mjs";
|
|
21
|
-
|
|
22
|
-
// src/events/receive.ts
|
|
23
|
-
init_define_KHALA_BUNDLE();
|
|
24
|
-
function toEventInboxEntry(base, rawContent, you) {
|
|
25
|
-
const decoded = decodeChannelEvent(rawContent);
|
|
26
|
-
if (!decoded.ok) return null;
|
|
27
|
-
const content = decoded.value;
|
|
28
|
-
const to = content.kind === "handoff" ? String(content.data?.to ?? "").replace(/^@/u, "").toLowerCase() : "";
|
|
29
|
-
return {
|
|
30
|
-
entry: {
|
|
31
|
-
...base,
|
|
32
|
-
kind: "event",
|
|
33
|
-
body: formatChannelEventLine(content) + (content.url ? " " + content.url : ""),
|
|
34
|
-
eventKind: content.kind,
|
|
35
|
-
...content.data ? { data: content.data } : {},
|
|
36
|
-
...to && to === you?.toLowerCase() ? { mention: true } : {}
|
|
37
|
-
},
|
|
38
|
-
...content.key !== void 0 ? { key: content.key } : {}
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function createEventKeyFilter() {
|
|
42
|
-
const seen = /* @__PURE__ */ new Set();
|
|
43
|
-
return (key) => {
|
|
44
|
-
if (key === void 0) return true;
|
|
45
|
-
if (seen.has(key)) return false;
|
|
46
|
-
seen.add(key);
|
|
47
|
-
return true;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function isWakeEntry(entry) {
|
|
51
|
-
return entry.kind === "message" || entry.mention === true;
|
|
52
|
-
}
|
|
53
|
-
var agentGroupMention = /(?<![\p{L}\p{N}_@])@(?:channel|agents)(?![\p{L}\p{N}_])/iu;
|
|
54
|
-
function mentions(entry, name) {
|
|
55
|
-
if (entry.kind === "message" && entry.senderKind !== "agent" && agentGroupMention.test(entry.body)) return true;
|
|
56
|
-
if (!name) return false;
|
|
57
|
-
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
58
|
-
return new RegExp(`(?<![\\p{L}\\p{N}_])@?${escaped}(?![\\p{L}\\p{N}_])`, "iu").test(entry.body);
|
|
59
|
-
}
|
|
60
|
-
function wakesAgent(entry, mode, you) {
|
|
61
|
-
if (mode === "mute" || !isWakeEntry(entry)) return false;
|
|
62
|
-
const pinged = entry.mention === true || mentions(entry, you);
|
|
63
|
-
return mode === "async" ? pinged : mode !== "sync" || entry.senderKind !== "agent" || pinged;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// src/inbox.ts
|
|
67
|
-
init_define_KHALA_BUNDLE();
|
|
68
|
-
import * as fs from "node:fs/promises";
|
|
69
|
-
async function readEntries(files) {
|
|
70
|
-
let content;
|
|
71
|
-
try {
|
|
72
|
-
content = await fs.readFile(files.inbox, "utf8");
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (error.code === "ENOENT") return [];
|
|
75
|
-
throw new StateError("storage_failed");
|
|
76
|
-
}
|
|
77
|
-
const entries = [];
|
|
78
|
-
for (const line of content.split("\n").slice(0, -1)) {
|
|
79
|
-
try {
|
|
80
|
-
const entry = JSON.parse(line);
|
|
81
|
-
if (entry && ["eventId", "roomId", "ts", "sender", "senderLabel", "body"].every((key) => typeof entry[key] === "string") && ["human", "agent", "unknown"].includes(entry.senderKind) && ["message", "event"].includes(entry.kind)) entries.push(entry);
|
|
82
|
-
} catch {
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return entries;
|
|
86
|
-
}
|
|
87
|
-
async function appendEntries(files, entries) {
|
|
88
|
-
const seen = new Set((await readEntries(files)).map((entry) => entry.eventId));
|
|
89
|
-
const fresh = entries.filter((entry) => {
|
|
90
|
-
if (seen.has(entry.eventId)) return false;
|
|
91
|
-
seen.add(entry.eventId);
|
|
92
|
-
return true;
|
|
93
|
-
});
|
|
94
|
-
if (fresh.length) {
|
|
95
|
-
try {
|
|
96
|
-
await fs.appendFile(files.inbox, fresh.map((entry) => JSON.stringify(entry) + "\n").join(""), { mode: 384, flag: "a" });
|
|
97
|
-
} catch {
|
|
98
|
-
throw new StateError("storage_failed");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return fresh;
|
|
102
|
-
}
|
|
103
|
-
async function readCursor(files) {
|
|
104
|
-
const cursor = await readJson(files.cursor);
|
|
105
|
-
return cursor && (cursor.lastDeliveredEventId === null || typeof cursor.lastDeliveredEventId === "string") && Number.isSafeInteger(cursor.deliveredCount) && cursor.deliveredCount >= 0 ? cursor : { lastDeliveredEventId: null, deliveredCount: 0 };
|
|
106
|
-
}
|
|
107
|
-
async function unread(files) {
|
|
108
|
-
const cursor = await readCursor(files);
|
|
109
|
-
const entries = await readEntries(files);
|
|
110
|
-
return { entries: entries.slice(cursor.deliveredCount), cursor };
|
|
111
|
-
}
|
|
112
|
-
function wakeStart(cursor) {
|
|
113
|
-
return Math.max(cursor.deliveredCount, Number.isSafeInteger(cursor.nudgedCount) ? cursor.nudgedCount : 0);
|
|
114
|
-
}
|
|
115
|
-
async function unwoken(files) {
|
|
116
|
-
return (await readEntries(files)).slice(wakeStart(await readCursor(files)));
|
|
117
|
-
}
|
|
118
|
-
async function markNudged(files, expected, count) {
|
|
119
|
-
const current = await readCursor(files);
|
|
120
|
-
if (current.deliveredCount !== expected.deliveredCount || current.lastDeliveredEventId !== expected.lastDeliveredEventId) return;
|
|
121
|
-
await writeJsonAtomic(files.cursor, { ...current, nudgedCount: count });
|
|
122
|
-
}
|
|
123
|
-
async function unreadCounts(files) {
|
|
124
|
-
const { entries } = await unread(files);
|
|
125
|
-
return { total: entries.length, messages: entries.filter((entry) => isWakeEntry(entry)).length };
|
|
126
|
-
}
|
|
127
|
-
async function advanceCursor(files, expected, delivered, shown = false) {
|
|
128
|
-
const current = await readCursor(files);
|
|
129
|
-
if (current.deliveredCount !== expected.deliveredCount || current.lastDeliveredEventId !== expected.lastDeliveredEventId) return "conflict";
|
|
130
|
-
if (!delivered.length) return expected;
|
|
131
|
-
const entries = await readEntries(files);
|
|
132
|
-
const slice = entries.slice(expected.deliveredCount, expected.deliveredCount + delivered.length);
|
|
133
|
-
if (slice.length !== delivered.length || slice.some((entry, index) => entry.eventId !== delivered[index]?.eventId)) return "conflict";
|
|
134
|
-
const last = delivered[delivered.length - 1].eventId;
|
|
135
|
-
const shownEventId = shown ? last : current.shownEventId;
|
|
136
|
-
const next = {
|
|
137
|
-
lastDeliveredEventId: last,
|
|
138
|
-
deliveredCount: expected.deliveredCount + delivered.length,
|
|
139
|
-
...shownEventId ? { shownEventId } : {},
|
|
140
|
-
...wakeStart(current) > expected.deliveredCount + delivered.length ? { nudgedCount: current.nudgedCount } : {}
|
|
141
|
-
};
|
|
142
|
-
await writeJsonAtomic(files.cursor, next);
|
|
143
|
-
return next;
|
|
144
|
-
}
|
|
145
|
-
async function appendInbox(dir, entry) {
|
|
146
|
-
return (await appendEntries(filesForDir(dir), [entry])).length === 1;
|
|
147
|
-
}
|
|
148
|
-
function unreadCount(dir) {
|
|
149
|
-
return unreadCounts(filesForDir(dir));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// src/channels.ts
|
|
153
|
-
init_define_KHALA_BUNDLE();
|
|
154
|
-
import * as fs2 from "node:fs/promises";
|
|
155
|
-
import path from "node:path";
|
|
156
|
-
function channelKey(roomId2) {
|
|
157
|
-
return stateKey(roomId2);
|
|
158
|
-
}
|
|
159
|
-
async function exists(file) {
|
|
160
|
-
try {
|
|
161
|
-
await fs2.lstat(file);
|
|
162
|
-
return true;
|
|
163
|
-
} catch (error) {
|
|
164
|
-
if (error.code === "ENOENT") return false;
|
|
165
|
-
throw new StateError("storage_failed");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function roomId(value) {
|
|
169
|
-
if (value && typeof value === "object" && "roomId" in value && typeof value.roomId === "string" && value.roomId.length) return value.roomId;
|
|
170
|
-
return void 0;
|
|
171
|
-
}
|
|
172
|
-
async function legacyRoomId(files) {
|
|
173
|
-
const saved = roomId(await readJson(files.session));
|
|
174
|
-
if (saved) return saved;
|
|
175
|
-
let content;
|
|
176
|
-
try {
|
|
177
|
-
content = await fs2.readFile(files.inbox, "utf8");
|
|
178
|
-
} catch (error) {
|
|
179
|
-
if (error.code === "ENOENT") return void 0;
|
|
180
|
-
throw new StateError("storage_failed");
|
|
181
|
-
}
|
|
182
|
-
try {
|
|
183
|
-
return roomId(JSON.parse(content.split("\n")[0]));
|
|
184
|
-
} catch {
|
|
185
|
-
return void 0;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
async function listChannels(files) {
|
|
189
|
-
await ensureStateDir(files.dir);
|
|
190
|
-
const channels = [];
|
|
191
|
-
const dir = channelsDir(files);
|
|
192
|
-
if (await exists(dir)) {
|
|
193
|
-
await ensureStateDir(dir);
|
|
194
|
-
for (const key of await fs2.readdir(dir)) {
|
|
195
|
-
if (!/^[a-f0-9]{24}$/.test(key)) continue;
|
|
196
|
-
const nested = filesForDir(path.join(dir, key));
|
|
197
|
-
await ensureStateDir(nested.dir);
|
|
198
|
-
const metadata = await readJson(path.join(nested.dir, "channel.json"));
|
|
199
|
-
const id = roomId(metadata);
|
|
200
|
-
if (!id || channelKey(id) !== key) continue;
|
|
201
|
-
channels.push({ key, roomId: id, ...typeof metadata?.channelName === "string" ? { channelName: metadata.channelName } : {}, files: nested, legacy: false });
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (await exists(files.inbox)) {
|
|
205
|
-
const id = await legacyRoomId(files);
|
|
206
|
-
if (id) {
|
|
207
|
-
const status = await readJson(files.status);
|
|
208
|
-
channels.push({ key: channelKey(id), roomId: id, ...typeof status?.channelName === "string" ? { channelName: status.channelName } : {}, files, legacy: true });
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return channels.sort((a, b) => (a.channelName ?? "").localeCompare(b.channelName ?? "") || a.roomId.localeCompare(b.roomId));
|
|
212
|
-
}
|
|
213
|
-
async function migrateLegacy(files) {
|
|
214
|
-
await ensureStateDir(files.dir);
|
|
215
|
-
const names = ["cursor.json", "mode.json", "inbox.jsonl"];
|
|
216
|
-
const present = await Promise.all(names.map((name) => exists(path.join(files.dir, name))));
|
|
217
|
-
if (!present.some(Boolean)) return "none";
|
|
218
|
-
const id = await legacyRoomId(files);
|
|
219
|
-
if (!id) {
|
|
220
|
-
for (const name of names) await removeStateFile(files.dir, name);
|
|
221
|
-
return "discarded";
|
|
222
|
-
}
|
|
223
|
-
const nested = channelFiles(files, id);
|
|
224
|
-
await ensureStateDir(nested.dir);
|
|
225
|
-
const destinationInbox = await exists(nested.inbox);
|
|
226
|
-
if (!destinationInbox && present[0] && await exists(nested.cursor)) {
|
|
227
|
-
const [legacyCursor, channelCursor] = await Promise.all([readCursor(files), readCursor(nested)]);
|
|
228
|
-
if (legacyCursor.deliveredCount !== channelCursor.deliveredCount || legacyCursor.lastDeliveredEventId !== channelCursor.lastDeliveredEventId) {
|
|
229
|
-
const safe = legacyCursor.deliveredCount < channelCursor.deliveredCount ? legacyCursor : channelCursor;
|
|
230
|
-
await writeStateFile(nested.dir, "cursor.json", safe);
|
|
231
|
-
console.warn(`Khala cursor migration: conflicting cursors for ${id}; using earlier position ${safe.deliveredCount} to avoid skipping messages (legacy=${legacyCursor.deliveredCount}, channel=${channelCursor.deliveredCount}).`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const status = await readJson(files.status);
|
|
235
|
-
const metadata = await readJson(path.join(nested.dir, "channel.json"));
|
|
236
|
-
if (!metadata || roomId(metadata) !== id) {
|
|
237
|
-
await writeStateFile(nested.dir, "channel.json", {
|
|
238
|
-
roomId: id,
|
|
239
|
-
...typeof status?.channelName === "string" ? { channelName: status.channelName } : {},
|
|
240
|
-
joinedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
if (destinationInbox) {
|
|
244
|
-
if (present[2]) await appendEntries(nested, (await unread(files)).entries);
|
|
245
|
-
for (const name of ["inbox.jsonl", "cursor.json", "mode.json"]) await removeStateFile(files.dir, name);
|
|
246
|
-
return "moved";
|
|
247
|
-
}
|
|
248
|
-
for (const name of names) {
|
|
249
|
-
if (await exists(path.join(nested.dir, name))) {
|
|
250
|
-
await removeStateFile(files.dir, name);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
try {
|
|
254
|
-
await fs2.rename(path.join(files.dir, name), path.join(nested.dir, name));
|
|
255
|
-
} catch (error) {
|
|
256
|
-
if (error.code !== "ENOENT") throw new StateError("storage_failed");
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return "moved";
|
|
260
|
-
}
|
|
261
|
-
function resolveChannel(list, ref) {
|
|
262
|
-
const channels = list.map((item) => ({ channel: item.channelName ?? item.roomId, roomId: item.roomId }));
|
|
263
|
-
if (ref === void 0) {
|
|
264
|
-
if (list.length === 1) return { ok: true, channel: list[0] };
|
|
265
|
-
return { ok: false, code: list.length ? "channel_required" : "channel_unknown", channels };
|
|
266
|
-
}
|
|
267
|
-
const exact = list.find((item) => item.roomId === ref);
|
|
268
|
-
if (exact) return { ok: true, channel: exact };
|
|
269
|
-
const normalized = ref.replace(/^#/, "").toLowerCase();
|
|
270
|
-
const matches = list.filter((item) => item.channelName?.replace(/^#/, "").toLowerCase() === normalized);
|
|
271
|
-
if (matches.length === 1) return { ok: true, channel: matches[0] };
|
|
272
|
-
return { ok: false, code: matches.length ? "channel_ambiguous" : "channel_unknown", channels };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// src/activity.ts
|
|
276
|
-
init_define_KHALA_BUNDLE();
|
|
277
|
-
import * as fs3 from "node:fs/promises";
|
|
278
|
-
import path2 from "node:path";
|
|
279
|
-
async function readActivity(files) {
|
|
280
|
-
const activity = await readJson(path2.join(files.dir, "activity.json"));
|
|
281
|
-
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() };
|
|
282
|
-
}
|
|
283
|
-
var USAGE_LIMIT = "usage-limit.json";
|
|
284
|
-
async function markUsageLimit(files, now = () => /* @__PURE__ */ new Date()) {
|
|
285
|
-
await writeJsonAtomic(path2.join(files.dir, USAGE_LIMIT), { at: now().toISOString() });
|
|
286
|
-
}
|
|
287
|
-
async function clearUsageLimit(files) {
|
|
288
|
-
await fs3.rm(path2.join(files.dir, USAGE_LIMIT), { force: true });
|
|
289
|
-
}
|
|
290
|
-
async function usageLimited(dir) {
|
|
291
|
-
return await readJson(path2.join(dir, USAGE_LIMIT)) !== null;
|
|
292
|
-
}
|
|
293
|
-
async function writeActivity(files, state, now = () => /* @__PURE__ */ new Date()) {
|
|
294
|
-
const activity = { state, updatedAt: now().toISOString() };
|
|
295
|
-
await writeJsonAtomic(path2.join(files.dir, "activity.json"), activity);
|
|
296
|
-
return activity;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export {
|
|
300
|
-
toEventInboxEntry,
|
|
301
|
-
createEventKeyFilter,
|
|
302
|
-
isWakeEntry,
|
|
303
|
-
mentions,
|
|
304
|
-
wakesAgent,
|
|
305
|
-
readEntries,
|
|
306
|
-
readCursor,
|
|
307
|
-
unread,
|
|
308
|
-
wakeStart,
|
|
309
|
-
unwoken,
|
|
310
|
-
markNudged,
|
|
311
|
-
advanceCursor,
|
|
312
|
-
appendInbox,
|
|
313
|
-
unreadCount,
|
|
314
|
-
channelKey,
|
|
315
|
-
listChannels,
|
|
316
|
-
migrateLegacy,
|
|
317
|
-
resolveChannel,
|
|
318
|
-
readActivity,
|
|
319
|
-
markUsageLimit,
|
|
320
|
-
clearUsageLimit,
|
|
321
|
-
usageLimited,
|
|
322
|
-
writeActivity
|
|
323
|
-
};
|