khala-cli 0.4.5 → 0.4.7

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.
Files changed (32) hide show
  1. package/dist/{chunk-TZKEQIHP.mjs → chunk-25AX7UMF.mjs} +305 -97
  2. package/dist/{chunk-AUJEUKVM.mjs → chunk-3OFKVYOX.mjs} +1 -1
  3. package/dist/{chunk-7G2G4E4U.mjs → chunk-4LSCOWWN.mjs} +18 -17
  4. package/dist/chunk-5RGEO5HN.mjs +142 -0
  5. package/dist/{chunk-V7M27QLT.mjs → chunk-6YEYXZZB.mjs} +8 -7
  6. package/dist/{chunk-EXHCVFVH.mjs → chunk-7V2YQIRI.mjs} +11 -5
  7. package/dist/{chunk-PXKGKVGW.mjs → chunk-APIWKRKG.mjs} +1 -1
  8. package/dist/{chunk-H46OG22B.mjs → chunk-C5SMOUTO.mjs} +34 -5
  9. package/dist/{chunk-Y4WVILEJ.mjs → chunk-IG2NU5J6.mjs} +4 -4
  10. package/dist/chunk-JEI32DEA.mjs +634 -0
  11. package/dist/{chunk-MBLP7LQJ.mjs → chunk-K6EJSZYO.mjs} +95 -98
  12. package/dist/{chunk-V4Y26O4R.mjs → chunk-L7L6UNXN.mjs} +85 -36
  13. package/dist/{chunk-P5CP32X2.mjs → chunk-LNG3ZZBK.mjs} +1 -1
  14. package/dist/{chunk-IR3VN2ID.mjs → chunk-OAFYPBCP.mjs} +1 -19
  15. package/dist/{chunk-Y2DRTRBJ.mjs → chunk-ODTSGEID.mjs} +24 -8
  16. package/dist/{chunk-T7YRVE43.mjs → chunk-OP2HZ7EM.mjs} +8 -30
  17. package/dist/{chunk-RSDOTTZQ.mjs → chunk-S3FX7VCY.mjs} +30 -12
  18. package/dist/{chunk-OLZ62X7S.mjs → chunk-SU6362YU.mjs} +1 -1
  19. package/dist/{chunk-EW22K3JO.mjs → chunk-TOBQUAPD.mjs} +210 -311
  20. package/dist/{chunk-SN3STAO2.mjs → chunk-U3MKSPRO.mjs} +40 -3
  21. package/dist/{chunk-6LC66VHK.mjs → chunk-W5HS2PQO.mjs} +12 -13
  22. package/dist/{chunk-KCJFSVDN.mjs → chunk-WNOIHCCC.mjs} +9 -7
  23. package/dist/{chunk-SQXDATEC.mjs → chunk-YIHRHD7W.mjs} +1 -1
  24. package/dist/khala.mjs +10 -10
  25. package/dist/web/assets/local-BQl2KrE1.js +9 -0
  26. package/dist/web/assets/local-OwDq47O4.css +1 -0
  27. package/dist/web/index.html +2 -2
  28. package/package.json +1 -1
  29. package/dist/chunk-HB3UGIZG.mjs +0 -144
  30. package/dist/chunk-VUE2DDYY.mjs +0 -34
  31. package/dist/web/assets/local-C1vz22ps.js +0 -9
  32. package/dist/web/assets/local-DdjwMERJ.css +0 -1
@@ -1,14 +1,28 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
+ import {
3
+ KHALA_AGENT_VERSION
4
+ } from "./chunk-YIHRHD7W.mjs";
5
+ import {
6
+ bundle
7
+ } from "./chunk-SU6362YU.mjs";
8
+ import {
9
+ StateError,
10
+ ensureStateDir,
11
+ readJson,
12
+ stateRoot
13
+ } from "./chunk-WNOIHCCC.mjs";
2
14
  import {
3
15
  LISTENING_MODES
4
- } from "./chunk-AUJEUKVM.mjs";
16
+ } from "./chunk-3OFKVYOX.mjs";
5
17
  import {
18
+ HARNESS_REGISTRY,
6
19
  M1_LABEL_MAX_BYTES,
7
20
  array,
8
21
  decodeWith,
9
22
  displayText,
10
23
  elementPath,
11
24
  fail,
25
+ harnessInfo,
12
26
  identifier,
13
27
  literal,
14
28
  nullable,
@@ -23,7 +37,7 @@ import {
23
37
  utcTimestamp,
24
38
  utf8Length,
25
39
  version
26
- } from "./chunk-SN3STAO2.mjs";
40
+ } from "./chunk-U3MKSPRO.mjs";
27
41
 
28
42
  // src/client.ts
29
43
  var KhalaClientError = class extends Error {
@@ -53,8 +67,8 @@ function fnv1a(value) {
53
67
  function defaultHumanColor(ownerId) {
54
68
  return HUMAN_COLOR_IDS[fnv1a(ownerId) % HUMAN_COLOR_IDS.length];
55
69
  }
56
- function readHumanColorId(input, path) {
57
- if (!isHumanColorId(input)) return fail(path, "invalid_value");
70
+ function readHumanColorId(input, path2) {
71
+ if (!isHumanColorId(input)) return fail(path2, "invalid_value");
58
72
  return input;
59
73
  }
60
74
 
@@ -69,8 +83,8 @@ function normalizeInitials(input) {
69
83
  function isCanonicalInitials(value) {
70
84
  return typeof value === "string" && normalizeInitials(value) === value;
71
85
  }
72
- function readHumanInitials(input, path) {
73
- if (!isCanonicalInitials(input)) return fail(path, "invalid_value");
86
+ function readHumanInitials(input, path2) {
87
+ if (!isCanonicalInitials(input)) return fail(path2, "invalid_value");
74
88
  return input;
75
89
  }
76
90
 
@@ -91,12 +105,18 @@ function checkName(input, kind) {
91
105
  if (reserved.test(name) || kind === "username" && agentSuffix.test(name)) return { ok: false, error: "reserved" };
92
106
  return { ok: true, name };
93
107
  }
94
- var MODEL_NAMES = { claude: "Claude", codex: "Codex", cursor: "Cursor" };
108
+ var escapePattern = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
109
+ var newAgentSuffix = new RegExp(`-(?:${HARNESS_REGISTRY.filter((row) => row.modelName !== "Agent").map((row) => escapePattern(row.modelName)).join("|")})(?:-\\d+)?$`, "iu");
110
+ function checkNewUsername(input) {
111
+ const checked = checkName(input, "username");
112
+ if (!checked.ok) return checked;
113
+ return newAgentSuffix.test(checked.name) ? { ok: false, error: "reserved" } : checked;
114
+ }
95
115
  function defaultAgentName(username, harness, n = 1) {
96
- return `${username}-${MODEL_NAMES[harness]}${n === 1 ? "" : `-${n}`}`;
116
+ return `${username}-${harnessInfo(harness).modelName}${n === 1 ? "" : `-${n}`}`;
97
117
  }
98
118
  function isDefaultAgentName(name, username, harness) {
99
- const escaped = `${username}-${MODEL_NAMES[harness]}`.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
119
+ const escaped = escapePattern(`${username}-${harnessInfo(harness).modelName}`);
100
120
  return new RegExp(`^${escaped}(?:-\\d+)?$`, "iu").test(name);
101
121
  }
102
122
 
@@ -147,90 +167,90 @@ var localRoomKey = (roomId) => {
147
167
  var localRoomPath = (roomId, tail) => `/api/local/rooms/${encodeURIComponent(roomId)}/${tail}`;
148
168
  var LOCAL_OWNER_ID = "local-owner";
149
169
  var LOCAL_OWNER_DEVICE_ID = "KH_LOCAL_OWNER";
150
- function readLocalRoomId(input, path) {
151
- const value = readRoomId(input, path);
152
- if (!isLocalRoomId(value)) fail(path, "invalid_value");
170
+ function readLocalRoomId(input, path2) {
171
+ const value = readRoomId(input, path2);
172
+ if (!isLocalRoomId(value)) fail(path2, "invalid_value");
153
173
  return value;
154
174
  }
155
- function readLocalUserId(input, path) {
156
- const value = readMatrixUserId(input, path);
157
- if (!isLocalUserId(value)) fail(path, "invalid_value");
175
+ function readLocalUserId(input, path2) {
176
+ const value = readMatrixUserId(input, path2);
177
+ if (!isLocalUserId(value)) fail(path2, "invalid_value");
158
178
  return value;
159
179
  }
160
- function readLocalEventId(input, path) {
161
- const value = identifier(input, path);
162
- if (!isLocalEventId(value)) fail(path, "invalid_value");
180
+ function readLocalEventId(input, path2) {
181
+ const value = identifier(input, path2);
182
+ if (!isLocalEventId(value)) fail(path2, "invalid_value");
163
183
  return value;
164
184
  }
165
- function readLocalTxnId(input, path) {
166
- const value = identifier(input, path);
167
- if (!isLocalTxnId(value)) fail(path, "invalid_value");
185
+ function readLocalTxnId(input, path2) {
186
+ const value = identifier(input, path2);
187
+ if (!isLocalTxnId(value)) fail(path2, "invalid_value");
168
188
  return value;
169
189
  }
170
- function readLocalUsername(input, path) {
171
- const value = identifier(input, path);
190
+ function readLocalUsername(input, path2) {
191
+ const value = identifier(input, path2);
172
192
  const checked = checkName(value, "username");
173
- if (!checked.ok || checked.name !== value) fail(path, "invalid_value");
193
+ if (!checked.ok || checked.name !== value) fail(path2, "invalid_value");
174
194
  return value;
175
195
  }
176
- function readLocalInitials(input, path) {
177
- return readHumanInitials(input, path);
196
+ function readLocalInitials(input, path2) {
197
+ return readHumanInitials(input, path2);
178
198
  }
179
- function record(input, path, required, optional = []) {
199
+ function record(input, path2, required, optional = []) {
180
200
  const present = optional.filter((key) => typeof input === "object" && input !== null && Object.hasOwn(input, key));
181
- return object(input, path, [...required, ...present]);
201
+ return object(input, path2, [...required, ...present]);
182
202
  }
183
203
  function has(input, key) {
184
204
  return typeof input === "object" && input !== null && Object.hasOwn(input, key);
185
205
  }
186
- function plainObject(input, path) {
187
- if (typeof input !== "object" || input === null || Array.isArray(input)) fail(path, "not_object");
206
+ function plainObject(input, path2) {
207
+ if (typeof input !== "object" || input === null || Array.isArray(input)) fail(path2, "not_object");
188
208
  const prototype = Object.getPrototypeOf(input);
189
- if (prototype !== Object.prototype && prototype !== null) fail(path, "not_object");
209
+ if (prototype !== Object.prototype && prototype !== null) fail(path2, "not_object");
190
210
  return input;
191
211
  }
192
- function contentObject(input, path) {
193
- const value = plainObject(input, path);
212
+ function contentObject(input, path2) {
213
+ const value = plainObject(input, path2);
194
214
  let encoded;
195
215
  try {
196
216
  encoded = JSON.stringify(value);
197
217
  } catch {
198
- fail(path, "invalid_value");
218
+ fail(path2, "invalid_value");
199
219
  }
200
- if (utf8Length(encoded) > LOCAL_CONTENT_MAX_BYTES) fail(path, "too_long");
220
+ if (utf8Length(encoded) > LOCAL_CONTENT_MAX_BYTES) fail(path2, "too_long");
201
221
  return value;
202
222
  }
203
- function channelName(input, path) {
204
- const value = displayName(input, path);
205
- if ([...value].length > LOCAL_CHANNEL_NAME_MAX_CHARS) fail(path, "too_long");
206
- if (value !== value.trim()) fail(path, "invalid_value");
223
+ function channelName(input, path2) {
224
+ const value = displayName(input, path2);
225
+ if ([...value].length > LOCAL_CHANNEL_NAME_MAX_CHARS) fail(path2, "too_long");
226
+ if (value !== value.trim()) fail(path2, "invalid_value");
207
227
  return value;
208
228
  }
209
- function displayName(input, path) {
210
- const value = displayText(input, path, M1_LABEL_MAX_BYTES);
211
- if (value.length === 0) fail(path, "empty");
229
+ function displayName(input, path2) {
230
+ const value = displayText(input, path2, M1_LABEL_MAX_BYTES);
231
+ if (value.length === 0) fail(path2, "empty");
212
232
  return value;
213
233
  }
214
- function seq(input, path) {
215
- const value = safeInteger(input, path);
216
- if (value === 0) fail(path, "invalid_value");
234
+ function seq(input, path2) {
235
+ const value = safeInteger(input, path2);
236
+ if (value === 0) fail(path2, "invalid_value");
217
237
  return value;
218
238
  }
219
- function readCreateContent(input, path) {
220
- const r = record(input, path, ["name", "createdBy"], ["operationId"]);
239
+ function readCreateContent(input, path2) {
240
+ const r = record(input, path2, ["name", "createdBy"], ["operationId"]);
221
241
  return {
222
242
  name: channelName(r.field("name"), r.at("name")),
223
243
  createdBy: readLocalUserId(r.field("createdBy"), r.at("createdBy")),
224
244
  ...has(input, "operationId") ? { operationId: readLocalTxnId(r.field("operationId"), r.at("operationId")) } : {}
225
245
  };
226
246
  }
227
- function readNameContent(input, path) {
228
- const r = object(input, path, ["name"]);
247
+ function readNameContent(input, path2) {
248
+ const r = object(input, path2, ["name"]);
229
249
  return { name: channelName(r.field("name"), r.at("name")) };
230
250
  }
231
- function readMemberContent(input, path) {
251
+ function readMemberContent(input, path2) {
232
252
  const modeKey = "com.khala.listening_mode";
233
- const r = record(input, path, ["user", "membership", "displayname", "kind"], ["harness", "invitedBy", modeKey]);
253
+ const r = record(input, path2, ["user", "membership", "displayname", "kind"], ["harness", "invitedBy", modeKey]);
234
254
  const kind = literal(r.field("kind"), r.at("kind"), ["human", "agent"]);
235
255
  if (kind === "human") {
236
256
  for (const key of ["harness", modeKey]) if (has(input, key)) fail(r.at(key), "invalid_value");
@@ -252,28 +272,28 @@ function decodeLocalMemberContent(input) {
252
272
  return decodeWith(() => readMemberContent(input, ""));
253
273
  }
254
274
  var EVENT_TYPES = ["m.room.create", "m.room.name", "m.room.member", "m.room.message", "com.khala.event.v1", "com.khala.listening_mode.v1"];
255
- function readContent(input, path, type) {
275
+ function readContent(input, path2, type) {
256
276
  switch (type) {
257
277
  case "m.room.create":
258
- return readCreateContent(input, path);
278
+ return readCreateContent(input, path2);
259
279
  case "m.room.name":
260
- return readNameContent(input, path);
280
+ return readNameContent(input, path2);
261
281
  case "m.room.member":
262
- return readMemberContent(input, path);
282
+ return readMemberContent(input, path2);
263
283
  default: {
264
- const content = contentObject(input, path);
284
+ const content = contentObject(input, path2);
265
285
  if (type === "m.room.message") {
266
- literal(content["msgtype"], `${path}.msgtype`, ["m.text", "m.notice"]);
267
- const body = text(content["body"], `${path}.body`, LOCAL_CONTENT_MAX_BYTES);
268
- if (body.length === 0) fail(`${path}.body`, "empty");
269
- if (body.length > LOCAL_BODY_MAX_CHARS) fail(`${path}.body`, "too_long");
286
+ literal(content["msgtype"], `${path2}.msgtype`, ["m.text", "m.notice"]);
287
+ const body = text(content["body"], `${path2}.body`, LOCAL_CONTENT_MAX_BYTES);
288
+ if (body.length === 0) fail(`${path2}.body`, "empty");
289
+ if (body.length > LOCAL_BODY_MAX_CHARS) fail(`${path2}.body`, "too_long");
270
290
  }
271
291
  return content;
272
292
  }
273
293
  }
274
294
  }
275
- function readEvent(input, path) {
276
- const r = record(input, path, ["seq", "eventId", "roomId", "type", "sender", "ts", "content"], ["txnId", "previousContent"]);
295
+ function readEvent(input, path2) {
296
+ const r = record(input, path2, ["seq", "eventId", "roomId", "type", "sender", "ts", "content"], ["txnId", "previousContent"]);
277
297
  const type = literal(r.field("type"), r.at("type"), EVENT_TYPES);
278
298
  return {
279
299
  seq: seq(r.field("seq"), r.at("seq")),
@@ -290,16 +310,16 @@ function readEvent(input, path) {
290
310
  function decodeLocalEvent(input) {
291
311
  return decodeWith(() => readEvent(input, ""));
292
312
  }
293
- function limitedArray(input, path, max, read) {
294
- const values = array(input, path);
295
- if (values.length > max) fail(path, "too_long");
296
- return values.map((value, i) => read(value, elementPath(path, i)));
313
+ function limitedArray(input, path2, max, read) {
314
+ const values = array(input, path2);
315
+ if (values.length > max) fail(path2, "too_long");
316
+ return values.map((value, i) => read(value, elementPath(path2, i)));
297
317
  }
298
- function readEvents(input, path, history) {
299
- const events = limitedArray(input, path, history ? LOCAL_HISTORY_LIMIT_MAX : LOCAL_EVENTS_PAGE_MAX, readEvent);
318
+ function readEvents(input, path2, history) {
319
+ const events = limitedArray(input, path2, history ? LOCAL_HISTORY_LIMIT_MAX : LOCAL_EVENTS_PAGE_MAX, readEvent);
300
320
  events.forEach((event, i) => {
301
- if (i > 0 && event.seq <= events[i - 1].seq) fail(`${elementPath(path, i)}.seq`, "invalid_value");
302
- if (history && event.type !== "m.room.message" && event.type !== "com.khala.event.v1") fail(`${elementPath(path, i)}.type`, "invalid_value");
321
+ if (i > 0 && event.seq <= events[i - 1].seq) fail(`${elementPath(path2, i)}.seq`, "invalid_value");
322
+ if (history && event.type !== "m.room.message" && event.type !== "com.khala.event.v1") fail(`${elementPath(path2, i)}.type`, "invalid_value");
303
323
  });
304
324
  return events;
305
325
  }
@@ -321,8 +341,8 @@ function decodeLocalHistoryPage(input) {
321
341
  };
322
342
  });
323
343
  }
324
- function readLocalMe(input, path) {
325
- const r = record(input, path, ["userId", "roomId", "roomName", "membership", "displayName"], ["invitedBy"]);
344
+ function readLocalMe(input, path2) {
345
+ const r = record(input, path2, ["userId", "roomId", "roomName", "membership", "displayName"], ["invitedBy"]);
326
346
  return {
327
347
  userId: readLocalUserId(r.field("userId"), r.at("userId")),
328
348
  roomId: readLocalRoomId(r.field("roomId"), r.at("roomId")),
@@ -335,8 +355,8 @@ function readLocalMe(input, path) {
335
355
  function decodeLocalMe(input) {
336
356
  return decodeWith(() => readLocalMe(input, ""));
337
357
  }
338
- function readLocalJoined(input, path) {
339
- const r = record(input, path, ["seq", "ts"], []);
358
+ function readLocalJoined(input, path2) {
359
+ const r = record(input, path2, ["seq", "ts"], []);
340
360
  return {
341
361
  seq: seq(r.field("seq"), r.at("seq")),
342
362
  ts: safeInteger(r.field("ts"), r.at("ts"))
@@ -345,8 +365,8 @@ function readLocalJoined(input, path) {
345
365
  function decodeLocalJoined(input) {
346
366
  return decodeWith(() => readLocalJoined(input, ""));
347
367
  }
348
- function readLocalSendResult(input, path) {
349
- const r = record(input, path, ["eventId"], []);
368
+ function readLocalSendResult(input, path2) {
369
+ const r = record(input, path2, ["eventId"], []);
350
370
  return {
351
371
  eventId: readLocalEventId(r.field("eventId"), r.at("eventId"))
352
372
  };
@@ -354,8 +374,8 @@ function readLocalSendResult(input, path) {
354
374
  function decodeLocalSendResult(input) {
355
375
  return decodeWith(() => readLocalSendResult(input, ""));
356
376
  }
357
- function readLocalErrorBody(input, path) {
358
- const r = record(input, path, ["error"], []);
377
+ function readLocalErrorBody(input, path2) {
378
+ const r = record(input, path2, ["error"], []);
359
379
  return {
360
380
  error: identifier(r.field("error"), r.at("error"))
361
381
  };
@@ -363,8 +383,8 @@ function readLocalErrorBody(input, path) {
363
383
  function decodeLocalErrorBody(input) {
364
384
  return decodeWith(() => readLocalErrorBody(input, ""));
365
385
  }
366
- function readOwnerProfile(input, path) {
367
- const r = record(input, path, ["v", "username", "color", "initials", "updatedAt"]);
386
+ function readOwnerProfile(input, path2) {
387
+ const r = record(input, path2, ["v", "username", "color", "initials", "updatedAt"]);
368
388
  return {
369
389
  v: version(r.field("v"), r.at("v")),
370
390
  username: readLocalUsername(r.field("username"), r.at("username")),
@@ -376,11 +396,11 @@ function readOwnerProfile(input, path) {
376
396
  function decodeOwnerProfile(input) {
377
397
  return decodeWith(() => readOwnerProfile(input, ""));
378
398
  }
379
- function nullableInitials(input, path) {
380
- return nullable(input, (value) => readLocalInitials(value, path));
399
+ function nullableInitials(input, path2) {
400
+ return nullable(input, (value) => readLocalInitials(value, path2));
381
401
  }
382
- function readMember(input, path) {
383
- const r = record(input, path, ["userId", "participantId", "ownerId", "deviceId", "displayName", "kind", "membership"], ["harness", "ownerLabel", "listeningMode"]);
402
+ function readMember(input, path2) {
403
+ const r = record(input, path2, ["userId", "participantId", "ownerId", "deviceId", "displayName", "kind", "membership"], ["harness", "ownerLabel", "listeningMode"]);
384
404
  const userId = readLocalUserId(r.field("userId"), r.at("userId"));
385
405
  const participantId = readLocalUserId(r.field("participantId"), r.at("participantId"));
386
406
  if (participantId !== userId) fail(r.at("participantId"), "invalid_value");
@@ -436,18 +456,18 @@ function decodeHelperFile(input) {
436
456
  };
437
457
  });
438
458
  }
439
- function readSha256(input, path) {
440
- const value = identifier(input, path);
441
- if (!/^[0-9a-f]{64}$/u.test(value)) fail(path, "invalid_value");
459
+ function readSha256(input, path2) {
460
+ const value = identifier(input, path2);
461
+ if (!/^[0-9a-f]{64}$/u.test(value)) fail(path2, "invalid_value");
442
462
  return value;
443
463
  }
444
464
  function decodeChannelSecrets(input) {
445
465
  return decodeWith(() => {
446
466
  const r = object(input, "", ["v", "links", "members"]);
447
467
  const links = Object.fromEntries(Object.entries(plainObject(r.field("links"), r.at("links"))).map(([key, value]) => {
448
- const path = `${r.at("links")}.${key}`;
449
- readSha256(key, path);
450
- const link = record(value, path, ["expiresAt", "kind"], ["consumedAt"]);
468
+ const path2 = `${r.at("links")}.${key}`;
469
+ readSha256(key, path2);
470
+ const link = record(value, path2, ["expiresAt", "kind"], ["consumedAt"]);
451
471
  return [key, {
452
472
  expiresAt: utcTimestamp(link.field("expiresAt"), link.at("expiresAt")),
453
473
  kind: literal(link.field("kind"), link.at("kind"), ["join"]),
@@ -455,9 +475,9 @@ function decodeChannelSecrets(input) {
455
475
  }];
456
476
  }));
457
477
  const members = Object.fromEntries(Object.entries(plainObject(r.field("members"), r.at("members"))).map(([key, value]) => {
458
- const path = `${r.at("members")}.${key}`;
459
- if (!isLocalAgentUserId(key)) fail(path, "invalid_value");
460
- const member = record(value, path, ["tokenSha256"], ["sessionKey"]);
478
+ const path2 = `${r.at("members")}.${key}`;
479
+ if (!isLocalAgentUserId(key)) fail(path2, "invalid_value");
480
+ const member = record(value, path2, ["tokenSha256"], ["sessionKey"]);
461
481
  return [key, {
462
482
  tokenSha256: readSha256(member.field("tokenSha256"), member.at("tokenSha256")),
463
483
  ...has(value, "sessionKey") ? { sessionKey: readSha256(member.field("sessionKey"), member.at("sessionKey")) } : {}
@@ -467,17 +487,201 @@ function decodeChannelSecrets(input) {
467
487
  });
468
488
  }
469
489
 
490
+ // src/local/lifecycle.ts
491
+ import * as fs from "node:fs/promises";
492
+ import { close as closeFd } from "node:fs";
493
+ import path from "node:path";
494
+ import { fileURLToPath } from "node:url";
495
+ import { setTimeout as sleepDefault } from "node:timers/promises";
496
+ import { spawn as nodeSpawn } from "node:child_process";
497
+ function helperPaths(env = process.env) {
498
+ const root = path.join(stateRoot(env), "local");
499
+ const candidate = /^\d{1,5}$/u.test(env.KHALA_LOCAL_PORT ?? "") ? Number(env.KHALA_LOCAL_PORT) : 0;
500
+ const port = candidate >= 1 && candidate <= 65535 ? candidate : LOCAL_DEFAULT_PORT;
501
+ return { root, helperFile: path.join(root, "helper.json"), logFile: path.join(root, "helper.log"), port, origin: `http://127.0.0.1:${port}` };
502
+ }
503
+ async function readHelperFile(env = process.env) {
504
+ const decoded = decodeHelperFile(await readJson(helperPaths(env).helperFile));
505
+ if (!decoded.ok) return null;
506
+ const file = decoded.value;
507
+ if (!Number.isSafeInteger(file.pid) || file.pid <= 0 || !Number.isSafeInteger(file.port) || file.port <= 0 || file.port > 65535 || file.origin !== `http://127.0.0.1:${file.port}` || !/^[A-Za-z0-9_-]{43}$/u.test(file.adminToken)) return null;
508
+ return file;
509
+ }
510
+ function helperBinPath() {
511
+ return fileURLToPath(bundle ? new URL("./khala.mjs", import.meta.url) : new URL("../../bin/khala.mjs", import.meta.url));
512
+ }
513
+ var HELPER_ENV_ALLOWLIST = [
514
+ "PATH",
515
+ "HOME",
516
+ "USER",
517
+ "LOGNAME",
518
+ "LANG",
519
+ "LC_ALL",
520
+ "TMPDIR",
521
+ "XDG_STATE_HOME",
522
+ "NODE_OPTIONS",
523
+ "KHALA_EGRESS_LOG",
524
+ "KHALA_LOCAL_PORT",
525
+ "KHALA_LOCAL_IDLE_MS",
526
+ "KHALA_LOCAL_WEB_DIR"
527
+ ];
528
+ function helperChildEnv(env) {
529
+ const childEnv = {};
530
+ for (const key of HELPER_ENV_ALLOWLIST) if (env[key] !== void 0) childEnv[key] = env[key];
531
+ return childEnv;
532
+ }
533
+ var attempts = /* @__PURE__ */ new Map();
534
+ var restarted = false;
535
+ var recovering = /* @__PURE__ */ new Map();
536
+ function olderHelperVersion(version2) {
537
+ if (typeof version2 !== "string") return true;
538
+ const parse = (value) => /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value);
539
+ const helper = parse(version2), cli = parse(KHALA_AGENT_VERSION);
540
+ if (!helper || !cli) return true;
541
+ for (let i = 1; i <= 3; i++) {
542
+ if (Number(helper[i]) !== Number(cli[i])) return Number(helper[i]) < Number(cli[i]);
543
+ }
544
+ if (helper[4] === cli[4]) return false;
545
+ if (!helper[4] || !cli[4]) return Boolean(helper[4]);
546
+ const a = helper[4].split("."), b = cli[4].split(".");
547
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
548
+ if (a[i] === b[i]) continue;
549
+ if (a[i] === void 0 || b[i] === void 0) return a[i] === void 0;
550
+ const an = /^\d+$/u.test(a[i]), bn = /^\d+$/u.test(b[i]);
551
+ if (an && bn) return Number(a[i]) < Number(b[i]);
552
+ if (an !== bn) return an;
553
+ return a[i] < b[i];
554
+ }
555
+ return false;
556
+ }
557
+ async function healthy(fetchImpl, file) {
558
+ try {
559
+ const response = await fetchImpl(`${file.origin}/healthz`, { signal: AbortSignal.timeout(500) });
560
+ if (response.status !== 200) {
561
+ await response.body?.cancel();
562
+ return null;
563
+ }
564
+ const body = await response.json();
565
+ return typeof body === "object" && body !== null && "ok" in body && body.ok === true && "pid" in body && body.pid === file.pid ? { version: "version" in body ? body.version : void 0 } : null;
566
+ } catch {
567
+ return null;
568
+ }
569
+ }
570
+ function connection(file) {
571
+ return { origin: file.origin, adminToken: file.adminToken };
572
+ }
573
+ async function attempt(env, deps, restartOrigin) {
574
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
575
+ const spawn = deps.spawn ?? ((command, argv, options) => nodeSpawn(command, [...argv], options));
576
+ const sleep = deps.sleep ?? sleepDefault;
577
+ const now = deps.now ?? Date.now;
578
+ const existing = await readHelperFile(env);
579
+ const status = existing ? await healthy(fetchImpl, existing) : null;
580
+ if (existing && status) {
581
+ if (!olderHelperVersion(status.version) && restartOrigin === void 0 || restarted) return connection(existing);
582
+ if (restartOrigin !== void 0 && existing.origin !== restartOrigin) return connection(existing);
583
+ restarted = true;
584
+ recovering.set(helperPaths(env).helperFile, existing.origin);
585
+ try {
586
+ const response = await fetchImpl(`${existing.origin}/api/local/shutdown`, {
587
+ method: "POST",
588
+ headers: { authorization: `Bearer ${existing.adminToken}` },
589
+ signal: AbortSignal.timeout(2e3)
590
+ });
591
+ await response.body?.cancel();
592
+ if (response.status !== 204) throw new Error("shutdown_failed");
593
+ const deadline = now() + 2e3;
594
+ for (let i = 0; i < 20; i++) {
595
+ if (!await healthy(fetchImpl, existing)) break;
596
+ if (now() >= deadline || i === 19) throw new Error("shutdown_timeout");
597
+ await sleep(100);
598
+ }
599
+ } catch {
600
+ const refreshed = await readHelperFile(env);
601
+ if (refreshed && await healthy(fetchImpl, refreshed)) return connection(refreshed);
602
+ }
603
+ }
604
+ const paths = helperPaths(env);
605
+ try {
606
+ await ensureStateDir(path.join(paths.root, "channels"));
607
+ let handle;
608
+ let fd;
609
+ let spawnFailed = false;
610
+ try {
611
+ if (deps.openLog) fd = await deps.openLog(paths.logFile);
612
+ else {
613
+ handle = await fs.open(paths.logFile, "a", 384);
614
+ await handle.chmod(384);
615
+ fd = handle.fd;
616
+ }
617
+ const child = spawn(process.execPath, [helperBinPath(), "local", "serve"], {
618
+ detached: true,
619
+ stdio: ["ignore", fd, fd],
620
+ env: helperChildEnv(existing && status ? { ...env, KHALA_LOCAL_PORT: String(existing.port) } : env),
621
+ cwd: paths.root,
622
+ shell: false
623
+ });
624
+ child.on("error", () => {
625
+ spawnFailed = true;
626
+ });
627
+ child.unref();
628
+ } finally {
629
+ if (handle) await handle.close();
630
+ else if (fd !== void 0) {
631
+ const descriptor = fd;
632
+ await new Promise((resolve) => {
633
+ closeFd(descriptor, () => resolve());
634
+ });
635
+ }
636
+ }
637
+ const deadline = now() + 5e3;
638
+ while (!spawnFailed && now() < deadline) {
639
+ const file = await readHelperFile(env);
640
+ if (file && await healthy(fetchImpl, file) && !spawnFailed && now() <= deadline) return connection(file);
641
+ if (spawnFailed || now() >= deadline) break;
642
+ await sleep(100);
643
+ }
644
+ } catch (error) {
645
+ if (error instanceof StateError) throw error;
646
+ }
647
+ throw new KhalaClientError("internal_error", "helper_unavailable");
648
+ }
649
+ function ensureHelper(env = process.env, deps = {}, restartOrigin) {
650
+ const key = helperPaths(env).helperFile;
651
+ const pending = attempts.get(key);
652
+ if (pending) return pending;
653
+ const promise = attempt(env, deps, restartOrigin).finally(() => {
654
+ attempts.delete(key);
655
+ recovering.delete(key);
656
+ });
657
+ attempts.set(key, promise);
658
+ return promise;
659
+ }
660
+ async function restartHelper(origin, env = process.env, deps = {}) {
661
+ const key = helperPaths(env).helperFile;
662
+ const pending = attempts.get(key);
663
+ if (pending) {
664
+ const shareRecovery = !restarted || recovering.get(key) === origin;
665
+ const connection2 = await pending;
666
+ if (shareRecovery && restarted && connection2.origin === origin) return true;
667
+ }
668
+ if (restarted) return false;
669
+ const file = await readHelperFile(env);
670
+ if (!file || file.origin !== origin) return false;
671
+ await ensureHelper(env, deps, origin);
672
+ return restarted;
673
+ }
674
+
470
675
  export {
471
676
  KhalaClientError,
472
677
  isHumanColorId,
473
678
  defaultHumanColor,
474
679
  normalizeInitials,
475
680
  checkName,
476
- MODEL_NAMES,
681
+ checkNewUsername,
477
682
  defaultAgentName,
478
683
  isDefaultAgentName,
479
684
  LOCAL_OWNER_USER_ID,
480
- LOCAL_DEFAULT_PORT,
481
685
  LOCAL_LINK_TTL_MS,
482
686
  LOCAL_LONG_POLL_MAX_S,
483
687
  LOCAL_IDLE_EXIT_MS,
@@ -503,6 +707,10 @@ export {
503
707
  decodeLocalErrorBody,
504
708
  decodeOwnerProfile,
505
709
  decodeLocalMembersResponse,
506
- decodeHelperFile,
507
- decodeChannelSecrets
710
+ decodeChannelSecrets,
711
+ helperPaths,
712
+ readHelperFile,
713
+ olderHelperVersion,
714
+ ensureHelper,
715
+ restartHelper
508
716
  };
@@ -5,7 +5,7 @@ import {
5
5
  object,
6
6
  readMatrixUserId,
7
7
  version
8
- } from "./chunk-SN3STAO2.mjs";
8
+ } from "./chunk-U3MKSPRO.mjs";
9
9
 
10
10
  // ../contracts/src/delivery/decode.ts
11
11
  var MAX_IDENTIFIER_BYTES = 512;