@lamplitisles/codex-for-love 0.4.2 → 0.5.0

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.
@@ -1,7 +1,8 @@
1
1
  import process$1 from "node:process";
2
- import { createHash, randomInt, randomUUID } from "node:crypto";
3
- import { mkdir, readFile, rename, rmdir, unlink, writeFile } from "node:fs/promises";
4
2
  import { dirname, join, resolve } from "node:path";
3
+ import { createHash, randomInt, randomUUID } from "node:crypto";
4
+ import { access, mkdir, readFile, rename, rmdir, unlink, writeFile } from "node:fs/promises";
5
+ import { isIP } from "node:net";
5
6
  import { execFile } from "node:child_process";
6
7
  import { promisify } from "node:util";
7
8
  //#region \0rolldown/runtime.js
@@ -21885,7 +21886,8 @@ function partnerPaths(workspace) {
21885
21886
  database: join(managedRoot, "session.sqlite"),
21886
21887
  relationshipJournal: join(managedRoot, "relationship.jsonl"),
21887
21888
  attachments: join(managedRoot, "attachments"),
21888
- audio: join(managedRoot, "audio")
21889
+ audio: join(managedRoot, "audio"),
21890
+ conversationImages: join(managedRoot, "conversation-images.json")
21889
21891
  };
21890
21892
  }
21891
21893
  //#endregion
@@ -22688,12 +22690,12 @@ const schema = object({
22688
22690
  user: string().min(1)
22689
22691
  }).strict().optional(),
22690
22692
  port: number().int().min(1024).max(65535).default(3082),
22693
+ listen_host: string().refine((value) => isIP(value) === 4, "Listen host must be an IPv4 address").default("127.0.0.1"),
22691
22694
  codex: object({
22692
22695
  command: string().min(1).default("codex-app-server"),
22693
22696
  model: string().min(1).default(DEFAULT_CODEX_MODEL),
22694
22697
  version: literal(SUPPORTED_CODEX_VERSION).default(SUPPORTED_CODEX_VERSION),
22695
22698
  home: string().min(1).optional(),
22696
- provenance: string().min(1).optional(),
22697
22699
  local_compaction: boolean().default(false),
22698
22700
  context_round_limit: number().int().min(0).default(10)
22699
22701
  }).strict().default({
@@ -22728,8 +22730,7 @@ async function loadConfig(path) {
22728
22730
  } : void 0,
22729
22731
  codex: {
22730
22732
  ...config.codex,
22731
- home: config.codex.home ? resolve(base, config.codex.home) : void 0,
22732
- provenance: config.codex.provenance ? resolve(base, config.codex.provenance) : void 0
22733
+ home: config.codex.home ? resolve(base, config.codex.home) : void 0
22733
22734
  },
22734
22735
  keet: config.keet ? {
22735
22736
  ...config.keet.endpoint ? { endpoint: keetEndpoint(config.keet.endpoint) } : {},
@@ -22950,11 +22951,72 @@ async function synthesizeSpeech(options) {
22950
22951
  }
22951
22952
  }
22952
22953
  //#endregion
22954
+ //#region apps/partner/runtime/conversation-images.ts
22955
+ const CURSOR_BYTES = 40;
22956
+ const CURSOR_TOKEN = /^[A-Za-z0-9_-]{54}$/u;
22957
+ const IMAGE_ID = /^[a-f0-9]{64}$/u;
22958
+ function cursorFor(image) {
22959
+ if (!Number.isSafeInteger(image.created) || image.created < 0 || !IMAGE_ID.test(image.id)) throw new Error("Invalid conversation image cursor data");
22960
+ const value = Buffer.alloc(CURSOR_BYTES);
22961
+ value.writeBigUInt64BE(BigInt(image.created));
22962
+ Buffer.from(image.id, "hex").copy(value, 8);
22963
+ return value.toString("base64url");
22964
+ }
22965
+ function readCursor(cursor) {
22966
+ if (!cursor) return void 0;
22967
+ if (!CURSOR_TOKEN.test(cursor)) return void 0;
22968
+ try {
22969
+ const value = Buffer.from(cursor, "base64url");
22970
+ if (value.length !== CURSOR_BYTES) return void 0;
22971
+ const created = Number(value.readBigUInt64BE());
22972
+ if (!Number.isSafeInteger(created)) return void 0;
22973
+ return [created, value.subarray(8).toString("hex")];
22974
+ } catch {
22975
+ return;
22976
+ }
22977
+ }
22978
+ function pageConversationImages(images, limit = 5, cursor) {
22979
+ const after = readCursor(cursor);
22980
+ if (cursor && !after) throw new Error("Invalid conversation image cursor");
22981
+ const ordered = images.filter((image) => !after || image.created < after[0] || image.created === after[0] && image.id < after[1]).sort((a, b) => b.created - a.created || b.id.localeCompare(a.id));
22982
+ const page = ordered.slice(0, limit);
22983
+ return {
22984
+ images: page,
22985
+ ...ordered.length > page.length ? { nextCursor: cursorFor(page.at(-1)) } : {}
22986
+ };
22987
+ }
22988
+ async function readConversationImages(path) {
22989
+ try {
22990
+ const value = JSON.parse(await readFile(path, "utf8"));
22991
+ return Array.isArray(value) ? value : [];
22992
+ } catch (error) {
22993
+ if (error.code === "ENOENT") return [];
22994
+ throw error;
22995
+ }
22996
+ }
22997
+ async function withAvailability(images) {
22998
+ return Promise.all(images.map(async (image) => {
22999
+ try {
23000
+ await access(image.path);
23001
+ return {
23002
+ ...image,
23003
+ available: true
23004
+ };
23005
+ } catch {
23006
+ return {
23007
+ ...image,
23008
+ available: false
23009
+ };
23010
+ }
23011
+ }));
23012
+ }
23013
+ //#endregion
22953
23014
  //#region apps/partner/runtime/companion-mcp.ts
22954
23015
  const workspace = process.argv[2];
22955
23016
  if (!workspace) throw new Error("Companion MCP requires a workspace path");
22956
23017
  const configPath = process.argv[3];
22957
23018
  const journal = partnerPaths(workspace).relationshipJournal;
23019
+ const conversationImages = partnerPaths(workspace).conversationImages;
22958
23020
  const server = new McpServer({
22959
23021
  name: "companion",
22960
23022
  version: "0.1.0"
@@ -22997,6 +23059,30 @@ server.registerTool("read_relationship_history", {
22997
23059
  type: "text",
22998
23060
  text: JSON.stringify((await readRelationshipJournal(journal)).reverse().slice(0, canonicalizeHistoryRead(input)))
22999
23061
  }] }));
23062
+ server.registerTool("list_photos", {
23063
+ description: "List our shared photo library: owner-sent, Partner-generated, and restored historical conversation images, newest first in bounded pages. Returned local paths may be inspected deliberately with native tools.",
23064
+ inputSchema: {
23065
+ limit: number().int().min(1).max(50).optional(),
23066
+ cursor: string().regex(/^[A-Za-z0-9_-]{54}$/u).optional()
23067
+ }
23068
+ }, async ({ limit, cursor }) => {
23069
+ const page = pageConversationImages(await withAvailability(await readConversationImages(conversationImages)), limit ?? 5, cursor);
23070
+ return { content: [{
23071
+ type: "text",
23072
+ text: JSON.stringify({
23073
+ images: page.images.map(({ id, filename, path, created, origin, available }) => ({
23074
+ id,
23075
+ filename,
23076
+ path,
23077
+ directory: dirname(path),
23078
+ created,
23079
+ origin,
23080
+ available
23081
+ })),
23082
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
23083
+ })
23084
+ }] };
23085
+ });
23000
23086
  server.registerTool("roll_dice", {
23001
23087
  description: "Roll dice with an optional modifier and label.",
23002
23088
  inputSchema: {