khala-cli 0.4.2 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-T6RZE5IQ.mjs → chunk-6LC66VHK.mjs} +10 -7
- package/dist/chunk-7G2G4E4U.mjs +236 -0
- package/dist/chunk-AUJEUKVM.mjs +121 -0
- package/dist/{chunk-NUS6M27N.mjs → chunk-EW22K3JO.mjs} +445 -199
- package/dist/chunk-EXHCVFVH.mjs +26 -0
- package/dist/{chunk-CDHHFC6C.mjs → chunk-H46OG22B.mjs} +22 -6
- package/dist/{chunk-YP45XTOH.mjs → chunk-HB3UGIZG.mjs} +5 -5
- package/dist/chunk-IR3VN2ID.mjs +52 -0
- package/dist/{chunk-B5BFL7OD.mjs → chunk-KCJFSVDN.mjs} +55 -4
- package/dist/{chunk-K4MDSFHU.mjs → chunk-MBLP7LQJ.mjs} +103 -38
- package/dist/{chunk-RW3DXWNC.mjs → chunk-OLZ62X7S.mjs} +1 -1
- package/dist/chunk-P5CP32X2.mjs +170 -0
- package/dist/{chunk-A3LJU4JA.mjs → chunk-PXKGKVGW.mjs} +1 -1
- package/dist/{chunk-7MQAMABY.mjs → chunk-RSDOTTZQ.mjs} +4 -4
- package/dist/chunk-SN3STAO2.mjs +190 -0
- package/dist/{chunk-MHZWR4A3.mjs → chunk-SQXDATEC.mjs} +1 -1
- package/dist/chunk-T7YRVE43.mjs +238 -0
- package/dist/{chunk-BZ53MZEE.mjs → chunk-TZKEQIHP.mjs} +8 -4
- package/dist/{chunk-46PTKRZC.mjs → chunk-V4Y26O4R.mjs} +171 -113
- package/dist/chunk-V7M27QLT.mjs +21 -0
- package/dist/{chunk-2OO725IZ.mjs → chunk-Y2DRTRBJ.mjs} +3 -3
- package/dist/{chunk-JRZZVTQ4.mjs → chunk-Y4WVILEJ.mjs} +94 -10
- package/dist/khala.mjs +12 -10
- package/dist/web/assets/apple-touch-icon-Du9V6qit.png +0 -0
- package/dist/web/assets/favicon-16x16-D5waNSZD.png +0 -0
- package/dist/web/assets/favicon-32x32-B9Ubw1Dx.png +0 -0
- package/dist/web/assets/favicon-zCD0pKtT.ico +0 -0
- package/dist/web/assets/local-C1vz22ps.js +9 -0
- package/dist/web/assets/local-DdjwMERJ.css +1 -0
- package/dist/web/index.html +7 -2
- package/package.json +1 -1
- package/dist/chunk-N6LMHR7P.mjs +0 -455
- package/dist/chunk-U5ZMOPUK.mjs +0 -149
- package/dist/web/assets/local-CVA9799w.js +0 -9
- package/dist/web/assets/local-DHYvcQG0.css +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
bundle
|
|
4
|
+
} from "./chunk-OLZ62X7S.mjs";
|
|
2
5
|
import {
|
|
3
6
|
CURSOR_WORKSPACE_ENV
|
|
4
7
|
} from "./chunk-VUE2DDYY.mjs";
|
|
5
|
-
import
|
|
6
|
-
bundle
|
|
7
|
-
} from "./chunk-RW3DXWNC.mjs";
|
|
8
|
-
import "./chunk-A3LJU4JA.mjs";
|
|
8
|
+
import "./chunk-PXKGKVGW.mjs";
|
|
9
9
|
|
|
10
10
|
// src/install/main.ts
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../contracts/src/messaging/decode.ts
|
|
4
|
+
var MAX_IDENTIFIER_BYTES = 512;
|
|
5
|
+
var DecodeFailure = class extends Error {
|
|
6
|
+
path;
|
|
7
|
+
code;
|
|
8
|
+
constructor(path, code) {
|
|
9
|
+
super(`${path || "<root>"}: ${code}`);
|
|
10
|
+
this.name = "DecodeFailure";
|
|
11
|
+
this.path = path;
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function fail(path, code) {
|
|
16
|
+
throw new DecodeFailure(path, code);
|
|
17
|
+
}
|
|
18
|
+
function decodeWith(read) {
|
|
19
|
+
try {
|
|
20
|
+
return { ok: true, value: read() };
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error instanceof DecodeFailure) return { ok: false, error: { path: error.path, code: error.code } };
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var join = (path, key) => typeof key === "number" ? `${path}[${key}]` : path ? `${path}.${key}` : key;
|
|
27
|
+
function object(input, path, keys) {
|
|
28
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) fail(path, "not_object");
|
|
29
|
+
const prototype = Object.getPrototypeOf(input);
|
|
30
|
+
if (prototype !== Object.prototype && prototype !== null) fail(path, "not_object");
|
|
31
|
+
const record = input;
|
|
32
|
+
for (const key of Object.keys(record)) if (!keys.includes(key)) fail(join(path, key), "unknown_field");
|
|
33
|
+
for (const key of keys) if (!Object.hasOwn(record, key)) fail(join(path, key), "missing_field");
|
|
34
|
+
return { path, field: (key) => record[key], at: (key) => join(path, key) };
|
|
35
|
+
}
|
|
36
|
+
function array(input, path) {
|
|
37
|
+
if (!Array.isArray(input)) fail(path, "wrong_type");
|
|
38
|
+
return input;
|
|
39
|
+
}
|
|
40
|
+
function elementPath(path, index) {
|
|
41
|
+
return join(path, index);
|
|
42
|
+
}
|
|
43
|
+
function utf8Length(value) {
|
|
44
|
+
let bytes = 0;
|
|
45
|
+
for (const char of value) {
|
|
46
|
+
const code = char.codePointAt(0) ?? 0;
|
|
47
|
+
bytes += code < 128 ? 1 : code < 2048 ? 2 : code < 65536 ? 3 : 4;
|
|
48
|
+
}
|
|
49
|
+
return bytes;
|
|
50
|
+
}
|
|
51
|
+
function isWellFormed(value) {
|
|
52
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
53
|
+
const unit = value.charCodeAt(index);
|
|
54
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
55
|
+
const next = value.charCodeAt(index + 1);
|
|
56
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
57
|
+
index += 1;
|
|
58
|
+
} else if (unit >= 56320 && unit <= 57343) return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
var CONTROL = /[\u0000-\u001f\u007f-\u009f]/;
|
|
63
|
+
var INVISIBLE = /[\u061c\u200b\u200e\u200f\u202a-\u202e\u2060\u2066-\u2069\ufeff]/;
|
|
64
|
+
function text(input, path, maxBytes) {
|
|
65
|
+
if (typeof input !== "string") fail(path, "wrong_type");
|
|
66
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) fail(path, "invalid_limits");
|
|
67
|
+
if (!isWellFormed(input)) fail(path, "malformed_unicode");
|
|
68
|
+
if (utf8Length(input) > maxBytes) fail(path, "too_long");
|
|
69
|
+
if (input.includes("\0")) fail(path, "control_character");
|
|
70
|
+
return input;
|
|
71
|
+
}
|
|
72
|
+
function label(input, path, maxBytes) {
|
|
73
|
+
const value = text(input, path, maxBytes);
|
|
74
|
+
if (CONTROL.test(value)) fail(path, "control_character");
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
function displayText(input, path, maxBytes) {
|
|
78
|
+
const value = label(input, path, maxBytes);
|
|
79
|
+
if (INVISIBLE.test(value)) fail(path, "control_character");
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
function identifier(input, path) {
|
|
83
|
+
const value = label(input, path, MAX_IDENTIFIER_BYTES);
|
|
84
|
+
if (value.length === 0) fail(path, "empty");
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function nullable(input, read) {
|
|
88
|
+
return input === null ? null : read(input);
|
|
89
|
+
}
|
|
90
|
+
function literal(input, path, allowed) {
|
|
91
|
+
if (!allowed.includes(input)) fail(path, typeof input === typeof allowed[0] ? "invalid_value" : "wrong_type");
|
|
92
|
+
return input;
|
|
93
|
+
}
|
|
94
|
+
function version(input, path) {
|
|
95
|
+
if (input !== 1) fail(path, "unsupported_version");
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
function safeInteger(input, path) {
|
|
99
|
+
if (typeof input !== "number") fail(path, "wrong_type");
|
|
100
|
+
if (!Number.isSafeInteger(input)) fail(path, "unsafe_integer");
|
|
101
|
+
if (input < 0) fail(path, "invalid_value");
|
|
102
|
+
return input;
|
|
103
|
+
}
|
|
104
|
+
var UTC_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?Z$/;
|
|
105
|
+
function utcTimestamp(input, path) {
|
|
106
|
+
if (typeof input !== "string") fail(path, "wrong_type");
|
|
107
|
+
const match = UTC_TIMESTAMP.exec(input);
|
|
108
|
+
if (!match) fail(path, "invalid_value");
|
|
109
|
+
const [year, month, day, hour, minute, second] = match.slice(1).map(Number);
|
|
110
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
111
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
112
|
+
date.setUTCHours(hour, minute, second, 0);
|
|
113
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day || date.getUTCHours() !== hour || date.getUTCMinutes() !== minute || date.getUTCSeconds() !== second) fail(path, "invalid_value");
|
|
114
|
+
return input;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ../contracts/src/messaging/agent-names.ts
|
|
118
|
+
var MAX_AGENT_NAME_BYTES = 80;
|
|
119
|
+
var unsafe = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff]/u;
|
|
120
|
+
var reserved = /\b(?:admin(?:istrator)?|system|khala|moderator|owner|human|security|support|official)\b/iu;
|
|
121
|
+
function validateAgentName(input) {
|
|
122
|
+
if (typeof input !== "string") return { ok: false, error: "blank" };
|
|
123
|
+
const name = input.trim().normalize("NFC");
|
|
124
|
+
if (!name) return { ok: false, error: "blank" };
|
|
125
|
+
if (unsafe.test(name)) return { ok: false, error: "invalid_characters" };
|
|
126
|
+
if (new TextEncoder().encode(name).byteLength > MAX_AGENT_NAME_BYTES) return { ok: false, error: "too_long" };
|
|
127
|
+
if (reserved.test(name.normalize("NFKC"))) return { ok: false, error: "reserved" };
|
|
128
|
+
return { ok: true, name };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ../contracts/src/m1/agent-join.ts
|
|
132
|
+
var HARNESSES = ["claude", "codex", "cursor"];
|
|
133
|
+
var M1_LABEL_MAX_BYTES = 512;
|
|
134
|
+
var agentConfirmPagePath = (id) => `/agent/confirm?joinId=${encodeURIComponent(id)}`;
|
|
135
|
+
function readHarness(input, path) {
|
|
136
|
+
return literal(input, path, HARNESSES);
|
|
137
|
+
}
|
|
138
|
+
function readHttpUrl(input, path) {
|
|
139
|
+
if (typeof input !== "string") fail(path, "wrong_type");
|
|
140
|
+
let url;
|
|
141
|
+
try {
|
|
142
|
+
url = new URL(input);
|
|
143
|
+
} catch {
|
|
144
|
+
fail(path, "invalid_value");
|
|
145
|
+
}
|
|
146
|
+
if (url.username || url.password || url.hash || !(url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) fail(path, "invalid_value");
|
|
147
|
+
return input;
|
|
148
|
+
}
|
|
149
|
+
function readMatrixUserId(input, path) {
|
|
150
|
+
const value = identifier(input, path);
|
|
151
|
+
if (!/^@[^\s:]+:\S+$/u.test(value)) fail(path, "invalid_value");
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
function readRoomId(input, path) {
|
|
155
|
+
const value = identifier(input, path);
|
|
156
|
+
if (!/^!\S+$/u.test(value)) fail(path, "invalid_value");
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
var validAgentSessionId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u.test(value);
|
|
160
|
+
var validAgentRejoinSecret = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{43}$/u.test(value);
|
|
161
|
+
|
|
162
|
+
export {
|
|
163
|
+
fail,
|
|
164
|
+
decodeWith,
|
|
165
|
+
object,
|
|
166
|
+
array,
|
|
167
|
+
elementPath,
|
|
168
|
+
utf8Length,
|
|
169
|
+
isWellFormed,
|
|
170
|
+
text,
|
|
171
|
+
label,
|
|
172
|
+
displayText,
|
|
173
|
+
identifier,
|
|
174
|
+
nullable,
|
|
175
|
+
literal,
|
|
176
|
+
version,
|
|
177
|
+
safeInteger,
|
|
178
|
+
utcTimestamp,
|
|
179
|
+
reserved,
|
|
180
|
+
validateAgentName,
|
|
181
|
+
HARNESSES,
|
|
182
|
+
M1_LABEL_MAX_BYTES,
|
|
183
|
+
agentConfirmPagePath,
|
|
184
|
+
readHarness,
|
|
185
|
+
readHttpUrl,
|
|
186
|
+
readMatrixUserId,
|
|
187
|
+
readRoomId,
|
|
188
|
+
validAgentSessionId,
|
|
189
|
+
validAgentRejoinSecret
|
|
190
|
+
};
|
|
@@ -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-OLZ62X7S.mjs";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
7
|
var KHALA_AGENT_VERSION = bundle?.version ?? "0.0.0";
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
StateError,
|
|
4
|
+
channelFiles,
|
|
5
|
+
channelsDir,
|
|
6
|
+
ensureStateDir,
|
|
7
|
+
filesForDir,
|
|
8
|
+
readJson,
|
|
9
|
+
readStatus,
|
|
10
|
+
removeStateFile,
|
|
11
|
+
stateKey,
|
|
12
|
+
writeJsonAtomic,
|
|
13
|
+
writeStateFile
|
|
14
|
+
} from "./chunk-KCJFSVDN.mjs";
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_LISTENING_MODE,
|
|
17
|
+
decodeListeningMode
|
|
18
|
+
} from "./chunk-AUJEUKVM.mjs";
|
|
19
|
+
|
|
20
|
+
// src/inbox.ts
|
|
21
|
+
import * as fs from "node:fs/promises";
|
|
22
|
+
async function readEntries(files) {
|
|
23
|
+
let content;
|
|
24
|
+
try {
|
|
25
|
+
content = await fs.readFile(files.inbox, "utf8");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error.code === "ENOENT") return [];
|
|
28
|
+
throw new StateError("storage_failed");
|
|
29
|
+
}
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (const line of content.split("\n").slice(0, -1)) {
|
|
32
|
+
try {
|
|
33
|
+
const entry = JSON.parse(line);
|
|
34
|
+
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);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return entries;
|
|
39
|
+
}
|
|
40
|
+
async function appendEntries(files, entries) {
|
|
41
|
+
const seen = new Set((await readEntries(files)).map((entry) => entry.eventId));
|
|
42
|
+
const fresh = entries.filter((entry) => {
|
|
43
|
+
if (seen.has(entry.eventId)) return false;
|
|
44
|
+
seen.add(entry.eventId);
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
if (fresh.length) {
|
|
48
|
+
try {
|
|
49
|
+
await fs.appendFile(files.inbox, fresh.map((entry) => JSON.stringify(entry) + "\n").join(""), { mode: 384, flag: "a" });
|
|
50
|
+
} catch {
|
|
51
|
+
throw new StateError("storage_failed");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return fresh;
|
|
55
|
+
}
|
|
56
|
+
async function readCursor(files) {
|
|
57
|
+
const cursor = await readJson(files.cursor);
|
|
58
|
+
return cursor && (cursor.lastDeliveredEventId === null || typeof cursor.lastDeliveredEventId === "string") && Number.isSafeInteger(cursor.deliveredCount) && cursor.deliveredCount >= 0 ? cursor : { lastDeliveredEventId: null, deliveredCount: 0 };
|
|
59
|
+
}
|
|
60
|
+
async function unread(files) {
|
|
61
|
+
const cursor = await readCursor(files);
|
|
62
|
+
const entries = await readEntries(files);
|
|
63
|
+
return { entries: entries.slice(cursor.deliveredCount), cursor };
|
|
64
|
+
}
|
|
65
|
+
async function unreadCounts(files) {
|
|
66
|
+
const { entries } = await unread(files);
|
|
67
|
+
return { total: entries.length, messages: entries.filter((entry) => entry.kind === "message").length };
|
|
68
|
+
}
|
|
69
|
+
async function advanceCursor(files, expected, delivered) {
|
|
70
|
+
const current = await readCursor(files);
|
|
71
|
+
if (current.deliveredCount !== expected.deliveredCount || current.lastDeliveredEventId !== expected.lastDeliveredEventId) return "conflict";
|
|
72
|
+
if (!delivered.length) return expected;
|
|
73
|
+
const entries = await readEntries(files);
|
|
74
|
+
const slice = entries.slice(expected.deliveredCount, expected.deliveredCount + delivered.length);
|
|
75
|
+
if (slice.length !== delivered.length || slice.some((entry, index) => entry.eventId !== delivered[index]?.eventId)) return "conflict";
|
|
76
|
+
const next = { lastDeliveredEventId: delivered[delivered.length - 1].eventId, deliveredCount: expected.deliveredCount + delivered.length };
|
|
77
|
+
await writeJsonAtomic(files.cursor, next);
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
async function appendInbox(dir, entry) {
|
|
81
|
+
return (await appendEntries(filesForDir(dir), [entry])).length === 1;
|
|
82
|
+
}
|
|
83
|
+
function unreadCount(dir) {
|
|
84
|
+
return unreadCounts(filesForDir(dir));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/channels.ts
|
|
88
|
+
import * as fs2 from "node:fs/promises";
|
|
89
|
+
import path from "node:path";
|
|
90
|
+
function channelKey(roomId2) {
|
|
91
|
+
return stateKey(roomId2);
|
|
92
|
+
}
|
|
93
|
+
async function exists(file) {
|
|
94
|
+
try {
|
|
95
|
+
await fs2.lstat(file);
|
|
96
|
+
return true;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error.code === "ENOENT") return false;
|
|
99
|
+
throw new StateError("storage_failed");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function roomId(value) {
|
|
103
|
+
if (value && typeof value === "object" && "roomId" in value && typeof value.roomId === "string" && value.roomId.length) return value.roomId;
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
async function legacyRoomId(files) {
|
|
107
|
+
const saved = roomId(await readJson(files.session));
|
|
108
|
+
if (saved) return saved;
|
|
109
|
+
let content;
|
|
110
|
+
try {
|
|
111
|
+
content = await fs2.readFile(files.inbox, "utf8");
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error.code === "ENOENT") return void 0;
|
|
114
|
+
throw new StateError("storage_failed");
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return roomId(JSON.parse(content.split("\n")[0]));
|
|
118
|
+
} catch {
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function listChannels(files) {
|
|
123
|
+
await ensureStateDir(files.dir);
|
|
124
|
+
const channels = [];
|
|
125
|
+
const dir = channelsDir(files);
|
|
126
|
+
if (await exists(dir)) {
|
|
127
|
+
await ensureStateDir(dir);
|
|
128
|
+
for (const key of await fs2.readdir(dir)) {
|
|
129
|
+
if (!/^[a-f0-9]{24}$/.test(key)) continue;
|
|
130
|
+
const nested = filesForDir(path.join(dir, key));
|
|
131
|
+
await ensureStateDir(nested.dir);
|
|
132
|
+
const metadata = await readJson(path.join(nested.dir, "channel.json"));
|
|
133
|
+
const id = roomId(metadata);
|
|
134
|
+
if (!id || channelKey(id) !== key) continue;
|
|
135
|
+
channels.push({ key, roomId: id, ...typeof metadata?.channelName === "string" ? { channelName: metadata.channelName } : {}, files: nested, legacy: false });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (await exists(files.inbox)) {
|
|
139
|
+
const id = await legacyRoomId(files);
|
|
140
|
+
if (id) {
|
|
141
|
+
const status = await readStatus(files);
|
|
142
|
+
channels.push({ key: channelKey(id), roomId: id, ...typeof status?.channelName === "string" ? { channelName: status.channelName } : {}, files, legacy: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return channels.sort((a, b) => (a.channelName ?? "").localeCompare(b.channelName ?? "") || a.roomId.localeCompare(b.roomId));
|
|
146
|
+
}
|
|
147
|
+
async function migrateLegacy(files) {
|
|
148
|
+
await ensureStateDir(files.dir);
|
|
149
|
+
const names = ["cursor.json", "mode.json", "inbox.jsonl"];
|
|
150
|
+
const present = await Promise.all(names.map((name) => exists(path.join(files.dir, name))));
|
|
151
|
+
if (!present.some(Boolean)) return "none";
|
|
152
|
+
const id = await legacyRoomId(files);
|
|
153
|
+
if (!id) {
|
|
154
|
+
for (const name of names) await removeStateFile(files.dir, name);
|
|
155
|
+
return "discarded";
|
|
156
|
+
}
|
|
157
|
+
const nested = channelFiles(files, id);
|
|
158
|
+
await ensureStateDir(nested.dir);
|
|
159
|
+
const destinationInbox = await exists(nested.inbox);
|
|
160
|
+
if (present[2] && !destinationInbox && present[0] && await exists(nested.cursor)) {
|
|
161
|
+
if (await fs2.readFile(files.cursor, "utf8") !== await fs2.readFile(nested.cursor, "utf8")) {
|
|
162
|
+
throw new StateError("storage_failed");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const status = await readStatus(files);
|
|
166
|
+
const metadata = await readJson(path.join(nested.dir, "channel.json"));
|
|
167
|
+
if (!metadata || roomId(metadata) !== id) {
|
|
168
|
+
await writeStateFile(nested.dir, "channel.json", {
|
|
169
|
+
roomId: id,
|
|
170
|
+
...typeof status?.channelName === "string" ? { channelName: status.channelName } : {},
|
|
171
|
+
joinedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (destinationInbox) {
|
|
175
|
+
if (present[2]) await appendEntries(nested, (await unread(files)).entries);
|
|
176
|
+
for (const name of ["inbox.jsonl", "cursor.json", "mode.json"]) await removeStateFile(files.dir, name);
|
|
177
|
+
return "moved";
|
|
178
|
+
}
|
|
179
|
+
for (const name of names) {
|
|
180
|
+
if (await exists(path.join(nested.dir, name))) {
|
|
181
|
+
await removeStateFile(files.dir, name);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
await fs2.rename(path.join(files.dir, name), path.join(nested.dir, name));
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error.code !== "ENOENT") throw new StateError("storage_failed");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return "moved";
|
|
191
|
+
}
|
|
192
|
+
function resolveChannel(list, ref) {
|
|
193
|
+
const channels = list.map((item) => ({ channel: item.channelName ?? item.roomId, roomId: item.roomId }));
|
|
194
|
+
if (ref === void 0) {
|
|
195
|
+
if (list.length === 1) return { ok: true, channel: list[0] };
|
|
196
|
+
return { ok: false, code: list.length ? "channel_required" : "channel_unknown", channels };
|
|
197
|
+
}
|
|
198
|
+
const exact = list.find((item) => item.roomId === ref);
|
|
199
|
+
if (exact) return { ok: true, channel: exact };
|
|
200
|
+
const normalized = ref.replace(/^#/, "").toLowerCase();
|
|
201
|
+
const matches = list.filter((item) => item.channelName?.replace(/^#/, "").toLowerCase() === normalized);
|
|
202
|
+
if (matches.length === 1) return { ok: true, channel: matches[0] };
|
|
203
|
+
return { ok: false, code: matches.length ? "channel_ambiguous" : "channel_unknown", channels };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/mode.ts
|
|
207
|
+
async function readListeningMode(files) {
|
|
208
|
+
const content = await readJson(files.mode);
|
|
209
|
+
const decoded = decodeListeningMode(typeof content === "object" && content !== null ? content.mode : void 0);
|
|
210
|
+
return decoded.ok ? decoded.value : DEFAULT_LISTENING_MODE;
|
|
211
|
+
}
|
|
212
|
+
async function applyListeningMode(files, next, meta, now = () => /* @__PURE__ */ new Date()) {
|
|
213
|
+
const previous = await readListeningMode(files);
|
|
214
|
+
if (previous === "async" && next !== "async") {
|
|
215
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
216
|
+
const { cursor, entries } = await unread(files);
|
|
217
|
+
if (await advanceCursor(files, cursor, entries) !== "conflict") break;
|
|
218
|
+
if (attempt === 1) throw new StateError("storage_failed");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
await writeJsonAtomic(files.mode, { mode: next, ...meta, updatedAt: now().toISOString() });
|
|
222
|
+
return { previous, next };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export {
|
|
226
|
+
readEntries,
|
|
227
|
+
readCursor,
|
|
228
|
+
unread,
|
|
229
|
+
advanceCursor,
|
|
230
|
+
appendInbox,
|
|
231
|
+
unreadCount,
|
|
232
|
+
channelKey,
|
|
233
|
+
listChannels,
|
|
234
|
+
migrateLegacy,
|
|
235
|
+
resolveChannel,
|
|
236
|
+
readListeningMode,
|
|
237
|
+
applyListeningMode
|
|
238
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
-
LISTENING_MODES
|
|
3
|
+
LISTENING_MODES
|
|
4
|
+
} from "./chunk-AUJEUKVM.mjs";
|
|
5
|
+
import {
|
|
4
6
|
M1_LABEL_MAX_BYTES,
|
|
5
7
|
array,
|
|
6
8
|
decodeWith,
|
|
@@ -21,16 +23,18 @@ import {
|
|
|
21
23
|
utcTimestamp,
|
|
22
24
|
utf8Length,
|
|
23
25
|
version
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-SN3STAO2.mjs";
|
|
25
27
|
|
|
26
28
|
// src/client.ts
|
|
27
29
|
var KhalaClientError = class extends Error {
|
|
28
|
-
code
|
|
29
|
-
constructor(code, message) {
|
|
30
|
+
constructor(code, message, extra) {
|
|
30
31
|
super(message ?? code);
|
|
32
|
+
this.extra = extra;
|
|
31
33
|
this.name = "KhalaClientError";
|
|
32
34
|
this.code = code;
|
|
33
35
|
}
|
|
36
|
+
extra;
|
|
37
|
+
code;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
// ../contracts/src/m1/colors.ts
|