@lamplitisles/codex-for-love 0.4.0 → 0.4.2

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.DfSR8oPO.css +2 -0
  4. package/vendor/build/_app/immutable/assets/2.GU_203nr.css +1 -0
  5. package/vendor/build/_app/immutable/chunks/BxP8U2lE.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.Di6q1DhK.js +2 -0
  10. package/vendor/build/_app/immutable/entry/start.CwKZcHfn.js +1 -0
  11. package/vendor/build/_app/immutable/nodes/0.PSG_XCTv.js +1 -0
  12. package/vendor/build/_app/immutable/nodes/{1.B1qFIsWm.js → 1.CJlXDQ0m.js} +1 -1
  13. package/vendor/build/_app/immutable/nodes/2.C7zq-Uwo.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 +8 -0
  17. package/vendor/runtime/cli.mjs +297 -37
  18. package/vendor/runtime/companion-mcp.mjs +6 -3
  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":"1789633167994"}
@@ -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, viewport-fit=cover" /><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.CwKZcHfn.js" rel="modulepreload">
4
+ <link href="/_app/immutable/chunks/BxP8U2lE.js" rel="modulepreload">
5
+ <link href="/_app/immutable/chunks/CTZw3jzS.js" rel="modulepreload">
6
+ <link href="/_app/immutable/entry/app.Di6q1DhK.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.PSG_XCTv.js" rel="modulepreload">
9
10
 
10
- <link href="/_app/immutable/assets/0._f6eIGbJ.css" rel="stylesheet"></head>
11
+ <link href="/_app/immutable/assets/0.DfSR8oPO.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_2pnxow = {
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.CwKZcHfn.js"),
23
+ import("/_app/immutable/entry/app.Di6q1DhK.js")
23
24
  ]).then(([kit, app]) => {
24
25
  kit.start(app, element);
25
26
  });
@@ -19,8 +19,16 @@ model = "gpt-5.6-luna"
19
19
  # The installed launcher selects and verifies its matching standalone app-server.
20
20
  # A checkout configuration may set command to a verified codex-app-server path.
21
21
  # local_compaction = true
22
+ # Number of newest complete text rounds included only after compaction. The
23
+ # default is 10; the 4,000-token soft budget can select fewer long rounds.
24
+ # Restart Partner after changing this value.
25
+ context_round_limit = 10
22
26
  # home = "/path/to/a/Codex-home" # omit to use the existing Codex login
23
27
 
28
+ # Optional browser pet. Disabled by default; restart Partner after changing it.
29
+ #[pet]
30
+ #enabled = true
31
+
24
32
  # Optional local Keet-for-Agent gateway. Both fields and a stdin-only `keet`
25
33
  # credential enable it; omit either field to keep ordinary local conversation.
26
34
  #[keet]
@@ -6203,12 +6203,14 @@ const schema = object({
6203
6203
  version: literal(SUPPORTED_CODEX_VERSION).default(SUPPORTED_CODEX_VERSION),
6204
6204
  home: string().min(1).optional(),
6205
6205
  provenance: string().min(1).optional(),
6206
- local_compaction: boolean().default(false)
6206
+ local_compaction: boolean().default(false),
6207
+ context_round_limit: number$1().int().min(0).default(10)
6207
6208
  }).strict().default({
6208
6209
  command: "codex-app-server",
6209
6210
  model: DEFAULT_CODEX_MODEL,
6210
6211
  version: SUPPORTED_CODEX_VERSION,
6211
- local_compaction: false
6212
+ local_compaction: false,
6213
+ context_round_limit: 10
6212
6214
  }),
6213
6215
  speech: object({
6214
6216
  endpoint: url().default("https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"),
@@ -6217,7 +6219,8 @@ const schema = object({
6217
6219
  keet: object({
6218
6220
  endpoint: string().min(1).optional(),
6219
6221
  media_root: string().min(1).optional()
6220
- }).strict().optional()
6222
+ }).strict().optional(),
6223
+ pet: object({ enabled: boolean().default(false) }).strict().default({ enabled: false })
6221
6224
  }).strict();
6222
6225
  async function loadConfig(path) {
6223
6226
  const config = schema.parse(parse$2(await readFile(path, "utf8")));
@@ -62600,6 +62603,8 @@ function partnerPaths(workspace) {
62600
62603
  audio: join(managedRoot, "audio")
62601
62604
  };
62602
62605
  }
62606
+ //#endregion
62607
+ //#region apps/partner/runtime/context-bootstrap.ts
62603
62608
  const CONTEXT_TOKEN_BUDGET = 4e3;
62604
62609
  const hookFileName = "session-start-hook.mjs";
62605
62610
  const companionMcpFileName = import.meta.url.endsWith(".mjs") ? "companion-mcp.mjs" : "companion-mcp.ts";
@@ -62620,7 +62625,7 @@ function finalAgentText(items) {
62620
62625
  return texts.length ? texts.join("\n\n").trim() : void 0;
62621
62626
  }
62622
62627
  /** Select newest completed conversational sides without copying tool or image payloads. */
62623
- function selectTextRounds(turns, budget = CONTEXT_TOKEN_BUDGET, limit = 5) {
62628
+ function selectTextRounds(turns, budget = CONTEXT_TOKEN_BUDGET, limit = 10) {
62624
62629
  const candidates = [];
62625
62630
  for (const turn of turns) {
62626
62631
  if (turn.status !== "completed" || !turn.items) continue;
@@ -62680,6 +62685,10 @@ function shellQuote(value) {
62680
62685
  function hookCommand(contextPath) {
62681
62686
  return `${shellQuote(process.execPath)} ${shellQuote(fileURLToPath(new URL(`./${hookFileName}`, import.meta.url)))} ${shellQuote(contextPath)}`;
62682
62687
  }
62688
+ /** A release path changes, but a hook targeting this internal bootstrap remains CFL-owned. */
62689
+ function ownsBootstrapHook(value, contextPath) {
62690
+ return isRecord$1(value) && value.type === "command" && typeof value.command === "string" && value.command.includes(hookFileName) && value.command.includes(shellQuote(contextPath));
62691
+ }
62683
62692
  function isRecord$1(value) {
62684
62693
  return typeof value === "object" && value !== null && !Array.isArray(value);
62685
62694
  }
@@ -62758,31 +62767,21 @@ async function ensureHookDeclaration(workspace, contextPath, partnerConfigPath,
62758
62767
  const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
62759
62768
  let changed = ensureCompanionMcp(config, workspace, partnerConfigPath);
62760
62769
  changed = ensureKeetMcp(config, keetEndpoint) || changed;
62761
- let own = false;
62762
- const normalizedGroups = sessionStart.map((group) => {
62763
- if (!isRecord$1(group) || !Array.isArray(group.hooks)) return group;
62764
- let groupChanged = false;
62765
- const handlers = group.hooks.map((handler) => {
62766
- if (!isRecord$1(handler) || handler.type !== "command" || handler.command !== command) return handler;
62767
- own = true;
62768
- if (group.matcher === "startup|compact" && handler.additionalContextLimit === 0) return handler;
62769
- groupChanged = true;
62770
- return {
62771
- ...handler,
62772
- type: "command",
62773
- command,
62774
- additionalContextLimit: 0
62775
- };
62770
+ const own = sessionStart.flatMap((group) => isRecord$1(group) && Array.isArray(group.hooks) ? group.hooks.filter((handler) => ownsBootstrapHook(handler, contextPath)).map((handler) => ({
62771
+ group,
62772
+ handler
62773
+ })) : []);
62774
+ const canonical = own.length === 1 && own[0].group.matcher === "startup|compact" && own[0].handler.command === command && own[0].handler.additionalContextLimit === 0;
62775
+ let normalizedGroups = sessionStart;
62776
+ if (!canonical) {
62777
+ normalizedGroups = sessionStart.flatMap((group) => {
62778
+ if (!isRecord$1(group) || !Array.isArray(group.hooks)) return [group];
62779
+ const handlers = group.hooks.filter((handler) => !ownsBootstrapHook(handler, contextPath));
62780
+ return handlers.length ? [{
62781
+ ...group,
62782
+ hooks: handlers
62783
+ }] : [];
62776
62784
  });
62777
- if (!groupChanged) return group;
62778
- changed = true;
62779
- return {
62780
- ...group,
62781
- matcher: "startup|compact",
62782
- hooks: handlers
62783
- };
62784
- });
62785
- if (!own) {
62786
62785
  normalizedGroups.push({
62787
62786
  matcher: "startup|compact",
62788
62787
  hooks: [{
@@ -65339,6 +65338,217 @@ function processError(event, error, details = {}) {
65339
65338
  ...event === "event.failed" ? {} : { error: redactError(message) }
65340
65339
  });
65341
65340
  }
65341
+ //#endregion
65342
+ //#region apps/partner/runtime/pet.ts
65343
+ /** The deliberately small, browser-safe representation of companion activity. */
65344
+ const PET_ACTIVITIES = [
65345
+ "idle",
65346
+ "thinking",
65347
+ "read",
65348
+ "work",
65349
+ "replying",
65350
+ "success",
65351
+ "concern"
65352
+ ];
65353
+ /**
65354
+ * Converts app-server lifecycle metadata into a finite presentation state.
65355
+ * Never retain item text, arguments, results, paths, commands, or errors here.
65356
+ */
65357
+ var PetActivityProjection = class {
65358
+ #activity = "idle";
65359
+ #revision = 0;
65360
+ #turnActive = false;
65361
+ #items = /* @__PURE__ */ new Map();
65362
+ #timer;
65363
+ #changed;
65364
+ #durations;
65365
+ constructor(changed = () => {}, durations = {
65366
+ success: 1200,
65367
+ concern: 2e3
65368
+ }) {
65369
+ this.#changed = changed;
65370
+ this.#durations = durations;
65371
+ }
65372
+ snapshot() {
65373
+ return {
65374
+ activity: this.#activity,
65375
+ revision: this.#revision
65376
+ };
65377
+ }
65378
+ close() {
65379
+ if (this.#timer) clearTimeout(this.#timer);
65380
+ }
65381
+ idle() {
65382
+ this.#turnActive = false;
65383
+ this.#items.clear();
65384
+ this.#set("idle");
65385
+ }
65386
+ turnStarted() {
65387
+ this.#turnActive = true;
65388
+ this.#set("thinking");
65389
+ }
65390
+ itemStarted(item) {
65391
+ this.#turnActive = true;
65392
+ const activity = activityForItem(item);
65393
+ const id = typeof item.id === "string" ? item.id : `anonymous:${this.#items.size}`;
65394
+ this.#items.set(id, activity);
65395
+ this.#set(activity);
65396
+ }
65397
+ itemCompleted(item) {
65398
+ const id = typeof item.id === "string" ? item.id : void 0;
65399
+ if (id) this.#items.delete(id);
65400
+ if (failedItem(item)) this.#transient("concern", 2e3);
65401
+ else this.#set(this.#current());
65402
+ }
65403
+ turnCompleted(status) {
65404
+ this.#turnActive = false;
65405
+ this.#items.clear();
65406
+ this.#transient([
65407
+ "failed",
65408
+ "cancelled",
65409
+ "interrupted"
65410
+ ].includes(String(status)) ? "concern" : "success", [
65411
+ "failed",
65412
+ "cancelled",
65413
+ "interrupted"
65414
+ ].includes(String(status)) ? this.#durations.concern : this.#durations.success);
65415
+ }
65416
+ #current() {
65417
+ return [...this.#items.values()].at(-1) ?? (this.#turnActive ? "thinking" : "idle");
65418
+ }
65419
+ #transient(activity, ms) {
65420
+ this.#set(activity);
65421
+ if (this.#timer) clearTimeout(this.#timer);
65422
+ this.#timer = setTimeout(() => {
65423
+ this.#timer = void 0;
65424
+ this.#set(this.#current());
65425
+ }, ms);
65426
+ }
65427
+ #set(activity) {
65428
+ if (this.#timer && !["success", "concern"].includes(activity)) {
65429
+ clearTimeout(this.#timer);
65430
+ this.#timer = void 0;
65431
+ }
65432
+ if (this.#activity !== activity) {
65433
+ this.#activity = activity;
65434
+ this.#revision += 1;
65435
+ this.#changed();
65436
+ }
65437
+ }
65438
+ };
65439
+ function activityForItem(item) {
65440
+ switch (item.type) {
65441
+ case "agentMessage": return "replying";
65442
+ case "webSearch":
65443
+ case "web": return "read";
65444
+ case "commandExecution":
65445
+ case "fileChange":
65446
+ case "imageGeneration": return "work";
65447
+ case "mcpToolCall": return activityForMcp(String(item.server ?? ""), String(item.tool ?? ""));
65448
+ default: return "thinking";
65449
+ }
65450
+ }
65451
+ function activityForMcp(server, tool) {
65452
+ if (server === "companion") {
65453
+ if (tool === "read_relationship_history") return "read";
65454
+ if (tool === "send_voice" || tool === "roll_dice") return tool === "send_voice" ? "replying" : "thinking";
65455
+ return "work";
65456
+ }
65457
+ if (server === "keet") return tool === "keet_send_message" || tool === "send_message" ? "replying" : "read";
65458
+ if (server === "web" || server === "openaiDeveloperDocs") return "read";
65459
+ if ([
65460
+ "project",
65461
+ "flicknote",
65462
+ "guion-email",
65463
+ "og",
65464
+ "skill"
65465
+ ].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";
65466
+ return "thinking";
65467
+ }
65468
+ function failedItem(item) {
65469
+ return [
65470
+ "failed",
65471
+ "error",
65472
+ "cancelled",
65473
+ "interrupted"
65474
+ ].includes(String(item.status));
65475
+ }
65476
+ //#endregion
65477
+ //#region apps/partner/runtime/pet-assets.ts
65478
+ const clip = object({
65479
+ file: string().regex(/^[a-z]+\.webp$/u),
65480
+ frameCount: union([literal(6), literal(8)]),
65481
+ fps: number$1().int().min(1).max(24),
65482
+ loop: boolean()
65483
+ }).strict();
65484
+ const manifestSchema = object({
65485
+ schemaVersion: literal(2),
65486
+ character: literal("shio"),
65487
+ revision: string().min(1).max(80),
65488
+ clips: object(Object.fromEntries(PET_ACTIVITIES.map((a) => [a, clip]))).strict()
65489
+ }).strict();
65490
+ const MAX_SHEET_BYTES = 15e5;
65491
+ async function localPetClip(state, activity) {
65492
+ const root = join(state, "pet-assets");
65493
+ let manifest;
65494
+ try {
65495
+ const parsed = manifestSchema.safeParse(JSON.parse(await readFile(join(root, "manifest.json"), "utf8")));
65496
+ if (!parsed.success) return void 0;
65497
+ manifest = parsed.data;
65498
+ } catch {
65499
+ return;
65500
+ }
65501
+ let selected;
65502
+ for (const candidate of PET_ACTIVITIES) {
65503
+ const data = await readClip(root, manifest.clips[candidate].file, manifest.clips[candidate].frameCount);
65504
+ if (!data) return void 0;
65505
+ if (candidate === activity) selected = data;
65506
+ }
65507
+ return selected ? {
65508
+ data: selected,
65509
+ manifest
65510
+ } : void 0;
65511
+ }
65512
+ function webpDimensions(data) {
65513
+ if (data.length < 20 || Buffer.from(data.subarray(0, 4)).toString() !== "RIFF" || Buffer.from(data.subarray(8, 12)).toString() !== "WEBP") return void 0;
65514
+ for (let offset = 12; offset + 8 <= data.length;) {
65515
+ const type = Buffer.from(data.subarray(offset, offset + 4)).toString();
65516
+ const size = data[offset + 4] | data[offset + 5] << 8 | data[offset + 6] << 16 | data[offset + 7] << 24;
65517
+ const body = offset + 8;
65518
+ if (body + size > data.length) return void 0;
65519
+ if (type === "VP8X" && size >= 10) return {
65520
+ width: 1 + data[body + 4] + (data[body + 5] << 8) + (data[body + 6] << 16),
65521
+ height: 1 + data[body + 7] + (data[body + 8] << 8) + (data[body + 9] << 16)
65522
+ };
65523
+ if (type === "VP8 " && size >= 10 && data[body + 3] === 157 && data[body + 4] === 1 && data[body + 5] === 42) return {
65524
+ width: data[body + 6] | (data[body + 7] & 63) << 8,
65525
+ height: data[body + 8] | (data[body + 9] & 63) << 8
65526
+ };
65527
+ if (type === "VP8L" && size >= 5 && data[body] === 47) return {
65528
+ width: 1 + data[body + 1] + ((data[body + 2] & 63) << 8),
65529
+ height: 1 + (data[body + 2] >> 6) + (data[body + 3] << 2) + ((data[body + 4] & 15) << 10)
65530
+ };
65531
+ offset = body + size + size % 2;
65532
+ }
65533
+ }
65534
+ async function readClip(root, name, frameCount) {
65535
+ if (extname(name) !== ".webp") return void 0;
65536
+ const path = resolve(root, name);
65537
+ if (relative(resolve(root), path).startsWith("..")) return void 0;
65538
+ try {
65539
+ const info = await lstat(path);
65540
+ if (!info.isFile() || info.isSymbolicLink() || info.size > MAX_SHEET_BYTES) return void 0;
65541
+ const [actualRoot, actual, data] = await Promise.all([
65542
+ realpath(root),
65543
+ realpath(path),
65544
+ readFile(path)
65545
+ ]);
65546
+ const dimensions = webpDimensions(data);
65547
+ return dimensions?.height === 512 && dimensions.width === frameCount * 512 && (actual === actualRoot || actual.startsWith(`${actualRoot}/`)) ? data : void 0;
65548
+ } catch {
65549
+ return;
65550
+ }
65551
+ }
65342
65552
  const HISTORY_PAGE_MESSAGES = 50;
65343
65553
  /** Match DSH's 50-message history window against CFL's rendered projection. */
65344
65554
  function visibleHistoryPage(messages, results, before) {
@@ -65397,6 +65607,19 @@ function turnTime(value, fallback) {
65397
65607
  if (number === null) return fallback;
65398
65608
  return number > 1e10 ? Math.round(number) : Math.round(number * 1e3);
65399
65609
  }
65610
+ function completedTurnTime(value) {
65611
+ const raw = numberOrNull(value);
65612
+ if (raw === null) return void 0;
65613
+ return raw > 1e10 ? Math.round(raw) : Math.round(raw * 1e3);
65614
+ }
65615
+ function inputTimeContext(source, now) {
65616
+ const date = new Date(now);
65617
+ const time = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
65618
+ return {
65619
+ kind: "application",
65620
+ value: `${source === "owner" ? "Current owner input" : "Qualifying Keet input"} received around local ${time}. Trusted delivery metadata; not user-authored text or an instruction.`
65621
+ };
65622
+ }
65400
65623
  function textFromUserItem(item) {
65401
65624
  if (item.type !== "userMessage" || !Array.isArray(item.content)) return void 0;
65402
65625
  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 +65879,9 @@ async function createPartner(config, credentials, dependencies = {}) {
65656
65879
  let keetFeed;
65657
65880
  let keetReconnect;
65658
65881
  let keetConnecting = false;
65882
+ const pet = config.pet.enabled ? new PetActivityProjection(() => {
65883
+ if (!closing) notify();
65884
+ }) : void 0;
65659
65885
  const notify = () => {
65660
65886
  for (const listener of listeners) listener();
65661
65887
  };
@@ -65711,7 +65937,7 @@ async function createPartner(config, credentials, dependencies = {}) {
65711
65937
  }
65712
65938
  async function refreshBootstrap() {
65713
65939
  const history = (await readRelationshipJournal(paths.relationshipJournal)).reverse();
65714
- const rounds = selectTextRounds([...turns.values()], CONTEXT_TOKEN_BUDGET, 5);
65940
+ const rounds = selectTextRounds([...turns.values()], CONTEXT_TOKEN_BUDGET, config.codex.context_round_limit);
65715
65941
  await writeBootstrapFile(bootstrapPath(paths.workspaceRoot), {
65716
65942
  startupPending,
65717
65943
  context: formatBootstrapContext(stateFromHistory(history)),
@@ -65958,6 +66184,7 @@ async function createPartner(config, credentials, dependencies = {}) {
65958
66184
  const voiceIds = voiceIdsFromTurn(turn);
65959
66185
  let resultError = errorFromTurn(turn);
65960
66186
  let resultStatus = status;
66187
+ const completedAt = status === "completed" ? completedTurnTime(turn.completedAt) : void 0;
65961
66188
  for (const item of turn.items ?? []) {
65962
66189
  if (item.type !== "imageGeneration") continue;
65963
66190
  const generatedId = await projectGeneratedImage(resultOperationId(turn.id), sourceIds, item);
@@ -65974,7 +66201,8 @@ async function createPartner(config, credentials, dependencies = {}) {
65974
66201
  error: resultError,
65975
66202
  status: resultStatus,
65976
66203
  generatedIds,
65977
- voiceIds
66204
+ voiceIds,
66205
+ ...completedAt === void 0 ? {} : { completedAt }
65978
66206
  };
65979
66207
  const fingerprint = JSON.stringify(resultBody);
65980
66208
  const previous = presentationFingerprints.get(turn.id);
@@ -66056,7 +66284,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66056
66284
  const turn = registerTurn((await appServer.call("turn/start", {
66057
66285
  threadId,
66058
66286
  input: nativeInput(intent.input, intent.images),
66059
- clientUserMessageId: intent.transportId
66287
+ clientUserMessageId: intent.transportId,
66288
+ ...intent.source !== "none" ? { additionalContext: { "codex-for-love.message-time": inputTimeContext(intent.source, now()) } } : {}
66060
66289
  })).turn, intent.ids);
66061
66290
  if (!turn) throw new Error("Codex app-server did not return a turn");
66062
66291
  intent.turnId = turn.id;
@@ -66100,7 +66329,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66100
66329
  threadId,
66101
66330
  input: nativeInput(intent.input, intent.images),
66102
66331
  clientUserMessageId: intent.transportId,
66103
- expectedTurnId: expected
66332
+ expectedTurnId: expected,
66333
+ ...intent.source !== "none" ? { additionalContext: { "codex-for-love.message-time": inputTimeContext(intent.source, now()) } } : {}
66104
66334
  });
66105
66335
  intent.turnId = response.turnId;
66106
66336
  mergeTurnInputIds(response.turnId, intent.ids);
@@ -66155,7 +66385,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66155
66385
  ids,
66156
66386
  input: intents.map((intent) => intent.input).join("\n"),
66157
66387
  images: intents.flatMap((intent) => intent.images),
66158
- transportId: clientIdForInputs(ids)
66388
+ transportId: clientIdForInputs(ids),
66389
+ source: intents.every((intent) => intent.source === "owner") ? "owner" : "none"
66159
66390
  };
66160
66391
  recoveryPromise = (async () => {
66161
66392
  try {
@@ -66192,7 +66423,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66192
66423
  ids: [first.id],
66193
66424
  input: payload.input,
66194
66425
  images: payload.images,
66195
- transportId: first.id
66426
+ transportId: first.id,
66427
+ source: "keet"
66196
66428
  });
66197
66429
  } catch (error) {
66198
66430
  if (!closing) processError("keet.admission_failed", error, { operationId: first.id });
@@ -66289,7 +66521,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66289
66521
  ids: unconsumed,
66290
66522
  input: "",
66291
66523
  images: [],
66292
- transportId: "restore"
66524
+ transportId: "restore",
66525
+ source: "none"
66293
66526
  });
66294
66527
  }
66295
66528
  async function interruptActiveTurn() {
@@ -66372,6 +66605,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66372
66605
  break;
66373
66606
  }
66374
66607
  case "turn/started": {
66608
+ pet?.turnStarted();
66375
66609
  const turn = registerTurn(params.turn);
66376
66610
  if (turn) {
66377
66611
  const reconciled = userItems(turn).length || sourceIdsForTurn(turn.id).length ? await reconcileTurn(turn.id) : turn;
@@ -66383,6 +66617,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66383
66617
  }
66384
66618
  case "item/started": {
66385
66619
  const item = record(params.item);
66620
+ pet?.itemStarted(item);
66386
66621
  const turnId = typeof params.turnId === "string" ? params.turnId : "";
66387
66622
  if (turnId && item.type === "imageGeneration") {
66388
66623
  mergeItem(turnId, item);
@@ -66400,6 +66635,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66400
66635
  }
66401
66636
  case "item/completed": {
66402
66637
  const item = record(params.item);
66638
+ pet?.itemCompleted(item);
66403
66639
  const turnId = typeof params.turnId === "string" ? params.turnId : "";
66404
66640
  if (turnId) mergeItem(turnId, item);
66405
66641
  if (turnId && [
@@ -66419,6 +66655,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66419
66655
  }
66420
66656
  case "turn/completed": {
66421
66657
  const turn = registerTurn(params.turn);
66658
+ pet?.turnCompleted(turn?.status);
66422
66659
  let reconciled = turn;
66423
66660
  if (turn && (userItems(turn).length || sourceIdsForTurn(turn.id).length)) {
66424
66661
  reconciled = await reconcileTurn(turn.id);
@@ -66678,6 +66915,9 @@ async function createPartner(config, credentials, dependencies = {}) {
66678
66915
  }
66679
66916
  },
66680
66917
  avatar: (kind) => avatars[kind],
66918
+ async petAsset(activity) {
66919
+ return pet ? localPetClip(config.state, activity) : void 0;
66920
+ },
66681
66921
  async snapshot(options = {}) {
66682
66922
  await eventChain;
66683
66923
  const page = options.after === void 0 ? visibleHistoryPage(await store.allMessages(), [...turnResults.values()], options.before) : await store.messagePage(options);
@@ -66734,7 +66974,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66734
66974
  voices: result.voiceIds.map((id) => ({
66735
66975
  id,
66736
66976
  url: `/api/audio/${id}.mp3`
66737
- }))
66977
+ })),
66978
+ ...result.completedAt === void 0 ? {} : { completedAt: result.completedAt }
66738
66979
  }));
66739
66980
  const unresolved = await unresolvedMessages();
66740
66981
  const context = await store.observedContext();
@@ -66766,6 +67007,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66766
67007
  pendingCount: pendingIds.size,
66767
67008
  cancellable,
66768
67009
  typing: activeTurnId !== null,
67010
+ ...pet ? { pet: pet.snapshot() } : {},
66769
67011
  messages,
66770
67012
  results,
66771
67013
  draft: restoredDraft ? {
@@ -66804,7 +67046,8 @@ async function createPartner(config, credentials, dependencies = {}) {
66804
67046
  ids: [id],
66805
67047
  input,
66806
67048
  images: materialized,
66807
- transportId: id
67049
+ transportId: id,
67050
+ source: "owner"
66808
67051
  };
66809
67052
  if (activeTurnId) await steerIntent(intent, activeTurnId);
66810
67053
  else await startIntent(intent);
@@ -66877,6 +67120,7 @@ async function createPartner(config, credentials, dependencies = {}) {
66877
67120
  close() {
66878
67121
  if (closePromise) return closePromise;
66879
67122
  closing = true;
67123
+ pet?.close();
66880
67124
  if (keetReconnect) clearTimeout(keetReconnect);
66881
67125
  keetFeed?.close();
66882
67126
  for (const unsubscribe of unsubscribeAppServer.splice(0)) unsubscribe();
@@ -67674,6 +67918,22 @@ function createWebServer(partner, assets, options = {}) {
67674
67918
  response.end(avatar.data);
67675
67919
  return;
67676
67920
  }
67921
+ if (path.startsWith("/api/pet-assets/") && request.method === "GET") {
67922
+ const activity = path.slice(16);
67923
+ if (!PET_ACTIVITIES.includes(activity)) return json(response, { error: "Pet asset not found" }, 404);
67924
+ const asset = await partner.petAsset(activity);
67925
+ if (!asset) return json(response, { error: "Pet asset not found" }, 404);
67926
+ const metadata = asset.manifest.clips[activity];
67927
+ response.writeHead(200, {
67928
+ "content-type": "image/webp",
67929
+ "cache-control": "no-store",
67930
+ "x-pet-frame-count": String(metadata.frameCount),
67931
+ "x-pet-fps": String(metadata.fps),
67932
+ "x-pet-loop": String(metadata.loop)
67933
+ });
67934
+ response.end(asset.data);
67935
+ return;
67936
+ }
67677
67937
  if (path === "/api/messages" && request.method === "POST") {
67678
67938
  const parsed = object({
67679
67939
  id: uuid(),
@@ -22694,12 +22694,14 @@ const schema = object({
22694
22694
  version: literal(SUPPORTED_CODEX_VERSION).default(SUPPORTED_CODEX_VERSION),
22695
22695
  home: string().min(1).optional(),
22696
22696
  provenance: string().min(1).optional(),
22697
- local_compaction: boolean().default(false)
22697
+ local_compaction: boolean().default(false),
22698
+ context_round_limit: number().int().min(0).default(10)
22698
22699
  }).strict().default({
22699
22700
  command: "codex-app-server",
22700
22701
  model: DEFAULT_CODEX_MODEL,
22701
22702
  version: SUPPORTED_CODEX_VERSION,
22702
- local_compaction: false
22703
+ local_compaction: false,
22704
+ context_round_limit: 10
22703
22705
  }),
22704
22706
  speech: object({
22705
22707
  endpoint: url().default("https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"),
@@ -22708,7 +22710,8 @@ const schema = object({
22708
22710
  keet: object({
22709
22711
  endpoint: string().min(1).optional(),
22710
22712
  media_root: string().min(1).optional()
22711
- }).strict().optional()
22713
+ }).strict().optional(),
22714
+ pet: object({ enabled: boolean().default(false) }).strict().default({ enabled: false })
22712
22715
  }).strict();
22713
22716
  async function loadConfig(path) {
22714
22717
  const config = schema.parse(parse(await readFile(path, "utf8")));