@lamplitisles/codex-for-love 0.4.0 → 0.4.1

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 (26) hide show
  1. package/package.json +1 -1
  2. package/vendor/IMPORTS.md +1 -1
  3. package/vendor/build/_app/immutable/assets/0.BI9m5aW9.css +2 -0
  4. package/vendor/build/_app/immutable/assets/2.GU_203nr.css +1 -0
  5. package/vendor/build/_app/immutable/chunks/Bg1Ry2sd.js +1 -0
  6. package/vendor/build/_app/immutable/chunks/CTZw3jzS.js +3 -0
  7. package/vendor/build/_app/immutable/chunks/CtNIUOtb.js +1147 -0
  8. package/vendor/build/_app/immutable/chunks/uBIymjUX.js +1 -0
  9. package/vendor/build/_app/immutable/entry/app.CAV4mc4Y.js +2 -0
  10. package/vendor/build/_app/immutable/entry/start.C7zzoZ2c.js +1 -0
  11. package/vendor/build/_app/immutable/nodes/0.sNCrAP8k.js +1 -0
  12. package/vendor/build/_app/immutable/nodes/{1.B1qFIsWm.js → 1.CcVh3vuf.js} +1 -1
  13. package/vendor/build/_app/immutable/nodes/2.D1r5MxMF.js +327 -0
  14. package/vendor/build/_app/version.json +1 -1
  15. package/vendor/build/index.html +10 -9
  16. package/vendor/config.example.toml +4 -0
  17. package/vendor/runtime/cli.mjs +271 -9
  18. package/vendor/runtime/companion-mcp.mjs +2 -1
  19. package/vendor/build/_app/immutable/assets/0._f6eIGbJ.css +0 -2
  20. package/vendor/build/_app/immutable/assets/2.D_B1Of5x.css +0 -1
  21. package/vendor/build/_app/immutable/chunks/DdacV1pJ.js +0 -1
  22. package/vendor/build/_app/immutable/chunks/DtqGwW3l.js +0 -3
  23. package/vendor/build/_app/immutable/entry/app.ROL0GFnN.js +0 -2
  24. package/vendor/build/_app/immutable/entry/start.D1dr4Kbn.js +0 -1
  25. package/vendor/build/_app/immutable/nodes/0.-C44RqM4.js +0 -1
  26. package/vendor/build/_app/immutable/nodes/2.TXBnv1oE.js +0 -326
@@ -1 +1 @@
1
- {"version":"1789543730015"}
1
+ {"version":"1789562160506"}
@@ -1,25 +1,26 @@
1
1
  <!doctype html>
2
2
  <html lang="en" data-theme="sticker-messenger">
3
- <head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><script>(function(){try{var appearance=localStorage.getItem('lamplitisles.companion.appearance');var language=localStorage.getItem('lamplitisles.companion.language');var systemDark=matchMedia('(prefers-color-scheme: dark)').matches;var dark=appearance==='dark'||(appearance!=='light'&&systemDark);document.documentElement.dataset.theme=dark?'night-voyage':'sticker-messenger';document.documentElement.lang=language==='zh'?'zh-Hans':'en'}catch(_){var systemDark=matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.dataset.theme=systemDark?'night-voyage':'sticker-messenger'}})();</script><link href="/_app/immutable/entry/start.D1dr4Kbn.js" rel="modulepreload">
4
- <link href="/_app/immutable/chunks/DdacV1pJ.js" rel="modulepreload">
5
- <link href="/_app/immutable/chunks/DtqGwW3l.js" rel="modulepreload">
6
- <link href="/_app/immutable/entry/app.ROL0GFnN.js" rel="modulepreload">
3
+ <head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><script>(function(){try{var appearance=localStorage.getItem('lamplitisles.companion.appearance');var language=localStorage.getItem('lamplitisles.companion.language');var systemDark=matchMedia('(prefers-color-scheme: dark)').matches;var dark=appearance==='dark'||(appearance!=='light'&&systemDark);document.documentElement.dataset.theme=dark?'night-voyage':'sticker-messenger';document.documentElement.lang=language==='zh'?'zh-Hans':'en'}catch(_){var systemDark=matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.dataset.theme=systemDark?'night-voyage':'sticker-messenger'}})();</script><link href="/_app/immutable/entry/start.C7zzoZ2c.js" rel="modulepreload">
4
+ <link href="/_app/immutable/chunks/Bg1Ry2sd.js" rel="modulepreload">
5
+ <link href="/_app/immutable/chunks/CTZw3jzS.js" rel="modulepreload">
6
+ <link href="/_app/immutable/entry/app.CAV4mc4Y.js" rel="modulepreload">
7
+ <link href="/_app/immutable/chunks/uBIymjUX.js" rel="modulepreload">
7
8
  <link href="/_app/immutable/chunks/xihTtKlq.js" rel="modulepreload">
8
- <link href="/_app/immutable/nodes/0.-C44RqM4.js" rel="modulepreload">
9
+ <link href="/_app/immutable/nodes/0.sNCrAP8k.js" rel="modulepreload">
9
10
 
10
- <link href="/_app/immutable/assets/0._f6eIGbJ.css" rel="stylesheet"></head>
11
+ <link href="/_app/immutable/assets/0.BI9m5aW9.css" rel="stylesheet"></head>
11
12
  <body data-sveltekit-preload-data="hover"><div style="display: contents">
12
13
  <script>
13
14
  {
14
- __sveltekit_1jsrk9c = {
15
+ __sveltekit_2j1r7b = {
15
16
  base: ""
16
17
  };
17
18
 
18
19
  const element = document.currentScript.parentElement;
19
20
 
20
21
  Promise.all([
21
- import("/_app/immutable/entry/start.D1dr4Kbn.js"),
22
- import("/_app/immutable/entry/app.ROL0GFnN.js")
22
+ import("/_app/immutable/entry/start.C7zzoZ2c.js"),
23
+ import("/_app/immutable/entry/app.CAV4mc4Y.js")
23
24
  ]).then(([kit, app]) => {
24
25
  kit.start(app, element);
25
26
  });
@@ -21,6 +21,10 @@ model = "gpt-5.6-luna"
21
21
  # local_compaction = true
22
22
  # home = "/path/to/a/Codex-home" # omit to use the existing Codex login
23
23
 
24
+ # Optional browser pet. Disabled by default; restart Partner after changing it.
25
+ #[pet]
26
+ #enabled = true
27
+
24
28
  # Optional local Keet-for-Agent gateway. Both fields and a stdin-only `keet`
25
29
  # credential enable it; omit either field to keep ordinary local conversation.
26
30
  #[keet]
@@ -6217,7 +6217,8 @@ const schema = object({
6217
6217
  keet: object({
6218
6218
  endpoint: string().min(1).optional(),
6219
6219
  media_root: string().min(1).optional()
6220
- }).strict().optional()
6220
+ }).strict().optional(),
6221
+ pet: object({ enabled: boolean().default(false) }).strict().default({ enabled: false })
6221
6222
  }).strict();
6222
6223
  async function loadConfig(path) {
6223
6224
  const config = schema.parse(parse$2(await readFile(path, "utf8")));
@@ -65339,6 +65340,217 @@ function processError(event, error, details = {}) {
65339
65340
  ...event === "event.failed" ? {} : { error: redactError(message) }
65340
65341
  });
65341
65342
  }
65343
+ //#endregion
65344
+ //#region apps/partner/runtime/pet.ts
65345
+ /** The deliberately small, browser-safe representation of companion activity. */
65346
+ const PET_ACTIVITIES = [
65347
+ "idle",
65348
+ "thinking",
65349
+ "read",
65350
+ "work",
65351
+ "replying",
65352
+ "success",
65353
+ "concern"
65354
+ ];
65355
+ /**
65356
+ * Converts app-server lifecycle metadata into a finite presentation state.
65357
+ * Never retain item text, arguments, results, paths, commands, or errors here.
65358
+ */
65359
+ var PetActivityProjection = class {
65360
+ #activity = "idle";
65361
+ #revision = 0;
65362
+ #turnActive = false;
65363
+ #items = /* @__PURE__ */ new Map();
65364
+ #timer;
65365
+ #changed;
65366
+ #durations;
65367
+ constructor(changed = () => {}, durations = {
65368
+ success: 1200,
65369
+ concern: 2e3
65370
+ }) {
65371
+ this.#changed = changed;
65372
+ this.#durations = durations;
65373
+ }
65374
+ snapshot() {
65375
+ return {
65376
+ activity: this.#activity,
65377
+ revision: this.#revision
65378
+ };
65379
+ }
65380
+ close() {
65381
+ if (this.#timer) clearTimeout(this.#timer);
65382
+ }
65383
+ idle() {
65384
+ this.#turnActive = false;
65385
+ this.#items.clear();
65386
+ this.#set("idle");
65387
+ }
65388
+ turnStarted() {
65389
+ this.#turnActive = true;
65390
+ this.#set("thinking");
65391
+ }
65392
+ itemStarted(item) {
65393
+ this.#turnActive = true;
65394
+ const activity = activityForItem(item);
65395
+ const id = typeof item.id === "string" ? item.id : `anonymous:${this.#items.size}`;
65396
+ this.#items.set(id, activity);
65397
+ this.#set(activity);
65398
+ }
65399
+ itemCompleted(item) {
65400
+ const id = typeof item.id === "string" ? item.id : void 0;
65401
+ if (id) this.#items.delete(id);
65402
+ if (failedItem(item)) this.#transient("concern", 2e3);
65403
+ else this.#set(this.#current());
65404
+ }
65405
+ turnCompleted(status) {
65406
+ this.#turnActive = false;
65407
+ this.#items.clear();
65408
+ this.#transient([
65409
+ "failed",
65410
+ "cancelled",
65411
+ "interrupted"
65412
+ ].includes(String(status)) ? "concern" : "success", [
65413
+ "failed",
65414
+ "cancelled",
65415
+ "interrupted"
65416
+ ].includes(String(status)) ? this.#durations.concern : this.#durations.success);
65417
+ }
65418
+ #current() {
65419
+ return [...this.#items.values()].at(-1) ?? (this.#turnActive ? "thinking" : "idle");
65420
+ }
65421
+ #transient(activity, ms) {
65422
+ this.#set(activity);
65423
+ if (this.#timer) clearTimeout(this.#timer);
65424
+ this.#timer = setTimeout(() => {
65425
+ this.#timer = void 0;
65426
+ this.#set(this.#current());
65427
+ }, ms);
65428
+ }
65429
+ #set(activity) {
65430
+ if (this.#timer && !["success", "concern"].includes(activity)) {
65431
+ clearTimeout(this.#timer);
65432
+ this.#timer = void 0;
65433
+ }
65434
+ if (this.#activity !== activity) {
65435
+ this.#activity = activity;
65436
+ this.#revision += 1;
65437
+ this.#changed();
65438
+ }
65439
+ }
65440
+ };
65441
+ function activityForItem(item) {
65442
+ switch (item.type) {
65443
+ case "agentMessage": return "replying";
65444
+ case "webSearch":
65445
+ case "web": return "read";
65446
+ case "commandExecution":
65447
+ case "fileChange":
65448
+ case "imageGeneration": return "work";
65449
+ case "mcpToolCall": return activityForMcp(String(item.server ?? ""), String(item.tool ?? ""));
65450
+ default: return "thinking";
65451
+ }
65452
+ }
65453
+ function activityForMcp(server, tool) {
65454
+ if (server === "companion") {
65455
+ if (tool === "read_relationship_history") return "read";
65456
+ if (tool === "send_voice" || tool === "roll_dice") return tool === "send_voice" ? "replying" : "thinking";
65457
+ return "work";
65458
+ }
65459
+ if (server === "keet") return tool === "keet_send_message" || tool === "send_message" ? "replying" : "read";
65460
+ if (server === "web" || server === "openaiDeveloperDocs") return "read";
65461
+ if ([
65462
+ "project",
65463
+ "flicknote",
65464
+ "guion-email",
65465
+ "og",
65466
+ "skill"
65467
+ ].includes(server)) return /(?:create|edit|update|write|send|apply|delete|move|organize)/iu.test(tool) ? server === "guion-email" && /(?:draft|send)/iu.test(tool) ? "replying" : "work" : "read";
65468
+ return "thinking";
65469
+ }
65470
+ function failedItem(item) {
65471
+ return [
65472
+ "failed",
65473
+ "error",
65474
+ "cancelled",
65475
+ "interrupted"
65476
+ ].includes(String(item.status));
65477
+ }
65478
+ //#endregion
65479
+ //#region apps/partner/runtime/pet-assets.ts
65480
+ const clip = object({
65481
+ file: string().regex(/^[a-z]+\.webp$/u),
65482
+ frameCount: union([literal(6), literal(8)]),
65483
+ fps: number$1().int().min(1).max(24),
65484
+ loop: boolean()
65485
+ }).strict();
65486
+ const manifestSchema = object({
65487
+ schemaVersion: literal(2),
65488
+ character: literal("shio"),
65489
+ revision: string().min(1).max(80),
65490
+ clips: object(Object.fromEntries(PET_ACTIVITIES.map((a) => [a, clip]))).strict()
65491
+ }).strict();
65492
+ const MAX_SHEET_BYTES = 15e5;
65493
+ async function localPetClip(state, activity) {
65494
+ const root = join(state, "pet-assets");
65495
+ let manifest;
65496
+ try {
65497
+ const parsed = manifestSchema.safeParse(JSON.parse(await readFile(join(root, "manifest.json"), "utf8")));
65498
+ if (!parsed.success) return void 0;
65499
+ manifest = parsed.data;
65500
+ } catch {
65501
+ return;
65502
+ }
65503
+ let selected;
65504
+ for (const candidate of PET_ACTIVITIES) {
65505
+ const data = await readClip(root, manifest.clips[candidate].file, manifest.clips[candidate].frameCount);
65506
+ if (!data) return void 0;
65507
+ if (candidate === activity) selected = data;
65508
+ }
65509
+ return selected ? {
65510
+ data: selected,
65511
+ manifest
65512
+ } : void 0;
65513
+ }
65514
+ function webpDimensions(data) {
65515
+ if (data.length < 20 || Buffer.from(data.subarray(0, 4)).toString() !== "RIFF" || Buffer.from(data.subarray(8, 12)).toString() !== "WEBP") return void 0;
65516
+ for (let offset = 12; offset + 8 <= data.length;) {
65517
+ const type = Buffer.from(data.subarray(offset, offset + 4)).toString();
65518
+ const size = data[offset + 4] | data[offset + 5] << 8 | data[offset + 6] << 16 | data[offset + 7] << 24;
65519
+ const body = offset + 8;
65520
+ if (body + size > data.length) return void 0;
65521
+ if (type === "VP8X" && size >= 10) return {
65522
+ width: 1 + data[body + 4] + (data[body + 5] << 8) + (data[body + 6] << 16),
65523
+ height: 1 + data[body + 7] + (data[body + 8] << 8) + (data[body + 9] << 16)
65524
+ };
65525
+ if (type === "VP8 " && size >= 10 && data[body + 3] === 157 && data[body + 4] === 1 && data[body + 5] === 42) return {
65526
+ width: data[body + 6] | (data[body + 7] & 63) << 8,
65527
+ height: data[body + 8] | (data[body + 9] & 63) << 8
65528
+ };
65529
+ if (type === "VP8L" && size >= 5 && data[body] === 47) return {
65530
+ width: 1 + data[body + 1] + ((data[body + 2] & 63) << 8),
65531
+ height: 1 + (data[body + 2] >> 6) + (data[body + 3] << 2) + ((data[body + 4] & 15) << 10)
65532
+ };
65533
+ offset = body + size + size % 2;
65534
+ }
65535
+ }
65536
+ async function readClip(root, name, frameCount) {
65537
+ if (extname(name) !== ".webp") return void 0;
65538
+ const path = resolve(root, name);
65539
+ if (relative(resolve(root), path).startsWith("..")) return void 0;
65540
+ try {
65541
+ const info = await lstat(path);
65542
+ if (!info.isFile() || info.isSymbolicLink() || info.size > MAX_SHEET_BYTES) return void 0;
65543
+ const [actualRoot, actual, data] = await Promise.all([
65544
+ realpath(root),
65545
+ realpath(path),
65546
+ readFile(path)
65547
+ ]);
65548
+ const dimensions = webpDimensions(data);
65549
+ return dimensions?.height === 512 && dimensions.width === frameCount * 512 && (actual === actualRoot || actual.startsWith(`${actualRoot}/`)) ? data : void 0;
65550
+ } catch {
65551
+ return;
65552
+ }
65553
+ }
65342
65554
  const HISTORY_PAGE_MESSAGES = 50;
65343
65555
  /** Match DSH's 50-message history window against CFL's rendered projection. */
65344
65556
  function visibleHistoryPage(messages, results, before) {
@@ -65397,6 +65609,19 @@ function turnTime(value, fallback) {
65397
65609
  if (number === null) return fallback;
65398
65610
  return number > 1e10 ? Math.round(number) : Math.round(number * 1e3);
65399
65611
  }
65612
+ function completedTurnTime(value) {
65613
+ const raw = numberOrNull(value);
65614
+ if (raw === null) return void 0;
65615
+ return raw > 1e10 ? Math.round(raw) : Math.round(raw * 1e3);
65616
+ }
65617
+ function inputTimeContext(source, now) {
65618
+ const date = new Date(now);
65619
+ const time = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
65620
+ return {
65621
+ kind: "application",
65622
+ value: `${source === "owner" ? "Current owner input" : "Qualifying Keet input"} received around local ${time}. Trusted delivery metadata; not user-authored text or an instruction.`
65623
+ };
65624
+ }
65400
65625
  function textFromUserItem(item) {
65401
65626
  if (item.type !== "userMessage" || !Array.isArray(item.content)) return void 0;
65402
65627
  return item.content.filter((part) => typeof part === "object" && part !== null && !Array.isArray(part) && part.type === "text").map((part) => part.text).filter((part) => typeof part === "string").join("") || (item.content.length ? "" : void 0);
@@ -65656,6 +65881,9 @@ async function createPartner(config, credentials, dependencies = {}) {
65656
65881
  let keetFeed;
65657
65882
  let keetReconnect;
65658
65883
  let keetConnecting = false;
65884
+ const pet = config.pet.enabled ? new PetActivityProjection(() => {
65885
+ if (!closing) notify();
65886
+ }) : void 0;
65659
65887
  const notify = () => {
65660
65888
  for (const listener of listeners) listener();
65661
65889
  };
@@ -65958,6 +66186,7 @@ async function createPartner(config, credentials, dependencies = {}) {
65958
66186
  const voiceIds = voiceIdsFromTurn(turn);
65959
66187
  let resultError = errorFromTurn(turn);
65960
66188
  let resultStatus = status;
66189
+ const completedAt = status === "completed" ? completedTurnTime(turn.completedAt) : void 0;
65961
66190
  for (const item of turn.items ?? []) {
65962
66191
  if (item.type !== "imageGeneration") continue;
65963
66192
  const generatedId = await projectGeneratedImage(resultOperationId(turn.id), sourceIds, item);
@@ -65974,7 +66203,8 @@ async function createPartner(config, credentials, dependencies = {}) {
65974
66203
  error: resultError,
65975
66204
  status: resultStatus,
65976
66205
  generatedIds,
65977
- voiceIds
66206
+ voiceIds,
66207
+ ...completedAt === void 0 ? {} : { completedAt }
65978
66208
  };
65979
66209
  const fingerprint = JSON.stringify(resultBody);
65980
66210
  const previous = presentationFingerprints.get(turn.id);
@@ -66056,7 +66286,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66056
66286
  const turn = registerTurn((await appServer.call("turn/start", {
66057
66287
  threadId,
66058
66288
  input: nativeInput(intent.input, intent.images),
66059
- clientUserMessageId: intent.transportId
66289
+ clientUserMessageId: intent.transportId,
66290
+ ...intent.source !== "none" ? { additionalContext: { "codex-for-love.message-time": inputTimeContext(intent.source, now()) } } : {}
66060
66291
  })).turn, intent.ids);
66061
66292
  if (!turn) throw new Error("Codex app-server did not return a turn");
66062
66293
  intent.turnId = turn.id;
@@ -66100,7 +66331,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66100
66331
  threadId,
66101
66332
  input: nativeInput(intent.input, intent.images),
66102
66333
  clientUserMessageId: intent.transportId,
66103
- expectedTurnId: expected
66334
+ expectedTurnId: expected,
66335
+ ...intent.source !== "none" ? { additionalContext: { "codex-for-love.message-time": inputTimeContext(intent.source, now()) } } : {}
66104
66336
  });
66105
66337
  intent.turnId = response.turnId;
66106
66338
  mergeTurnInputIds(response.turnId, intent.ids);
@@ -66155,7 +66387,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66155
66387
  ids,
66156
66388
  input: intents.map((intent) => intent.input).join("\n"),
66157
66389
  images: intents.flatMap((intent) => intent.images),
66158
- transportId: clientIdForInputs(ids)
66390
+ transportId: clientIdForInputs(ids),
66391
+ source: intents.every((intent) => intent.source === "owner") ? "owner" : "none"
66159
66392
  };
66160
66393
  recoveryPromise = (async () => {
66161
66394
  try {
@@ -66192,7 +66425,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66192
66425
  ids: [first.id],
66193
66426
  input: payload.input,
66194
66427
  images: payload.images,
66195
- transportId: first.id
66428
+ transportId: first.id,
66429
+ source: "keet"
66196
66430
  });
66197
66431
  } catch (error) {
66198
66432
  if (!closing) processError("keet.admission_failed", error, { operationId: first.id });
@@ -66289,7 +66523,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66289
66523
  ids: unconsumed,
66290
66524
  input: "",
66291
66525
  images: [],
66292
- transportId: "restore"
66526
+ transportId: "restore",
66527
+ source: "none"
66293
66528
  });
66294
66529
  }
66295
66530
  async function interruptActiveTurn() {
@@ -66372,6 +66607,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66372
66607
  break;
66373
66608
  }
66374
66609
  case "turn/started": {
66610
+ pet?.turnStarted();
66375
66611
  const turn = registerTurn(params.turn);
66376
66612
  if (turn) {
66377
66613
  const reconciled = userItems(turn).length || sourceIdsForTurn(turn.id).length ? await reconcileTurn(turn.id) : turn;
@@ -66383,6 +66619,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66383
66619
  }
66384
66620
  case "item/started": {
66385
66621
  const item = record(params.item);
66622
+ pet?.itemStarted(item);
66386
66623
  const turnId = typeof params.turnId === "string" ? params.turnId : "";
66387
66624
  if (turnId && item.type === "imageGeneration") {
66388
66625
  mergeItem(turnId, item);
@@ -66400,6 +66637,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66400
66637
  }
66401
66638
  case "item/completed": {
66402
66639
  const item = record(params.item);
66640
+ pet?.itemCompleted(item);
66403
66641
  const turnId = typeof params.turnId === "string" ? params.turnId : "";
66404
66642
  if (turnId) mergeItem(turnId, item);
66405
66643
  if (turnId && [
@@ -66419,6 +66657,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66419
66657
  }
66420
66658
  case "turn/completed": {
66421
66659
  const turn = registerTurn(params.turn);
66660
+ pet?.turnCompleted(turn?.status);
66422
66661
  let reconciled = turn;
66423
66662
  if (turn && (userItems(turn).length || sourceIdsForTurn(turn.id).length)) {
66424
66663
  reconciled = await reconcileTurn(turn.id);
@@ -66678,6 +66917,9 @@ async function createPartner(config, credentials, dependencies = {}) {
66678
66917
  }
66679
66918
  },
66680
66919
  avatar: (kind) => avatars[kind],
66920
+ async petAsset(activity) {
66921
+ return pet ? localPetClip(config.state, activity) : void 0;
66922
+ },
66681
66923
  async snapshot(options = {}) {
66682
66924
  await eventChain;
66683
66925
  const page = options.after === void 0 ? visibleHistoryPage(await store.allMessages(), [...turnResults.values()], options.before) : await store.messagePage(options);
@@ -66734,7 +66976,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66734
66976
  voices: result.voiceIds.map((id) => ({
66735
66977
  id,
66736
66978
  url: `/api/audio/${id}.mp3`
66737
- }))
66979
+ })),
66980
+ ...result.completedAt === void 0 ? {} : { completedAt: result.completedAt }
66738
66981
  }));
66739
66982
  const unresolved = await unresolvedMessages();
66740
66983
  const context = await store.observedContext();
@@ -66766,6 +67009,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66766
67009
  pendingCount: pendingIds.size,
66767
67010
  cancellable,
66768
67011
  typing: activeTurnId !== null,
67012
+ ...pet ? { pet: pet.snapshot() } : {},
66769
67013
  messages,
66770
67014
  results,
66771
67015
  draft: restoredDraft ? {
@@ -66804,7 +67048,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66804
67048
  ids: [id],
66805
67049
  input,
66806
67050
  images: materialized,
66807
- transportId: id
67051
+ transportId: id,
67052
+ source: "owner"
66808
67053
  };
66809
67054
  if (activeTurnId) await steerIntent(intent, activeTurnId);
66810
67055
  else await startIntent(intent);
@@ -66877,6 +67122,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66877
67122
  close() {
66878
67123
  if (closePromise) return closePromise;
66879
67124
  closing = true;
67125
+ pet?.close();
66880
67126
  if (keetReconnect) clearTimeout(keetReconnect);
66881
67127
  keetFeed?.close();
66882
67128
  for (const unsubscribe of unsubscribeAppServer.splice(0)) unsubscribe();
@@ -67674,6 +67920,22 @@ function createWebServer(partner, assets, options = {}) {
67674
67920
  response.end(avatar.data);
67675
67921
  return;
67676
67922
  }
67923
+ if (path.startsWith("/api/pet-assets/") && request.method === "GET") {
67924
+ const activity = path.slice(16);
67925
+ if (!PET_ACTIVITIES.includes(activity)) return json(response, { error: "Pet asset not found" }, 404);
67926
+ const asset = await partner.petAsset(activity);
67927
+ if (!asset) return json(response, { error: "Pet asset not found" }, 404);
67928
+ const metadata = asset.manifest.clips[activity];
67929
+ response.writeHead(200, {
67930
+ "content-type": "image/webp",
67931
+ "cache-control": "no-store",
67932
+ "x-pet-frame-count": String(metadata.frameCount),
67933
+ "x-pet-fps": String(metadata.fps),
67934
+ "x-pet-loop": String(metadata.loop)
67935
+ });
67936
+ response.end(asset.data);
67937
+ return;
67938
+ }
67677
67939
  if (path === "/api/messages" && request.method === "POST") {
67678
67940
  const parsed = object({
67679
67941
  id: uuid(),
@@ -22708,7 +22708,8 @@ const schema = object({
22708
22708
  keet: object({
22709
22709
  endpoint: string().min(1).optional(),
22710
22710
  media_root: string().min(1).optional()
22711
- }).strict().optional()
22711
+ }).strict().optional(),
22712
+ pet: object({ enabled: boolean().default(false) }).strict().default({ enabled: false })
22712
22713
  }).strict();
22713
22714
  async function loadConfig(path) {
22714
22715
  const config = schema.parse(parse(await readFile(path, "utf8")));