yaml-flow 7.0.0 → 7.1.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.
Files changed (86) hide show
  1. package/browser/asset-integrity.json +1 -1
  2. package/browser/board-livecards-client.js +1 -1
  3. package/browser/board-livecards-client.js.map +1 -1
  4. package/browser/board-livecards-localstorage.js +5 -5
  5. package/browser/board-livecards-localstorage.js.map +1 -1
  6. package/browser/live-cards.js +3 -1
  7. package/dist/{board-live-cards-public-CW5074xr.d.cts → board-live-cards-public-5n1-syA3.d.cts} +1 -2
  8. package/dist/{board-live-cards-public-hnZo0mAf.d.ts → board-live-cards-public-CK_J8uv0.d.ts} +1 -2
  9. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -2
  10. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -1
  11. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +2 -2
  12. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +2 -2
  13. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -2
  14. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -1
  15. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -1
  16. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -1
  17. package/dist/cli/node/artifacts-store-cli.cjs +5 -5
  18. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -1
  19. package/dist/cli/node/artifacts-store-cli.js +5 -5
  20. package/dist/cli/node/artifacts-store-cli.js.map +1 -1
  21. package/dist/cli/node/board-live-cards-cli.cjs +7 -7
  22. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -1
  23. package/dist/cli/node/board-live-cards-cli.js +7 -7
  24. package/dist/cli/node/board-live-cards-cli.js.map +1 -1
  25. package/dist/cli/node/card-store-cli.cjs +4 -4
  26. package/dist/cli/node/card-store-cli.cjs.map +1 -1
  27. package/dist/cli/node/card-store-cli.js +4 -4
  28. package/dist/cli/node/card-store-cli.js.map +1 -1
  29. package/dist/cli/node/execution-adapter.cjs +1 -1
  30. package/dist/cli/node/execution-adapter.cjs.map +1 -1
  31. package/dist/cli/node/execution-adapter.js +1 -1
  32. package/dist/cli/node/execution-adapter.js.map +1 -1
  33. package/dist/cli/node/fs-board-adapter.cjs +7 -7
  34. package/dist/cli/node/fs-board-adapter.cjs.map +1 -1
  35. package/dist/cli/node/fs-board-adapter.d.cts +2 -2
  36. package/dist/cli/node/fs-board-adapter.d.ts +2 -2
  37. package/dist/cli/node/fs-board-adapter.js +7 -7
  38. package/dist/cli/node/fs-board-adapter.js.map +1 -1
  39. package/dist/cli/node/source-cli-task-executor.cjs +2 -2
  40. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -1
  41. package/dist/cli/node/source-cli-task-executor.js +2 -2
  42. package/dist/cli/node/source-cli-task-executor.js.map +1 -1
  43. package/dist/execution-refs.cjs +2 -2
  44. package/dist/execution-refs.cjs.map +1 -1
  45. package/dist/execution-refs.d.cts +9 -4
  46. package/dist/execution-refs.d.ts +9 -4
  47. package/dist/execution-refs.js +2 -2
  48. package/dist/execution-refs.js.map +1 -1
  49. package/dist/server-runtime/index.cjs +4 -4
  50. package/dist/server-runtime/index.cjs.map +1 -1
  51. package/dist/server-runtime/index.d.cts +3 -3
  52. package/dist/server-runtime/index.d.ts +3 -3
  53. package/dist/server-runtime/index.js +4 -4
  54. package/dist/server-runtime/index.js.map +1 -1
  55. package/dist/step-machine-public/index.cjs +2 -1
  56. package/dist/step-machine-public/index.cjs.map +1 -1
  57. package/dist/step-machine-public/index.d.cts +7 -0
  58. package/dist/step-machine-public/index.d.ts +7 -0
  59. package/dist/step-machine-public/index.js +2 -1
  60. package/dist/step-machine-public/index.js.map +1 -1
  61. package/dist/storage-refs.cjs +2 -2
  62. package/dist/storage-refs.cjs.map +1 -1
  63. package/dist/storage-refs.d.cts +1 -2
  64. package/dist/storage-refs.d.ts +1 -2
  65. package/dist/storage-refs.js +2 -2
  66. package/dist/storage-refs.js.map +1 -1
  67. package/dist/{types-BxEFcVK9.d.cts → types-CU3DjTKL.d.cts} +1 -1
  68. package/dist/{types-B1ZRa4aI.d.ts → types-HGDTWIun.d.ts} +1 -1
  69. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +13 -0
  70. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.py +398 -0
  71. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +4 -4
  72. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +1 -1
  73. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +1 -1
  74. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +1 -1
  75. package/examples/example-board/agent-instructions.md +1 -1
  76. package/examples/example-board/cards/{card-market-prices.json → cardT-market-prices.json} +2 -2
  77. package/examples/example-board/cards/{card-portfolio.json → cardT-portfolio.json} +3 -13
  78. package/examples/example-board/demo-server-config.json +1 -1
  79. package/examples/example-board/demo-server.js +48 -25
  80. package/examples/example-board/demo-shell-localstorage.html +3 -3
  81. package/examples/example-board/demo-shell-with-server.html +2 -2
  82. package/examples/example-board/demo-task-executor.js +4 -8
  83. package/package.json +2 -2
  84. package/step-machine-cli.js +1 -1
  85. package/examples/example-board/cards/_index.json +0 -47
  86. /package/examples/example-board/cards/{card-portfolio-value.json → cardT-portfolio-value.json} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/common/storage-interface.ts","../../../src/cli/node/storage-fs-adapters.ts","../../../src/cli/common/artifacts-store-lib.ts","../../../src/cli/common/artifacts-store-lib-public.ts","../../../src/cli/node/process-runner.ts","../../../src/cli/node/artifacts-store-cli.ts"],"names":["REF_PREFIX","fromBase64Url","input","base64","buf","binary","bytes","i","parseRef","s","parsed","candidate","renameSync","src","dest","delays","err","code","createFsBlobStorage","rootDir","resolve","key","toKey","fullPath","walk","dir","out","entry","p","content","tmp","randomUUID","prefix","all","sorted","k","st","INDEX_KEY","nowIso","utf8ByteLength","text","loadIndex","blob","raw","saveIndex","index","statToInfo","stat","updateIndex","info","createArtifactsStore","head","fromStat","contentType","envelope","infoByKey","a","b","createArtifactsStorePublic","store","ok","data","fail","error","oops","e","body","byteValues","n","as","resolvePath","segments","requireFlag","args","flag","usage","idx","val","optFlag","readStdinBytes","parts","chunk","HELP","cli","argv","cmd","rest","ref","root","filePath","x","result","outPath","isMain","msg"],"mappings":"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","file":"artifacts-store-cli.js","sourcesContent":["/**\n * storage-interface.ts\n *\n * Three minimal storage primitives that together cover all persistence needs\n * of the board-live-cards system. Any backend (Node fs, CosmosDB, Azure Blob,\n * browser localStorage, in-memory test double) implements these three interfaces.\n *\n * The pure-logic stores in board-live-cards-all-stores.ts depend only on these\n * interfaces — never on Node built-ins.\n *\n * Blob — raw string content at a logical, backend-neutral key\n * Journal — append-only log with cursor-based reads\n * KV — key-value store with list/delete\n *\n * Mapping to existing storage adapters:\n *\n * CardStorageAdapter\n * inventory (cardId → { blobRef, checksum, fileMetadata? }) → KV\n * card JSON files → Blob\n * source output files → Blob\n *\n * JournalStorageAdapter → Journal (board-journal.jsonl)\n *\n * ExecutionRequestStore → KV (keyed by journalId, via createFsKvStorage)\n *\n * StateSnapshotStorageAdapter\n * board-graph.json (packed single JSON, written atomically) → Blob\n * per-card sidecars (cards/<id>/runtime, fetched-sources-manifest) → KV\n */\n\n// ============================================================================\n// Blob — raw content at an opaque key\n//\n// The key is backend-specific (file path, blob name, storage key).\n// Text helpers are always available. Binary helpers are optional so existing\n// backends can adopt incrementally.\n// ============================================================================\n\nexport interface BlobStat {\n key: string;\n size: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface BlobStorage {\n /** Returns raw content string, or null if the blob does not exist. */\n read(key: string): string | null;\n\n /** Write content at key. Implementations should be atomic (write-rename). */\n write(key: string, content: string): void;\n\n /** Returns true if a blob exists at key. */\n exists(key: string): boolean;\n\n /** Delete the blob at key. No-op if it does not exist. */\n remove(key: string): void;\n\n /** Optional binary read for file-like artifacts. */\n readBytes?(key: string): Uint8Array | null;\n\n /** Optional binary write for file-like artifacts. */\n writeBytes?(key: string, content: Uint8Array): void;\n\n /** Optional key listing by prefix. */\n listKeys?(prefix?: string): string[];\n\n /** Optional metadata lookup. */\n stat?(key: string): BlobStat | null;\n}\n\n// ============================================================================\n// KindValueRef — backend-neutral typed reference\n//\n// A ref describes WHERE content lives without carrying the bytes.\n// Serialized on the CLI wire as: b64:<base64url({\"kind\":\"...\",\"value\":\"...\"})>\n// kind = 'fs-path': value is an absolute file path\n// Additional kinds (e.g. 'cosmos') are added in public-storage-adapter.ts as new backends are supported.\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\nconst REF_PREFIX = 'b64:';\n\nfunction toBase64Url(raw: string): string {\n const utf8 = new TextEncoder().encode(raw);\n const buf = (globalThis as { Buffer?: { from(data: Uint8Array): { toString(enc: string): string } } }).Buffer;\n let base64: string;\n if (buf) {\n base64 = buf.from(utf8).toString('base64');\n } else if (typeof btoa === 'function') {\n let binary = '';\n for (const byte of utf8) binary += String.fromCharCode(byte);\n base64 = btoa(binary);\n } else {\n throw new Error('No base64 encoder available in this runtime');\n }\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction fromBase64Url(input: string): string {\n const base64 = input.replace(/-/g, '+').replace(/_/g, '/')\n + '='.repeat((4 - (input.length % 4)) % 4);\n const buf = (globalThis as { Buffer?: { from(data: string, enc: string): { toString(enc: string): string } } }).Buffer;\n if (buf) return buf.from(base64, 'base64').toString('utf8');\n if (typeof atob === 'function') {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n throw new Error('No base64 decoder available in this runtime');\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `${REF_PREFIX}${toBase64Url(JSON.stringify(ref))}`;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef.\n * Also accepts the legacy ::fs-path::<path> format for backward compatibility. */\nexport function parseRef(s: string): KindValueRef {\n // Legacy format: ::fs-path::<path>\n if (s.startsWith('::fs-path::')) {\n return { kind: 'fs-path', value: s.slice('::fs-path::'.length) };\n }\n if (!s.startsWith(REF_PREFIX)) throw new Error(`Invalid ref format (expected ${REF_PREFIX}<base64url(json)>): ${s}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(s.slice(REF_PREFIX.length)));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = parsed as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n// ============================================================================\n// Journal — append-only log, cursor-based reads\n//\n// Each entry has a string id (UUID or monotonic token) and an opaque payload.\n// Cursors are entry ids — readAfter returns entries strictly after that id.\n// A null/empty cursor means \"read from the beginning\".\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n payload: unknown;\n}\n\nexport interface JournalReadResult {\n entries: JournalEntry[];\n /** The id of the last entry returned, suitable for use as the next cursor. */\n newCursor: string | null;\n}\n\nexport interface JournalStorage {\n /** Append an entry. The storage layer assigns the id. */\n append(payload: unknown): JournalEntry;\n\n /** Read ALL entries (for index rebuilds, full replay). */\n readAll(): JournalEntry[];\n\n /**\n * Read entries appended after the given cursor id.\n * If cursor is null/empty, returns all entries from the beginning.\n */\n readAfter(cursor: string | null): JournalReadResult;\n}\n\n// ============================================================================\n// KV — key-value store with list and delete\n//\n// Values are opaque unknown — callers own serialisation.\n// Keys are scoped by the adapter factory (e.g. a boardDir prefix is closed\n// over in the adapter, not passed per-call).\n// ============================================================================\n\nexport interface KVStorage {\n /** Returns the stored value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /**\n * List all keys, optionally filtered to those starting with prefix.\n * Order is implementation-defined.\n */\n listKeys(prefix?: string): string[];\n}\n\n// ============================================================================\n// JSONStorage — KV store with JSON-aware merge operations\n//\n// Backed by KVStorage under the hood. Adds deepMerge and shallowMerge so\n// callers never need to read-modify-write manually for partial updates.\n// ============================================================================\n\nexport interface JSONStorage {\n /** Returns the stored JSON value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /**\n * Read a nested value inside the stored object using a dot-notation path.\n * e.g. get('myKey', 'a.b.c') returns the value at { a: { b: { c: ... } } }.\n * Returns null if the key does not exist or the path cannot be traversed.\n */\n get(key: string, jsonPath: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /** List all keys, optionally filtered by prefix. */\n listKeys(prefix?: string): string[];\n\n /**\n * Shallow-merge patch into the existing object at key.\n * Equivalent to: write(key, { ...read(key), ...patch })\n * Creates the key if it does not exist.\n */\n shallowMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Deep-merge patch into the existing object at key.\n * Recursively merges nested plain objects; arrays and primitives are replaced.\n * Creates the key if it does not exist.\n */\n deepMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Set a nested value inside the stored object using a dot-notation path.\n * e.g. patch('myKey', 'a.b.c', 42) sets { a: { b: { c: 42 } } } into the stored object.\n * Intermediate objects are created if absent. Arrays are not traversed — use integer\n * segments to index into them (e.g. 'items.0.name').\n * Creates the top-level key if it does not exist.\n */\n patch(key: string, jsonPath: string, value: unknown): void;\n}\n\n// ============================================================================\n// StorageProvider — aggregate of all three primitives\n//\n// Adapter factories receive a StorageProvider and close over any scope (e.g.\n// boardDir) themselves. This is the single injection point for swapping\n// backends (Node fs → CosmosDB, browser localStorage, test doubles, etc.).\n// ============================================================================\n\nexport interface StorageProvider {\n blob: BlobStorage;\n journal: JournalStorage;\n kv: KVStorage;\n}\n\n// ============================================================================\n// AtomicRelayLock — non-blocking try-acquire lock with relay-on-busy semantics\n//\n// This interface serves TWO tightly coupled purposes which are intentionally\n// unified into a single primitive:\n//\n// 1. ATOMICITY — ensures that a read-mutate-save cycle is executed by at\n// most one actor at a time, preventing concurrent actors from racing on\n// stale state and writing conflicting snapshots.\n//\n// 2. RELAY SIGNAL — when tryAcquire() returns null, the caller knows the\n// cycle is already in progress. Because the holder always reads fresh\n// state upon entry, it will pick up every change appended by the skipping\n// caller before the lock was attempted. The caller can therefore safely\n// exit — its work will be completed by the holder. This is the\n// \"relay baton\" pattern: the lock being held IS the in-progress signal.\n//\n// These two purposes are not an accidental overload — they are the same\n// invariant expressed at different scopes. Any backend implementation\n// (FS lockfile, Cosmos document lease, Azure entity lock, in-memory flag)\n// that satisfies \"at most one holder at a time\" automatically satisfies both.\n//\n// Contract:\n// - tryAcquire() is non-blocking. It never waits.\n// - Returns a release function on success, or null if already held.\n// - The release function must be called exactly once (use try/finally).\n// - Behaviour after calling release() more than once is undefined.\n// ============================================================================\n\nexport interface AtomicRelayLock {\n /**\n * Attempt to acquire the lock without blocking.\n * Returns a `release` function if successful, or `null` if the lock is\n * already held by another actor (relay: that actor will complete the work).\n */\n tryAcquire(): (() => void) | null;\n}\n\n/**\n * Execute `work` under an `AtomicRelayLock`.\n *\n * - If the lock is busy, returns false immediately (relay: the holder will\n * complete the work on behalf of this caller).\n * - If acquired, runs `work` exclusively, releases the lock, then calls\n * `continuation` if provided — allowing the caller to schedule the next\n * cycle (e.g. spawn a detached process) after the lock is free.\n * - Returns true if work ran.\n */\nexport async function withRelayLock(\n lock: AtomicRelayLock,\n work: () => Promise<void>,\n continuation?: () => void,\n): Promise<boolean> {\n const release = lock.tryAcquire();\n if (!release) return false; // relay: holder is already doing the work\n try {\n await work();\n } finally {\n release(); // release before continuation so it can immediately re-acquire\n }\n continuation?.();\n return true;\n}\n","/**\n * storage-fs-adapters.ts\n *\n * Node fs implementations of the three StorageProvider primitives:\n * FsBlobStorage — files under a root directory, key segments → subdirectories\n * FsKvStorage — each key stored as a JSON file under a kv directory\n * FsJournalStorage — append-only JSONL file\n *\n * All three are pure Node — no board-specific logic. They can be composed into\n * a StorageProvider and passed to any adapter factory.\n *\n * blobRef keys and KV keys must be logical (e.g. \"cards/abc123.json\"),\n * not physical fs paths. The adapters resolve them to fs paths internally.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { randomUUID, createHash } from 'crypto';\nimport { lockSync } from 'proper-lockfile';\n\n/**\n * On Windows, renameSync can fail with EPERM/EBUSY when the destination file\n * is held open by another process. Retry with exponential back-off (~280ms max).\n */\nfunction renameSync(src: string, dest: string): void {\n if (process.platform !== 'win32') { fs.renameSync(src, dest); return; }\n const delays = [10, 20, 40, 80, 160];\n for (let i = 0; i <= delays.length; i++) {\n try { fs.renameSync(src, dest); return; } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if ((code === 'EPERM' || code === 'EBUSY') && i < delays.length) {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delays[i]);\n continue;\n }\n throw err;\n }\n }\n}\n\nimport type { GraphEvent } from '../../event-graph/types.js';\nimport type {\n AtomicRelayLock,\n BlobStorage,\n JournalEntry,\n JournalReadResult,\n JournalStorage,\n JSONStorage,\n KVStorage,\n StorageProvider,\n} from '../common/storage-interface.js';\nimport type {\n CardIndex,\n LiveCard,\n StateSnapshotStorageAdapter,\n StateSnapshotReadView,\n} from '../common/board-live-cards-lib.js';\n\n// ============================================================================\n// FsBlobStorage\n//\n// key \"cards/abc123.json\" → <rootDir>/cards/abc123.json\n// write is atomic: write to tmp file then rename.\n// ============================================================================\n\nexport function createFsBlobStorage(rootDir: string): BlobStorage {\n function resolve(key: string): string {\n return path.join(rootDir, ...key.split('/'));\n }\n\n function toKey(fullPath: string): string {\n const rel = path.relative(rootDir, fullPath).replace(/\\\\/g, '/');\n return rel;\n }\n\n function walk(dir: string, out: string[]): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const p = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(p, out);\n continue;\n }\n if (!entry.isFile()) continue;\n out.push(toKey(p));\n }\n }\n\n return {\n read(key: string): string | null {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try { return fs.readFileSync(p, 'utf-8'); } catch { return null; }\n },\n\n write(key: string, content: string): void {\n const p = resolve(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, content, 'utf-8');\n renameSync(tmp, p);\n },\n\n exists(key: string): boolean {\n return fs.existsSync(resolve(key));\n },\n\n remove(key: string): void {\n const p = resolve(key);\n try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch { /* best-effort */ }\n },\n\n readBytes(key: string): Uint8Array | null {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try { return new Uint8Array(fs.readFileSync(p)); } catch { return null; }\n },\n\n writeBytes(key: string, content: Uint8Array): void {\n const p = resolve(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, Buffer.from(content));\n renameSync(tmp, p);\n },\n\n listKeys(prefix?: string): string[] {\n const all: string[] = [];\n walk(rootDir, all);\n const sorted = all.sort();\n if (!prefix) return sorted;\n return sorted.filter((k) => k.startsWith(prefix));\n },\n\n stat(key: string) {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try {\n const st = fs.statSync(p);\n return {\n key,\n size: Number(st.size || 0),\n updatedAt: new Date(st.mtimeMs).toISOString(),\n };\n } catch {\n return null;\n }\n },\n };\n}\n\n/**\n * Create a BlobStorage where the key IS the absolute file path.\n * Implements the full BlobStorage interface (read, write, exists, remove).\n * Use this for operations on known absolute paths (e.g., temp file cleanup).\n */\nexport function createFsAbsolutePathBlobStorage(): BlobStorage {\n return {\n read(key: string): string | null {\n if (!fs.existsSync(key)) return null;\n try { return fs.readFileSync(key, 'utf-8'); } catch { return null; }\n },\n write(key: string, content: string): void {\n const tmp = `${key}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(tmp, content, 'utf-8');\n renameSync(tmp, key);\n },\n exists(key: string): boolean {\n return fs.existsSync(key);\n },\n remove(key: string): void {\n try { if (fs.existsSync(key)) fs.unlinkSync(key); } catch { /* best-effort */ }\n },\n\n readBytes(key: string): Uint8Array | null {\n if (!fs.existsSync(key)) return null;\n try { return new Uint8Array(fs.readFileSync(key)); } catch { return null; }\n },\n\n writeBytes(key: string, content: Uint8Array): void {\n const tmp = `${key}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(tmp, Buffer.from(content));\n renameSync(tmp, key);\n },\n\n stat(key: string) {\n if (!fs.existsSync(key)) return null;\n try {\n const st = fs.statSync(key);\n return {\n key,\n size: Number(st.size || 0),\n updatedAt: new Date(st.mtimeMs).toISOString(),\n };\n } catch {\n return null;\n }\n },\n };\n}\n\n// ============================================================================\n// FsKvStorage\n//\n// key \"cards/abc123/runtime\" → <kvDir>/cards/abc123/runtime.json\n// Values are JSON-serialised on write and parsed on read.\n// listKeys(prefix) does a recursive walk and filters by prefix.\n// ============================================================================\n\nexport function createFsKvStorage(kvDir: string): KVStorage {\n function keyToPath(key: string): string {\n return path.join(kvDir, ...key.split('/')) + '.json';\n }\n\n function walkKeys(dir: string, relPrefix: string, prefix: string | undefined, results: string[]): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n walkKeys(path.join(dir, entry.name), rel, prefix, results);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.json')) continue;\n const key = rel.replace(/\\.json$/, '');\n if (!prefix || key.startsWith(prefix)) results.push(key);\n }\n }\n\n return {\n read(key: string): unknown | null {\n const p = keyToPath(key);\n if (!fs.existsSync(p)) return null;\n try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }\n },\n\n write(key: string, value: unknown): void {\n const p = keyToPath(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, JSON.stringify(value, null, 2), 'utf-8');\n renameSync(tmp, p);\n },\n\n delete(key: string): void {\n const p = keyToPath(key);\n try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch { /* best-effort */ }\n },\n\n listKeys(prefix?: string): string[] {\n const results: string[] = [];\n walkKeys(kvDir, '', prefix, results);\n return results.sort();\n },\n };\n}\n\n// ============================================================================\n// FsJournalStorage\n//\n// Each entry is a JSON line: { \"id\": \"<uuid>\", \"payload\": <any> }\n// readAfter(cursor) returns all entries after the entry with id === cursor.\n// A null/empty cursor returns all entries from the beginning.\n// ============================================================================\n\nexport function createFsJournalStorage(journalPath: string): JournalStorage {\n function readLines(): JournalEntry[] {\n if (!fs.existsSync(journalPath)) return [];\n const content = fs.readFileSync(journalPath, 'utf-8').trim();\n if (!content) return [];\n return content.split('\\n').filter(Boolean).map(l => JSON.parse(l) as JournalEntry);\n }\n\n return {\n append(payload: unknown): JournalEntry {\n const entry: JournalEntry = { id: randomUUID(), payload };\n fs.mkdirSync(path.dirname(journalPath), { recursive: true });\n fs.appendFileSync(journalPath, JSON.stringify(entry) + '\\n', 'utf-8');\n return entry;\n },\n\n readAll(): JournalEntry[] {\n return readLines();\n },\n\n readAfter(cursor: string | null): JournalReadResult {\n const all = readLines();\n if (!cursor) {\n return { entries: all, newCursor: all.length > 0 ? all[all.length - 1].id : null };\n }\n const idx = all.findIndex(e => e.id === cursor);\n const entries = idx === -1 ? all : all.slice(idx + 1);\n return {\n entries,\n newCursor: entries.length > 0 ? entries[entries.length - 1].id : cursor,\n };\n },\n };\n}\n\n// ============================================================================\n// createFsStorageProvider\n//\n// Convenience factory that wires up all three fs adapters under a board directory:\n// blob → boardDir (card/source blobs resolved relative to boardDir)\n// kv → boardDir/.kv/\n// journal → boardDir/<journalFile>\n// ============================================================================\n\n// ============================================================================\n// computeStableJsonHash — canonical content hash for any value\n//\n// Used by card-commands to dedup upserts without needing node:crypto at the\n// pure-logic layer.\n// ============================================================================\n\nfunction stableJson(value: unknown): string {\n if (value === null || value === undefined || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${(value as unknown[]).map(stableJson).join(',')}]`;\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map(k => `${JSON.stringify(k)}:${stableJson(obj[k])}`).join(',')}}`;\n}\n\nexport function computeStableJsonHash(value: unknown): string {\n return createHash('sha256').update(stableJson(value)).digest('hex');\n}\n\n// ============================================================================\n// createFsJsonStorage — KVStorage with JSON-aware merge and patch operations\n// ============================================================================\n\nfunction deepMergeObjects(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(patch)) {\n if (v !== null && typeof v === 'object' && !Array.isArray(v) &&\n result[k] !== null && typeof result[k] === 'object' && !Array.isArray(result[k])) {\n result[k] = deepMergeObjects(result[k] as Record<string, unknown>, v as Record<string, unknown>);\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n\nfunction applyJsonPath(obj: Record<string, unknown>, segments: string[], value: unknown): Record<string, unknown> {\n if (segments.length === 0) return obj;\n const [head, ...tail] = segments;\n if (tail.length === 0) return { ...obj, [head]: value };\n const nested = (obj[head] !== null && typeof obj[head] === 'object' && !Array.isArray(obj[head]))\n ? (obj[head] as Record<string, unknown>)\n : {};\n return { ...obj, [head]: applyJsonPath(nested, tail, value) };\n}\n\nexport function createFsJsonStorage(kvDir: string): JSONStorage {\n const kv = createFsKvStorage(kvDir);\n return {\n read: (key) => kv.read(key),\n get(key, jsonPath) {\n const obj = kv.read(key);\n if (obj === null) return null;\n let current: unknown = obj;\n for (const segment of jsonPath.split('.').filter(Boolean)) {\n if (current === null || typeof current !== 'object' || Array.isArray(current)) return null;\n current = (current as Record<string, unknown>)[segment] ?? null;\n }\n return current ?? null;\n },\n write: (key, value) => kv.write(key, value),\n delete: (key) => kv.delete(key),\n listKeys: (prefix?) => kv.listKeys(prefix),\n shallowMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, { ...existing, ...patch });\n },\n deepMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, deepMergeObjects(existing, patch));\n },\n patch(key, jsonPath, value) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n const segments = jsonPath.split('.').filter(Boolean);\n kv.write(key, applyJsonPath(existing, segments, value));\n },\n };\n}\n\n// ============================================================================\n// createFsJournalStorageAdapter — JournalStorageAdapter backed by a JSONL file\n// ============================================================================\n\nexport function createFsJournalStorageAdapter(boardDir: string): {\n readAllEntries(): { id: string; event: GraphEvent }[];\n appendEntry(entry: { id: string; event: GraphEvent }): void;\n generateId(): string;\n} {\n const journalPath = path.join(boardDir, 'board-journal.jsonl');\n return {\n readAllEntries() {\n if (!fs.existsSync(journalPath)) return [];\n const content = fs.readFileSync(journalPath, 'utf-8').trim();\n if (!content) return [];\n return content.split('\\n').filter(Boolean).map((l) => JSON.parse(l) as { id: string; event: GraphEvent });\n },\n appendEntry(entry) {\n fs.appendFileSync(journalPath, JSON.stringify(entry) + '\\n', 'utf-8');\n },\n generateId() { return randomUUID(); },\n };\n}\n\nexport function createFsStorageProvider(boardDir: string, journalFile: string): StorageProvider {\n return {\n blob: createFsBlobStorage(boardDir),\n kv: createFsKvStorage(path.join(boardDir, '.kv')),\n journal: createFsJournalStorage(path.join(boardDir, journalFile)),\n };\n}\n\n/**\n * FS implementation of AtomicRelayLock.\n * Uses proper-lockfile on the given file path as the lock target.\n * tryAcquire() is non-blocking (retries: 0) — returns null immediately if busy.\n */\nexport function createFsAtomicRelayLock(lockTargetPath: string): AtomicRelayLock {\n return {\n tryAcquire() {\n try {\n // proper-lockfile requires the target file to exist before locking.\n if (!fs.existsSync(lockTargetPath)) {\n fs.mkdirSync(path.dirname(lockTargetPath), { recursive: true });\n try { fs.writeFileSync(lockTargetPath, '{}', { flag: 'wx' }); } catch { /* race: another init won */ }\n }\n return lockSync(lockTargetPath, { retries: 0 });\n } catch {\n return null;\n }\n },\n };\n}\n\n// ============================================================================\n// createFsCardStorageAdapter — KV-backed card storage\n// kvDir is the KV storage directory (used directly, no hidden subdirectory added).\n// ============================================================================\n\nexport function createFsCardStorageAdapter(kvDir: string): {\n readIndex(): CardIndex | null;\n writeIndex(index: CardIndex): void;\n readCard(key: string): LiveCard | null;\n writeCard(key: string, card: LiveCard): string;\n cardExists(key: string): boolean;\n defaultCardKey(cardId: string): string;\n} {\n const json = createFsJsonStorage(kvDir);\n return {\n readIndex() {\n return json.read('_index') as CardIndex | null;\n },\n writeIndex(index) {\n json.write('_index', index);\n },\n readCard(id) {\n return json.read(id) as LiveCard | null;\n },\n writeCard(id, card) {\n json.write(id, card);\n return computeStableJsonHash(card);\n },\n cardExists(id) {\n return json.read(id) !== null;\n },\n defaultCardKey(cardId) {\n return cardId;\n },\n };\n}\n\n// ============================================================================\n// createFsStateSnapshotStorageAdapter — KV-backed state snapshot storage\n// Each key stored under <scopeDir>/.state-snapshot/\n// ============================================================================\n\nexport function createFsStateSnapshotStorageAdapter(): StateSnapshotStorageAdapter {\n return {\n readValues(scopeDir: string): StateSnapshotReadView {\n const kv = createFsKvStorage(path.join(scopeDir, '.state-snapshot'));\n const keys = kv.listKeys().sort();\n if (keys.length === 0) return { version: null, values: {} };\n const values: Record<string, unknown> = {};\n for (const key of keys) values[key] = kv.read(key);\n return { version: computeStableJsonHash(values), values };\n },\n writeValues(scopeDir: string, nextValues: Record<string, unknown>, deletedKeys: string[]): string {\n const kv = createFsKvStorage(path.join(scopeDir, '.state-snapshot'));\n for (const key of deletedKeys) kv.delete(key);\n for (const [key, value] of Object.entries(nextValues)) kv.write(key, value);\n return computeStableJsonHash(nextValues);\n },\n };\n}\n","/**\n * artifacts-store-lib.ts\n *\n * Backend-neutral artifact store built on BlobStorage.\n * Supports text + binary content, CRUD, listing, and lightweight metadata.\n */\n\nimport type { BlobStat, BlobStorage } from './storage-interface.js';\n\nexport interface ArtifactInfo {\n key: string;\n size?: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface ArtifactsStore {\n exists(key: string): boolean;\n putText(key: string, content: string, contentType?: string): ArtifactInfo;\n putBytes(key: string, content: Uint8Array, contentType?: string): ArtifactInfo;\n getText(key: string): string | null;\n getBytes(key: string): Uint8Array | null;\n head(key: string): ArtifactInfo | null;\n list(prefix?: string): ArtifactInfo[];\n remove(key: string): void;\n}\n\nexport interface ChatIndexRecord {\n serial: number;\n role: string;\n stored_name: string;\n path: string;\n updated_at?: string | null;\n}\n\nexport interface ChatRecord extends ChatIndexRecord {\n text: string;\n}\n\nexport interface ChatSignal {\n count: number;\n latest_mtime_ms: number;\n processing: boolean;\n}\n\nexport interface ChatArtifactsStore {\n indexKey(cardPrefix: string): string;\n loadIndex(cardPrefix: string): ChatIndexRecord[];\n saveIndex(cardPrefix: string, records: ChatIndexRecord[]): void;\n nextSerial(cardPrefix: string): number;\n appendIndexRecord(cardPrefix: string, record: ChatIndexRecord): void;\n readRecords(cardPrefix: string): ChatRecord[];\n clear(cardPrefix: string): void;\n readSignal(cardPrefix: string): ChatSignal;\n}\n\nexport interface FileArtifactsStore {\n nextSerial(cardPrefix: string, seedNames?: string[]): number;\n buildStoredName(displayName: string, serial: number, opts?: { maxLen?: number }): string;\n allocateStoredName(cardPrefix: string, displayName: string, opts?: { seedNames?: string[]; maxLen?: number }): string;\n}\n\nexport interface CardFileMetadata {\n name: string;\n stored_name: string;\n size: number | null;\n mime_type: string | null;\n path: string | null;\n uploaded_at: string | null;\n}\n\nexport type CardFileLookupResult =\n | { ok: true; file: CardFileMetadata }\n | { ok: false; reason: 'index_out_of_range' | 'missing_stored_name' | 'stale_reference' };\n\nexport interface CardFileMetadataStore {\n read(cardData: unknown): CardFileMetadata[];\n normalizeIncoming(payloadFiles: unknown, defaultUploadedAt?: string): CardFileMetadata[];\n merge(cardData: Record<string, unknown>, incoming: CardFileMetadata[]): CardFileMetadata[];\n resolve(cardData: unknown, index: number, expectedStoredName?: string | null): CardFileLookupResult;\n}\n\nconst INDEX_KEY = '.artifacts-index.json';\n\ninterface ArtifactIndexEntry {\n key: string;\n size?: number;\n updatedAt?: string;\n contentType?: string;\n}\n\ninterface ArtifactIndex {\n entries: Record<string, ArtifactIndexEntry>;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction utf8ByteLength(text: string): number {\n return new TextEncoder().encode(text).byteLength;\n}\n\nfunction loadIndex(blob: BlobStorage): ArtifactIndex {\n const raw = blob.read(INDEX_KEY);\n if (!raw) return { entries: {} };\n try {\n const parsed = JSON.parse(raw) as ArtifactIndex;\n if (parsed && parsed.entries && typeof parsed.entries === 'object') return parsed;\n } catch {\n // fall through\n }\n return { entries: {} };\n}\n\nfunction saveIndex(blob: BlobStorage, index: ArtifactIndex): void {\n blob.write(INDEX_KEY, JSON.stringify(index, null, 2));\n}\n\nfunction statToInfo(stat: BlobStat | null): ArtifactInfo | null {\n if (!stat) return null;\n return {\n key: stat.key,\n size: stat.size,\n updatedAt: stat.updatedAt,\n contentType: stat.contentType,\n };\n}\n\nfunction updateIndex(index: ArtifactIndex, key: string, info: ArtifactInfo): void {\n index.entries[key] = {\n key,\n size: info.size,\n updatedAt: info.updatedAt,\n contentType: info.contentType,\n };\n}\n\nfunction parseLeadingSerial(fileName: string): number {\n const m = String(fileName || '').match(/^(\\d+)[-_]/);\n return m ? parseInt(m[1], 10) : 0;\n}\n\nfunction normalizeDisplayFileName(name: string): string {\n const input = String(name || '').trim();\n if (!input) return 'upload.bin';\n const slash = Math.max(input.lastIndexOf('/'), input.lastIndexOf('\\\\'));\n const base = slash >= 0 ? input.slice(slash + 1) : input;\n return base || 'upload.bin';\n}\n\nfunction normalizeStem(rawStem: string): string {\n const normalized = String(rawStem || '')\n .toLowerCase()\n .replace(/\\s+/g, '_')\n .replace(/[^a-z0-9_-]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '');\n return normalized || 'file';\n}\n\nfunction normalizeExt(rawExt: string): string {\n if (!rawExt || rawExt === '.') return '';\n const extBody = String(rawExt).replace(/^\\./, '').toLowerCase().replace(/[^a-z0-9]/g, '');\n return extBody ? `.${extBody}` : '';\n}\n\nfunction splitBaseExt(name: string): { stem: string; ext: string } {\n const base = normalizeDisplayFileName(name);\n const dot = base.lastIndexOf('.');\n if (dot <= 0 || dot === base.length - 1) return { stem: base, ext: '' };\n return { stem: base.slice(0, dot), ext: base.slice(dot) };\n}\n\nfunction basenameFromKey(key: string): string {\n const slash = key.lastIndexOf('/');\n return slash >= 0 ? key.slice(slash + 1) : key;\n}\n\nexport function createArtifactsStore(blob: BlobStorage): ArtifactsStore {\n function head(key: string): ArtifactInfo | null {\n const fromStat = blob.stat ? statToInfo(blob.stat(key)) : null;\n if (fromStat) return fromStat;\n\n const index = loadIndex(blob);\n const entry = index.entries[key];\n if (entry) return { ...entry };\n\n if (!blob.exists(key)) return null;\n const content = blob.read(key);\n if (content === null) return { key };\n return {\n key,\n size: utf8ByteLength(content),\n };\n }\n\n return {\n exists(key: string): boolean {\n return blob.exists(key);\n },\n\n putText(key: string, content: string, contentType = 'text/plain; charset=utf-8'): ArtifactInfo {\n blob.write(key, content);\n const info = head(key) ?? { key };\n info.contentType = contentType;\n info.updatedAt = info.updatedAt ?? nowIso();\n info.size = info.size ?? utf8ByteLength(content);\n const index = loadIndex(blob);\n updateIndex(index, key, info);\n saveIndex(blob, index);\n return info;\n },\n\n putBytes(key: string, content: Uint8Array, contentType = 'application/octet-stream'): ArtifactInfo {\n if (blob.writeBytes) {\n blob.writeBytes(key, content);\n } else {\n // Fallback for text-only backends.\n const envelope = JSON.stringify({ __kind: 'bytes-array', data: [...content] });\n blob.write(key, envelope);\n }\n const info = head(key) ?? { key };\n info.contentType = contentType;\n info.updatedAt = info.updatedAt ?? nowIso();\n info.size = info.size ?? content.byteLength;\n const index = loadIndex(blob);\n updateIndex(index, key, info);\n saveIndex(blob, index);\n return info;\n },\n\n getText(key: string): string | null {\n const raw = blob.read(key);\n if (raw === null) {\n if (!blob.readBytes) return null;\n const bytes = blob.readBytes(key);\n if (bytes === null) return null;\n return Buffer.from(bytes).toString('utf-8');\n }\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: number[] };\n if (parsed && parsed.__kind === 'bytes-array' && Array.isArray(parsed.data)) {\n return new TextDecoder('utf-8').decode(new Uint8Array(parsed.data));\n }\n } catch {\n // plain text path\n }\n return raw;\n },\n\n getBytes(key: string): Uint8Array | null {\n if (blob.readBytes) {\n const bytes = blob.readBytes(key);\n if (bytes !== null) return bytes;\n }\n const raw = blob.read(key);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: number[] };\n if (parsed && parsed.__kind === 'bytes-array' && Array.isArray(parsed.data)) {\n return new Uint8Array(parsed.data);\n }\n } catch {\n // plain text path\n }\n return new TextEncoder().encode(raw);\n },\n\n head,\n\n list(prefix = ''): ArtifactInfo[] {\n const infoByKey = new Map<string, ArtifactInfo>();\n\n if (blob.listKeys) {\n for (const key of blob.listKeys(prefix)) {\n if (key === INDEX_KEY) continue;\n const info = head(key) ?? { key };\n infoByKey.set(key, info);\n }\n }\n\n const index = loadIndex(blob);\n for (const [key, entry] of Object.entries(index.entries)) {\n if (key === INDEX_KEY || (prefix && !key.startsWith(prefix))) continue;\n if (!infoByKey.has(key)) infoByKey.set(key, { ...entry });\n }\n\n return [...infoByKey.values()].sort((a, b) => a.key.localeCompare(b.key));\n },\n\n remove(key: string): void {\n blob.remove(key);\n const index = loadIndex(blob);\n delete index.entries[key];\n saveIndex(blob, index);\n },\n };\n}\n\nexport function createChatArtifactsStore(\n store: ArtifactsStore,\n opts?: { indexFileName?: string },\n): ChatArtifactsStore {\n const indexFileName = opts?.indexFileName || '.index.json';\n\n function indexKey(cardPrefix: string): string {\n return `${cardPrefix}/${indexFileName}`;\n }\n\n function loadIndex(cardPrefix: string): ChatIndexRecord[] {\n const raw = store.getText(indexKey(cardPrefix));\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed\n .filter((row) => row && typeof row.stored_name === 'string')\n .map((row) => ({\n serial: Number(row.serial || parseLeadingSerial(String(row.stored_name)) || 0),\n role: String(row.role || 'system').toLowerCase(),\n stored_name: String(row.stored_name),\n path: typeof row.path === 'string' ? row.path : `${cardPrefix}/chats/${String(row.stored_name)}`,\n updated_at: typeof row.updated_at === 'string' ? row.updated_at : null,\n }));\n } catch {\n return [];\n }\n }\n\n function saveIndex(cardPrefix: string, records: ChatIndexRecord[]): void {\n store.putText(indexKey(cardPrefix), JSON.stringify(records, null, 2), 'application/json; charset=utf-8');\n }\n\n function nextSerial(cardPrefix: string): number {\n const index = loadIndex(cardPrefix);\n let maxSeen = 0;\n for (const row of index) {\n const serial = Number(row.serial || 0);\n if (Number.isFinite(serial) && serial > maxSeen) maxSeen = serial;\n }\n return maxSeen + 1;\n }\n\n function appendIndexRecord(cardPrefix: string, record: ChatIndexRecord): void {\n const index = loadIndex(cardPrefix);\n index.push(record);\n saveIndex(cardPrefix, index);\n }\n\n function readRecords(cardPrefix: string): ChatRecord[] {\n const index = loadIndex(cardPrefix);\n const out: ChatRecord[] = [];\n for (const row of index) {\n const key = `${cardPrefix}/${row.stored_name}`;\n const text = store.getText(key);\n if (text === null) continue;\n out.push({\n serial: Number(row.serial || parseLeadingSerial(row.stored_name) || 0),\n role: String(row.role || 'system').toLowerCase(),\n text,\n path: typeof row.path === 'string' ? row.path : `${cardPrefix}/chats/${row.stored_name}`,\n stored_name: row.stored_name,\n updated_at: row.updated_at || null,\n });\n }\n out.sort((a, b) => a.serial - b.serial || a.stored_name.localeCompare(b.stored_name));\n return out;\n }\n\n function clear(cardPrefix: string): void {\n const prefix = `${cardPrefix}/`;\n for (const entry of store.list(prefix)) store.remove(entry.key);\n }\n\n function readSignal(cardPrefix: string): ChatSignal {\n const prefix = `${cardPrefix}/`;\n const entries = store.list(prefix);\n let count = 0;\n let latestMtimeMs = 0;\n let processing = false;\n for (const entry of entries) {\n const name = entry.key.slice(prefix.length);\n if (name === '.processing') {\n processing = true;\n continue;\n }\n if (!/^(\\d+)[-_]([a-z0-9_-]+)\\.txt$/i.test(name)) continue;\n count += 1;\n const mtimeMs = entry.updatedAt ? Number(new Date(entry.updatedAt).getTime() || 0) : 0;\n if (mtimeMs > latestMtimeMs) latestMtimeMs = mtimeMs;\n }\n return { count, latest_mtime_ms: latestMtimeMs, processing };\n }\n\n return {\n indexKey,\n loadIndex,\n saveIndex,\n nextSerial,\n appendIndexRecord,\n readRecords,\n clear,\n readSignal,\n };\n}\n\nexport function createFileArtifactsStore(store: ArtifactsStore): FileArtifactsStore {\n function nextSerial(cardPrefix: string, seedNames?: string[]): number {\n let maxSeen = 0;\n const names: string[] = [];\n if (Array.isArray(seedNames)) names.push(...seedNames);\n for (const entry of store.list(`${cardPrefix}/`)) {\n names.push(basenameFromKey(entry.key));\n }\n for (const name of names) {\n const serial = parseLeadingSerial(name);\n if (Number.isFinite(serial) && serial > maxSeen) maxSeen = serial;\n }\n return maxSeen + 1;\n }\n\n function buildStoredName(displayName: string, serial: number, opts?: { maxLen?: number }): string {\n const maxLen = Number(opts?.maxLen || 32);\n const { stem, ext } = splitBaseExt(displayName);\n const safeExt = normalizeExt(ext);\n const safeStem = normalizeStem(stem);\n const prefix = `${String(serial).padStart(3, '0')}-`;\n\n let keepExt = safeExt;\n let stemBudget = maxLen - prefix.length - keepExt.length;\n if (stemBudget < 1) {\n keepExt = '';\n stemBudget = maxLen - prefix.length;\n }\n\n const outStem = safeStem.slice(0, Math.max(1, stemBudget));\n let out = `${prefix}${outStem}${keepExt}`;\n if (out.length > maxLen) out = out.slice(0, maxLen).replace(/\\.$/, '');\n return out;\n }\n\n function allocateStoredName(cardPrefix: string, displayName: string, opts?: { seedNames?: string[]; maxLen?: number }): string {\n let serial = nextSerial(cardPrefix, opts?.seedNames);\n let out = buildStoredName(displayName, serial, { maxLen: opts?.maxLen });\n while (store.exists(`${cardPrefix}/${out}`)) {\n serial += 1;\n out = buildStoredName(displayName, serial, { maxLen: opts?.maxLen });\n }\n return out;\n }\n\n return {\n nextSerial,\n buildStoredName,\n allocateStoredName,\n };\n}\n\nexport function createCardFileMetadataStore(): CardFileMetadataStore {\n function normalizeIncoming(payloadFiles: unknown, defaultUploadedAt?: string): CardFileMetadata[] {\n if (!Array.isArray(payloadFiles)) return [];\n const out: CardFileMetadata[] = [];\n for (const raw of payloadFiles) {\n if (!raw || typeof raw !== 'object') continue;\n const row = raw as Record<string, unknown>;\n if (typeof row.stored_name !== 'string') continue;\n out.push({\n name: typeof row.name === 'string' ? row.name : row.stored_name,\n stored_name: row.stored_name,\n size: typeof row.size === 'number' && Number.isFinite(row.size) ? row.size : null,\n mime_type: typeof row.mime_type === 'string' ? row.mime_type : null,\n path: typeof row.path === 'string' ? row.path : null,\n uploaded_at: typeof row.uploaded_at === 'string' ? row.uploaded_at : (defaultUploadedAt || null),\n });\n }\n return out;\n }\n\n function read(cardData: unknown): CardFileMetadata[] {\n if (!cardData || typeof cardData !== 'object') return [];\n const row = cardData as Record<string, unknown>;\n return normalizeIncoming(row.files, undefined);\n }\n\n function merge(cardData: Record<string, unknown>, incoming: CardFileMetadata[]): CardFileMetadata[] {\n const existing = read(cardData);\n if (incoming.length === 0) {\n cardData.files = existing;\n return existing;\n }\n const known = new Set(existing.map((f) => f.stored_name));\n for (const file of incoming) {\n if (known.has(file.stored_name)) continue;\n existing.push(file);\n known.add(file.stored_name);\n }\n cardData.files = existing;\n return existing;\n }\n\n function resolve(cardData: unknown, index: number, expectedStoredName?: string | null): CardFileLookupResult {\n const files = read(cardData);\n if (!Number.isInteger(index) || index < 0 || index >= files.length) {\n return { ok: false, reason: 'index_out_of_range' };\n }\n const file = files[index];\n if (!file || !file.stored_name) return { ok: false, reason: 'missing_stored_name' };\n if (expectedStoredName && expectedStoredName !== file.stored_name) {\n return { ok: false, reason: 'stale_reference' };\n }\n return { ok: true, file };\n }\n\n return {\n read,\n normalizeIncoming,\n merge,\n resolve,\n };\n}\n","/**\n * artifacts-store-lib-public.ts\n *\n * Public API wrapper for ArtifactsStore, following CommandInput/CommandResult.\n */\n\nimport type { CommandInput, CommandResult } from './board-live-cards-public.js';\nimport type { ArtifactInfo, ArtifactsStore } from './artifacts-store-lib.js';\n\nexport interface ArtifactsStorePublic {\n list(input: CommandInput): CommandResult<{ artifacts: ArtifactInfo[] }>;\n head(input: CommandInput): CommandResult<{ artifact: ArtifactInfo | null }>;\n put(input: CommandInput): CommandResult<{ artifact: ArtifactInfo }>;\n get(input: CommandInput): CommandResult<{ key: string; contentType?: string; size?: number; text?: string; bytes?: number[] }>;\n del(input: CommandInput): CommandResult<{ ok: true }>;\n}\n\nexport function createArtifactsStorePublic(store: ArtifactsStore): ArtifactsStorePublic {\n function ok<T>(data: T): CommandResult<T> {\n return { status: 'success', data } as CommandResult<T>;\n }\n function fail<T>(error: string): CommandResult<T> {\n return { status: 'fail', error } as CommandResult<T>;\n }\n function oops<T>(e: unknown): CommandResult<T> {\n return { status: 'error', error: e instanceof Error ? e.message : String(e) } as CommandResult<T>;\n }\n\n return {\n list(input: CommandInput): CommandResult<{ artifacts: ArtifactInfo[] }> {\n try {\n const prefix = (input.params?.['prefix'] as string | undefined) ?? '';\n return ok({ artifacts: store.list(prefix) });\n } catch (e) { return oops(e); }\n },\n\n head(input: CommandInput): CommandResult<{ artifact: ArtifactInfo | null }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n if (!key) return fail('head requires params.key');\n return ok({ artifact: store.head(key) });\n } catch (e) { return oops(e); }\n },\n\n put(input: CommandInput): CommandResult<{ artifact: ArtifactInfo }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n const contentType = input.params?.['contentType'] as string | undefined;\n if (!key) return fail('put requires params.key');\n\n const body = input.body;\n if (typeof body === 'string') {\n return ok({ artifact: store.putText(key, body, contentType) });\n }\n\n if (body && typeof body === 'object' && typeof (body as { text?: unknown }).text === 'string') {\n return ok({ artifact: store.putText(key, (body as { text: string }).text, contentType) });\n }\n\n if (body && typeof body === 'object' && Array.isArray((body as { bytes?: unknown }).bytes)) {\n const byteValues = (body as { bytes: number[] }).bytes;\n const bytes = new Uint8Array(byteValues.map((n) => Math.max(0, Math.min(255, Number(n) || 0))));\n return ok({ artifact: store.putBytes(key, bytes, contentType) });\n }\n\n return fail('put requires body as string, {text}, or {bytes:number[]}');\n } catch (e) { return oops(e); }\n },\n\n get(input: CommandInput): CommandResult<{ key: string; contentType?: string; size?: number; text?: string; bytes?: number[] }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n const as = (input.params?.['as'] as string | undefined) ?? 'base64';\n if (!key) return fail('get requires params.key');\n\n const head = store.head(key);\n if (!head) return fail(`artifact \"${key}\" not found`);\n\n if (as === 'text') {\n const text = store.getText(key);\n if (text === null) return fail(`artifact \"${key}\" not found`);\n return ok({ key, contentType: head.contentType, size: head.size, text });\n }\n\n const bytes = store.getBytes(key);\n if (bytes === null) return fail(`artifact \"${key}\" not found`);\n return ok({ key, contentType: head.contentType, size: head.size, bytes: [...bytes] });\n } catch (e) { return oops(e); }\n },\n\n del(input: CommandInput): CommandResult<{ ok: true }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n if (!key) return fail('del requires params.key');\n store.remove(key);\n return ok({ ok: true });\n } catch (e) { return oops(e); }\n },\n };\n}\n","/**\n * process-runner.ts — Single source of truth for child process execution.\n *\n * All CLI execution paths (task-executor, source.cli, inference-adapter,\n * detached background workers) route through these helpers.\n *\n * DESIGN:\n * - CommandSpec is the structured command form: { command, args, cwd, env, timeoutMs }\n * - runSync / runAsync use execFileSync / execFile (no ambient shell)\n * - runDetached handles OS differences in one place\n * - parseCommandSpec reads both legacy string form and new { command, args } form\n *\n * WHY NO SHELL BY DEFAULT:\n * - Shell interpretation is platform-dependent (cmd.exe vs /bin/sh vs bash)\n * - Shell parsing of argument strings is fragile and platform-fragile\n * - execFile / execFileSync avoids all quoting and escaping issues\n *\n * BACKWARD COMPAT:\n * - parseCommandSpec(\"node my-tool.js --flag\") → { command: process.execPath, args: ['my-tool.js', '--flag'] }\n * - Legacy .task-executor / .inference-adapter / source.cli string values still load correctly\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as net from 'node:net';\nimport { fileURLToPath } from 'node:url';\nimport { execFileSync, execFile, spawn } from 'node:child_process';\nimport { randomUUID, createHash } from 'node:crypto';\n\nimport type { CommandSpec } from '../../continuous-event-graph/handlers.js';\nimport type { KindValueRef } from '../common/storage-interface.js';\nimport { serializeRef } from '../common/storage-interface.js';\nexport type { CommandSpec };\n\n// ============================================================================\n// makeBoardTempFilePath — board-scoped temp file path for external process handoff\n// ============================================================================\n\n/**\n * Return a unique file path under `<boardDir>/.tmp/` suitable for passing\n * to an external binary (task-executor, inference-adapter) as `--in`, `--out`,\n * or `--err` arguments.\n *\n * - Files are co-located with the board they belong to (not global os.tmpdir()).\n * - The `.tmp/` directory is created on demand.\n * - The file itself is NOT created here — the caller writes it before use.\n * - `ext` defaults to `.json`; use `.txt` for plain-text error files.\n */\nexport function makeBoardTempFilePath(boardDir: string, label: string, ext = '.json'): string {\n const tmpDir = path.join(boardDir, '.tmp');\n fs.mkdirSync(tmpDir, { recursive: true });\n return path.join(tmpDir, `${label}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`);\n}\n\n/** Join path segments — thin wrapper so callers don't need to import node:path. */\nexport function joinPath(...segments: string[]): string { return path.join(...segments); }\n\n/** Resolve a path to absolute — thin wrapper so callers don't need to import node:path. */\nexport function resolvePath(...segments: string[]): string { return path.resolve(...segments); }\n\n/** Return the directory name of a path. */\nexport function dirnamePath(p: string): string { return path.dirname(p); }\n\n/** Return true if the path is absolute. */\nexport function isAbsolutePath(p: string): boolean { return path.isAbsolute(p); }\n\n/** Generate a new random UUID. */\nexport function genUUID(): string { return randomUUID(); }\n\n/** SHA-256 hex hash of a string. */\nexport function getHash(x: string): string { return createHash('sha256').update(x).digest('hex'); }\n\n/** Resolve the directory of an ESM module from its import.meta.url. */\nexport function resolveModuleDir(importMetaUrl: string): string { return path.dirname(fileURLToPath(importMetaUrl)); }\n\n// ============================================================================\n// parseCommandSpec — legacy string or structured CommandSpec → normalized form\n// ============================================================================\n\n/**\n * Parse a legacy string command or pass through a structured CommandSpec.\n *\n * - Legacy string: \"node script.js --flag value\"\n * → { command: process.execPath, args: ['script.js', '--flag', 'value'] }\n *\n * - Structured: { command: 'node', args: ['script.js', '--flag', 'value'] }\n * → { command: process.execPath, args: ['script.js', '--flag', 'value'] }\n *\n * After parsing, 'node'/'node.exe' is resolved to process.execPath, and bare\n * '.js'/'.mjs' paths are wrapped in a node invocation.\n */\nexport function parseCommandSpec(raw: string | CommandSpec): CommandSpec {\n if (typeof raw === 'object' && raw !== null) {\n const { command, args = [], ...rest } = raw;\n const resolved = _resolveNode(command, args);\n return { ...rest, command: resolved.command, args: resolved.args };\n }\n const parts = splitCommandLine(raw);\n if (parts.length === 0) throw new Error(`Empty command spec: ${JSON.stringify(raw)}`);\n return _resolveNode(parts[0], parts.slice(1));\n}\n\nfunction _resolveNode(cmd: string, args: string[]): { command: string; args: string[] } {\n if (/^(node|node\\.exe)$/i.test(cmd)) return { command: process.execPath, args };\n if (/\\.m?js$/i.test(cmd)) return { command: process.execPath, args: [cmd, ...args] };\n return { command: cmd, args };\n}\n\n// ============================================================================\n// splitCommandLine — shell-style string splitting (legacy compat only)\n// ============================================================================\n\n/**\n * Split a shell-style command string into tokens, respecting single/double quotes.\n * Used only for backward-compat parsing of legacy string-format config values.\n */\nexport function splitCommandLine(command: string): string[] {\n const tokens: string[] = [];\n let current = '';\n let quote: '\"' | '\\'' | null = null;\n\n for (const ch of command.trim()) {\n if (quote) {\n if (ch === quote) { quote = null; } else { current += ch; }\n continue;\n }\n if (ch === '\"' || ch === '\\'') { quote = ch; continue; }\n if (/\\s/.test(ch)) {\n if (current) { tokens.push(current); current = ''; }\n continue;\n }\n current += ch;\n }\n\n if (quote) throw new Error(`Unterminated quote in command: ${command}`);\n if (current) tokens.push(current);\n return tokens;\n}\n\n// ============================================================================\n// .cmd/.bat on Windows needs shell: true\n// ============================================================================\n\nfunction _needsWindowsShell(cmd: string): boolean {\n return process.platform === 'win32' && /\\.(cmd|bat)$/i.test(cmd);\n}\n\n// ============================================================================\n// runSync — synchronous process execution\n// ============================================================================\n\n/**\n * Run a command synchronously and return stdout as a string.\n * Uses execFileSync — no ambient shell. Safe on all platforms.\n */\nexport function runSync(spec: CommandSpec, options?: { encoding?: BufferEncoding; input?: string }): string {\n const { command, args = [], cwd, env, timeoutMs } = spec;\n const output = execFileSync(command, args, {\n shell: _needsWindowsShell(command),\n timeout: timeoutMs,\n encoding: options?.encoding ?? 'utf-8',\n cwd,\n windowsHide: true,\n env: env ? { ...process.env, ...env } : undefined,\n input: options?.input,\n });\n return output as string;\n}\n\n// ============================================================================\n// runAsync — async process execution with callback\n// ============================================================================\n\n/**\n * Run a command asynchronously, calling back with (err, stdout, stderr).\n * Uses execFile — no ambient shell. Safe on all platforms.\n */\nexport function runAsync(\n spec: CommandSpec,\n callback: (err: Error | null, stdout: string, stderr: string) => void,\n): void {\n const { command, args = [], cwd, env, timeoutMs = 30_000 } = spec;\n execFile(\n command,\n args,\n {\n shell: _needsWindowsShell(command),\n encoding: 'utf8',\n windowsHide: true,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024,\n cwd,\n env: env ? { ...process.env, ...env } : undefined,\n },\n (err, stdout, stderr) => callback(err ?? null, stdout, stderr),\n );\n}\n\n// ============================================================================\n// Git Bash detection (Windows only — needed for runDetached)\n// ============================================================================\n\nlet _gitBashPath: string | false | undefined;\nconst _GIT_BASH_CACHE = path.join(os.tmpdir(), '.board-live-cards-git-bash-cache.json');\n\nexport function findGitBash(): string | false {\n if (_gitBashPath !== undefined) return _gitBashPath;\n if (process.platform !== 'win32') return (_gitBashPath = false);\n\n try {\n const cached = JSON.parse(fs.readFileSync(_GIT_BASH_CACHE, 'utf8')) as { path: string | false };\n if (cached.path === false || (typeof cached.path === 'string' && fs.existsSync(cached.path))) {\n return (_gitBashPath = cached.path);\n }\n } catch { /* cache miss */ }\n\n const candidates: Array<string | undefined> = [\n process.env.SHELL,\n process.env.PROGRAMFILES\n ? path.join(process.env.PROGRAMFILES, 'Git', 'usr', 'bin', 'bash.exe')\n : undefined,\n process.env.PROGRAMFILES\n ? path.join(process.env.PROGRAMFILES, 'Git', 'bin', 'bash.exe')\n : undefined,\n process.env['PROGRAMFILES(X86)']\n ? path.join(process.env['PROGRAMFILES(X86)']!, 'Git', 'bin', 'bash.exe')\n : undefined,\n process.env.LOCALAPPDATA\n ? path.join(process.env.LOCALAPPDATA, 'Programs', 'Git', 'bin', 'bash.exe')\n : undefined,\n ];\n\n for (const c of candidates) {\n if (c && /bash(\\.exe)?$/i.test(c) && fs.existsSync(c)) {\n _gitBashPath = c;\n try { fs.writeFileSync(_GIT_BASH_CACHE, JSON.stringify({ path: c })); } catch { /* best-effort */ }\n return _gitBashPath;\n }\n }\n\n _gitBashPath = false;\n try { fs.writeFileSync(_GIT_BASH_CACHE, JSON.stringify({ path: false })); } catch { /* best-effort */ }\n return _gitBashPath;\n}\n\nfunction _shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n// ============================================================================\n// runDetached — fire-and-forget background spawn\n// ============================================================================\n\n/**\n * Spawn a detached background process that survives parent exit.\n * Handles Windows (Git Bash / cmd /c start /b) and Linux/macOS transparently.\n */\nexport function runDetached(spec: CommandSpec): void {\n const { command, args = [] } = spec;\n\n if (process.platform === 'win32') {\n const child = spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n windowsHide: true,\n shell: _needsWindowsShell(command),\n });\n child.unref();\n return;\n }\n\n const child = spawn(command, args, { detached: true, stdio: 'ignore' });\n child.unref();\n}\n\n// ============================================================================\n// buildBoardCliInvocation — resolve how to invoke board-live-cards-cli\n//\n// cliDir is the directory containing board-live-cards-cli.ts / .js.\n// Probe order: compiled .js → tsx dev → npx tsx fallback.\n// ============================================================================\n\n/**\n * Return { cmd, args } that invokes `board-live-cards-cli <command> [...args]`\n * in whatever environment is available (compiled dist, dev tsx, npx fallback).\n *\n * Pass `__dirname` (from the calling file's own directory) as `cliDir`.\n */\nexport function buildBoardCliInvocation(\n cliDir: string,\n command: string,\n args: string[],\n): { cmd: string; args: string[] } {\n const jsPath = path.join(cliDir, 'board-live-cards-cli.js');\n if (fs.existsSync(jsPath)) {\n return { cmd: process.execPath, args: [jsPath, command, ...args] };\n }\n\n const tsPath = path.join(cliDir, 'board-live-cards-cli.ts');\n const tsxMjs = path.join(cliDir, '..', '..', 'node_modules', 'tsx', 'dist', 'cli.mjs');\n const tsxBin = path.join(cliDir, '..', '..', 'node_modules', '.bin', 'tsx');\n const tsx = fs.existsSync(tsxMjs) ? tsxMjs : tsxBin;\n if (fs.existsSync(tsPath) && fs.existsSync(tsx)) {\n return { cmd: process.execPath, args: [tsx, tsPath, command, ...args] };\n }\n\n const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';\n return { cmd: npxCmd, args: ['tsx', tsPath, command, ...args] };\n}\n\n// ============================================================================\n// requestProcessAccumulatedDetached — fire-and-forget dispatch of next drain pass\n// ============================================================================\n\n/**\n * Spawn a detached board-live-cards process-accumulated-events pass for the given board.\n */\nexport function requestProcessAccumulatedDetached(cliDir: string, baseRef: KindValueRef, notifyChannel?: string): void {\n const cliArgs = ['--base-ref', serializeRef(baseRef)];\n if (notifyChannel) cliArgs.push('--notify-channel', notifyChannel);\n const { cmd, args } = buildBoardCliInvocation(cliDir, 'process-accumulated-events', cliArgs);\n runDetached({ command: cmd, args });\n}\n\n// ============================================================================\n// Named-pipe event transport (cross-process board notifications)\n// ============================================================================\n\n/** Return canonical named-pipe/socket path for the given channel name. */\nexport function getNamedPipePath(pipeName: string): string {\n if (process.platform === 'win32') return `\\\\\\\\.\\\\pipe\\\\${pipeName}`;\n return path.join(os.tmpdir(), `${pipeName}.sock`);\n}\n\n/**\n * Publish a batch of JSON notifications as newline-delimited records to a named pipe.\n * Best-effort: if the pipe is unavailable, logs via onWarn and drops the batch.\n *\n * All payloads are concatenated into a single `socket.write()` call so the consumer\n * receives them atomically (no interleaving from other drain cycles).\n * Uses a per-pipeName persistent connection so ordering is preserved across calls.\n */\nconst _pipeClients = new Map<string, { socket: net.Socket; ready: boolean; queue: string[] }>();\n\nexport function publishJsonEventsToNamedPipe(\n pipeName: string,\n payloads: unknown[],\n onWarn?: (msg: string) => void,\n): void {\n if (payloads.length === 0) return;\n const chunk = payloads.map(p => JSON.stringify(p)).join('\\n') + '\\n';\n let entry = _pipeClients.get(pipeName);\n\n if (entry && !entry.socket.destroyed) {\n if (entry.ready) {\n entry.socket.write(chunk);\n } else {\n entry.queue.push(chunk);\n }\n return;\n }\n\n // Create a new persistent connection\n const pipePath = getNamedPipePath(pipeName);\n const socket = net.createConnection(pipePath);\n entry = { socket, ready: false, queue: [chunk] };\n _pipeClients.set(pipeName, entry);\n\n socket.on('connect', () => {\n entry!.ready = true;\n for (const queued of entry!.queue) socket.write(queued);\n entry!.queue.length = 0;\n });\n\n socket.on('error', (e) => {\n onWarn?.(`[named-pipe publish] ${pipePath}: ${e instanceof Error ? e.message : String(e)}`);\n _pipeClients.delete(pipeName);\n });\n\n socket.on('close', () => {\n _pipeClients.delete(pipeName);\n });\n}\n\n// ============================================================================\n// createNodeCommandExecutor — Node implementation of CommandExecutor\n//\n// Wraps runSync / runAsync / runDetached / parseCommandSpec / splitCommandLine\n// into a single injectable object. Pass to command handlers instead of the\n// individual execCommandSync / execCommandAsync / resolveCommandInvocation /\n// splitCommandLine / spawnDetachedCommand dep functions.\n// ============================================================================\n\nimport type { CommandExecutor, ExecOptions } from '../common/process-interface.js';\n\nexport function createNodeCommandExecutor(): CommandExecutor {\n return {\n executeSync(cmd: string, args: string[], options?: ExecOptions): string {\n return runSync(\n { command: cmd, args, cwd: options?.cwd, timeoutMs: options?.timeout, env: options?.env as Record<string, string> | undefined },\n { encoding: options?.encoding as BufferEncoding | undefined, input: options?.input },\n );\n },\n executeAsync(cmd: string, args: string[], callback: (err: Error | null, stdout: string, stderr: string) => void): void {\n runAsync({ command: cmd, args }, callback);\n },\n resolveInvocation(rawCmd: string, rawArgs: string[]): { cmd: string; args: string[] } {\n const spec = parseCommandSpec({ command: rawCmd, args: rawArgs });\n return { cmd: spec.command, args: spec.args ?? [] };\n },\n splitCommand: splitCommandLine,\n spawnDetached(cmd: string, args: string[]): void {\n runDetached({ command: cmd, args });\n },\n };\n}\n","/**\n * artifacts-store-cli.ts\n *\n * Thin arg parser for ArtifactsStore public API.\n */\n\nimport * as fs from 'node:fs';\nimport { parseRef } from '../common/storage-interface.js';\nimport { createFsBlobStorage } from './storage-fs-adapters.js';\nimport { createArtifactsStore } from '../common/artifacts-store-lib.js';\nimport { createArtifactsStorePublic } from '../common/artifacts-store-lib-public.js';\nimport { resolvePath } from './process-runner.js';\n\nfunction requireFlag(args: string[], flag: string, usage: string): string {\n const idx = args.indexOf(flag);\n const val = idx !== -1 ? args[idx + 1] : undefined;\n if (!val) throw new Error(`Missing ${flag}\\nUsage: ${usage}`);\n return val;\n}\n\nfunction optFlag(args: string[], flag: string): string | undefined {\n const idx = args.indexOf(flag);\n return idx !== -1 ? args[idx + 1] : undefined;\n}\n\nasync function readStdinBytes(): Promise<Uint8Array> {\n const parts: Buffer[] = [];\n for await (const chunk of process.stdin) {\n parts.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array));\n }\n return new Uint8Array(Buffer.concat(parts));\n}\n\nconst HELP = [\n 'artifacts-store — generic artifact CRUD on a blob-backed store',\n '',\n ' artifacts-store put --store-ref <ref> --key <key> [--file <path> | --text <text>] [--content-type <mime>]',\n ' artifacts-store get --store-ref <ref> --key <key> [--out <path>] [--as text|bytes]',\n ' artifacts-store head --store-ref <ref> --key <key>',\n ' artifacts-store list --store-ref <ref> [--prefix <prefix>]',\n ' artifacts-store del --store-ref <ref> --key <key>',\n].join('\\n');\n\nexport async function cli(argv: string[]): Promise<void> {\n const cmd = argv[0];\n const rest = argv.slice(1);\n\n if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {\n console.error(HELP);\n return;\n }\n\n const ref = requireFlag(rest, '--store-ref', `artifacts-store ${cmd} --store-ref <b64-ref>`);\n const root = parseRef(ref).value;\n const store = createArtifactsStorePublic(createArtifactsStore(createFsBlobStorage(root)));\n\n if (cmd === 'put') {\n const key = requireFlag(rest, '--key', 'artifacts-store put --store-ref <ref> --key <key>');\n const contentType = optFlag(rest, '--content-type');\n const filePath = optFlag(rest, '--file');\n const text = optFlag(rest, '--text');\n\n let body: unknown;\n if (filePath) {\n const bytes = new Uint8Array(fs.readFileSync(filePath));\n body = { bytes: [...bytes] };\n } else if (typeof text === 'string') {\n body = { text };\n } else if (!process.stdin.isTTY) {\n const bytes = await readStdinBytes();\n body = { bytes: [...bytes] };\n } else {\n throw new Error('put requires --file, --text, or stdin bytes');\n }\n\n const result = store.put({ params: { key, ...(contentType ? { contentType } : {}) }, body });\n if (result.status !== 'success') throw new Error(result.error || 'put failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'get') {\n const key = requireFlag(rest, '--key', 'artifacts-store get --store-ref <ref> --key <key>');\n const as = (optFlag(rest, '--as') || 'bytes').toLowerCase();\n const outPath = optFlag(rest, '--out');\n const result = store.get({ params: { key, as } });\n if (result.status !== 'success') throw new Error(result.error || 'get failed');\n\n if (as === 'text') {\n const text = result.data.text ?? '';\n if (outPath) fs.writeFileSync(outPath, text, 'utf-8');\n else process.stdout.write(text);\n return;\n }\n\n const bytes = new Uint8Array(result.data.bytes ?? []);\n if (outPath) fs.writeFileSync(outPath, Buffer.from(bytes));\n else process.stdout.write(JSON.stringify({ ...result.data, bytes: undefined, byteLength: bytes.byteLength }, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'head') {\n const key = requireFlag(rest, '--key', 'artifacts-store head --store-ref <ref> --key <key>');\n const result = store.head({ params: { key } });\n if (result.status !== 'success') throw new Error(result.error || 'head failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'list') {\n const prefix = optFlag(rest, '--prefix') || '';\n const result = store.list({ params: prefix ? { prefix } : {} });\n if (result.status !== 'success') throw new Error(result.error || 'list failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'del' || cmd === 'delete' || cmd === 'rm') {\n const key = requireFlag(rest, '--key', 'artifacts-store del --store-ref <ref> --key <key>');\n const result = store.del({ params: { key } });\n if (result.status !== 'success') throw new Error(result.error || 'del failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n throw new Error(`Unknown command \"${cmd}\"\\n\\n${HELP}`);\n}\n\nconst isMain = process.argv[1] && resolvePath(process.argv[1]) === resolvePath(new URL(import.meta.url).pathname.replace(/^\\/([A-Z]:)/, '$1'));\nif (isMain) {\n cli(process.argv.slice(2)).catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n });\n}\n"]}
1
+ {"version":3,"sources":["../../../src/cli/common/storage-interface.ts","../../../src/cli/node/storage-fs-adapters.ts","../../../src/cli/common/artifacts-store-lib.ts","../../../src/cli/common/artifacts-store-lib-public.ts","../../../src/cli/node/process-runner.ts","../../../src/cli/node/artifacts-store-cli.ts"],"names":["REF_PREFIX","fromBase64Url","input","base64","buf","binary","bytes","i","parseRef","s","parsed","candidate","renameSync","src","dest","delays","err","code","createFsBlobStorage","rootDir","resolve","key","toKey","fullPath","walk","dir","out","entry","p","content","tmp","randomUUID","prefix","all","sorted","k","st","INDEX_KEY","nowIso","utf8ByteLength","text","loadIndex","blob","raw","saveIndex","index","statToInfo","stat","updateIndex","info","createArtifactsStore","head","fromStat","contentType","envelope","infoByKey","a","b","createArtifactsStorePublic","store","ok","data","fail","error","oops","e","body","byteValues","n","as","resolvePath","segments","g","requireFlag","args","flag","usage","idx","val","optFlag","readStdinBytes","parts","chunk","HELP","cli","argv","cmd","rest","ref","root","filePath","x","result","outPath","isMain","msg"],"mappings":"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","file":"artifacts-store-cli.js","sourcesContent":["/**\n * storage-interface.ts\n *\n * Three minimal storage primitives that together cover all persistence needs\n * of the board-live-cards system. Any backend (Node fs, CosmosDB, Azure Blob,\n * browser localStorage, in-memory test double) implements these three interfaces.\n *\n * The pure-logic stores in board-live-cards-all-stores.ts depend only on these\n * interfaces — never on Node built-ins.\n *\n * Blob — raw string content at a logical, backend-neutral key\n * Journal — append-only log with cursor-based reads\n * KV — key-value store with list/delete\n *\n * Mapping to existing storage adapters:\n *\n * CardStorageAdapter\n * inventory (cardId → { blobRef, checksum, fileMetadata? }) → KV\n * card JSON files → Blob\n * source output files → Blob\n *\n * JournalStorageAdapter → Journal (board-journal.jsonl)\n *\n * ExecutionRequestStore → KV (keyed by journalId, via createFsKvStorage)\n *\n * StateSnapshotStorageAdapter\n * board-graph.json (packed single JSON, written atomically) → Blob\n * per-card sidecars (cards/<id>/runtime, fetched-sources-manifest) → KV\n */\n\n// ============================================================================\n// Blob — raw content at an opaque key\n//\n// The key is backend-specific (file path, blob name, storage key).\n// Text helpers are always available. Binary helpers are optional so existing\n// backends can adopt incrementally.\n// ============================================================================\n\nexport interface BlobStat {\n key: string;\n size: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface BlobStorage {\n /** Returns raw content string, or null if the blob does not exist. */\n read(key: string): string | null;\n\n /** Write content at key. Implementations should be atomic (write-rename). */\n write(key: string, content: string): void;\n\n /** Returns true if a blob exists at key. */\n exists(key: string): boolean;\n\n /** Delete the blob at key. No-op if it does not exist. */\n remove(key: string): void;\n\n /** Optional binary read for file-like artifacts. */\n readBytes?(key: string): Uint8Array | null;\n\n /** Optional binary write for file-like artifacts. */\n writeBytes?(key: string, content: Uint8Array): void;\n\n /** Optional key listing by prefix. */\n listKeys?(prefix?: string): string[];\n\n /** Optional metadata lookup. */\n stat?(key: string): BlobStat | null;\n}\n\n// ============================================================================\n// KindValueRef — backend-neutral typed reference\n//\n// A ref describes WHERE content lives without carrying the bytes.\n// Serialized on the CLI wire as: b64:<base64url({\"kind\":\"...\",\"value\":\"...\"})>\n// kind = 'fs-path': value is an absolute file path\n// Additional kinds (e.g. 'cosmos') are added in public-storage-adapter.ts as new backends are supported.\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\nconst REF_PREFIX = 'b64:';\n\nfunction toBase64Url(raw: string): string {\n const utf8 = new TextEncoder().encode(raw);\n const buf = (globalThis as { Buffer?: { from(data: Uint8Array): { toString(enc: string): string } } }).Buffer;\n let base64: string;\n if (buf) {\n base64 = buf.from(utf8).toString('base64');\n } else if (typeof btoa === 'function') {\n let binary = '';\n for (const byte of utf8) binary += String.fromCharCode(byte);\n base64 = btoa(binary);\n } else {\n throw new Error('No base64 encoder available in this runtime');\n }\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction fromBase64Url(input: string): string {\n const base64 = input.replace(/-/g, '+').replace(/_/g, '/')\n + '='.repeat((4 - (input.length % 4)) % 4);\n const buf = (globalThis as { Buffer?: { from(data: string, enc: string): { toString(enc: string): string } } }).Buffer;\n if (buf) return buf.from(base64, 'base64').toString('utf8');\n if (typeof atob === 'function') {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n throw new Error('No base64 decoder available in this runtime');\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `${REF_PREFIX}${toBase64Url(JSON.stringify(ref))}`;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef. */\nexport function parseRef(s: string): KindValueRef {\n if (!s.startsWith(REF_PREFIX)) throw new Error(`Invalid ref format (expected ${REF_PREFIX}<base64url(json)>): ${s}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(s.slice(REF_PREFIX.length)));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = parsed as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n// ============================================================================\n// Journal — append-only log, cursor-based reads\n//\n// Each entry has a string id (UUID or monotonic token) and an opaque payload.\n// Cursors are entry ids — readAfter returns entries strictly after that id.\n// A null/empty cursor means \"read from the beginning\".\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n payload: unknown;\n}\n\nexport interface JournalReadResult {\n entries: JournalEntry[];\n /** The id of the last entry returned, suitable for use as the next cursor. */\n newCursor: string | null;\n}\n\nexport interface JournalStorage {\n /** Append an entry. The storage layer assigns the id. */\n append(payload: unknown): JournalEntry;\n\n /** Read ALL entries (for index rebuilds, full replay). */\n readAll(): JournalEntry[];\n\n /**\n * Read entries appended after the given cursor id.\n * If cursor is null/empty, returns all entries from the beginning.\n */\n readAfter(cursor: string | null): JournalReadResult;\n}\n\n// ============================================================================\n// KV — key-value store with list and delete\n//\n// Values are opaque unknown — callers own serialisation.\n// Keys are scoped by the adapter factory (e.g. a boardDir prefix is closed\n// over in the adapter, not passed per-call).\n// ============================================================================\n\nexport interface KVStorage {\n /** Returns the stored value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /**\n * List all keys, optionally filtered to those starting with prefix.\n * Order is implementation-defined.\n */\n listKeys(prefix?: string): string[];\n}\n\n// ============================================================================\n// JSONStorage — KV store with JSON-aware merge operations\n//\n// Backed by KVStorage under the hood. Adds deepMerge and shallowMerge so\n// callers never need to read-modify-write manually for partial updates.\n// ============================================================================\n\nexport interface JSONStorage {\n /** Returns the stored JSON value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /**\n * Read a nested value inside the stored object using a dot-notation path.\n * e.g. get('myKey', 'a.b.c') returns the value at { a: { b: { c: ... } } }.\n * Returns null if the key does not exist or the path cannot be traversed.\n */\n get(key: string, jsonPath: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /** List all keys, optionally filtered by prefix. */\n listKeys(prefix?: string): string[];\n\n /**\n * Shallow-merge patch into the existing object at key.\n * Equivalent to: write(key, { ...read(key), ...patch })\n * Creates the key if it does not exist.\n */\n shallowMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Deep-merge patch into the existing object at key.\n * Recursively merges nested plain objects; arrays and primitives are replaced.\n * Creates the key if it does not exist.\n */\n deepMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Set a nested value inside the stored object using a dot-notation path.\n * e.g. patch('myKey', 'a.b.c', 42) sets { a: { b: { c: 42 } } } into the stored object.\n * Intermediate objects are created if absent. Arrays are not traversed — use integer\n * segments to index into them (e.g. 'items.0.name').\n * Creates the top-level key if it does not exist.\n */\n patch(key: string, jsonPath: string, value: unknown): void;\n}\n\n// ============================================================================\n// StorageProvider — aggregate of all three primitives\n//\n// Adapter factories receive a StorageProvider and close over any scope (e.g.\n// boardDir) themselves. This is the single injection point for swapping\n// backends (Node fs → CosmosDB, browser localStorage, test doubles, etc.).\n// ============================================================================\n\nexport interface StorageProvider {\n blob: BlobStorage;\n journal: JournalStorage;\n kv: KVStorage;\n}\n\n// ============================================================================\n// AtomicRelayLock — non-blocking try-acquire lock with relay-on-busy semantics\n//\n// This interface serves TWO tightly coupled purposes which are intentionally\n// unified into a single primitive:\n//\n// 1. ATOMICITY — ensures that a read-mutate-save cycle is executed by at\n// most one actor at a time, preventing concurrent actors from racing on\n// stale state and writing conflicting snapshots.\n//\n// 2. RELAY SIGNAL — when tryAcquire() returns null, the caller knows the\n// cycle is already in progress. Because the holder always reads fresh\n// state upon entry, it will pick up every change appended by the skipping\n// caller before the lock was attempted. The caller can therefore safely\n// exit — its work will be completed by the holder. This is the\n// \"relay baton\" pattern: the lock being held IS the in-progress signal.\n//\n// These two purposes are not an accidental overload — they are the same\n// invariant expressed at different scopes. Any backend implementation\n// (FS lockfile, Cosmos document lease, Azure entity lock, in-memory flag)\n// that satisfies \"at most one holder at a time\" automatically satisfies both.\n//\n// Contract:\n// - tryAcquire() is non-blocking. It never waits.\n// - Returns a release function on success, or null if already held.\n// - The release function must be called exactly once (use try/finally).\n// - Behaviour after calling release() more than once is undefined.\n// ============================================================================\n\nexport interface AtomicRelayLock {\n /**\n * Attempt to acquire the lock without blocking.\n * Returns a `release` function if successful, or `null` if the lock is\n * already held by another actor (relay: that actor will complete the work).\n */\n tryAcquire(): (() => void) | null;\n}\n\n/**\n * Execute `work` under an `AtomicRelayLock`.\n *\n * - If the lock is busy, returns false immediately (relay: the holder will\n * complete the work on behalf of this caller).\n * - If acquired, runs `work` exclusively, releases the lock, then calls\n * `continuation` if provided — allowing the caller to schedule the next\n * cycle (e.g. spawn a detached process) after the lock is free.\n * - Returns true if work ran.\n */\nexport async function withRelayLock(\n lock: AtomicRelayLock,\n work: () => Promise<void>,\n continuation?: () => void,\n): Promise<boolean> {\n const release = lock.tryAcquire();\n if (!release) return false; // relay: holder is already doing the work\n try {\n await work();\n } finally {\n release(); // release before continuation so it can immediately re-acquire\n }\n continuation?.();\n return true;\n}\n","/**\n * storage-fs-adapters.ts\n *\n * Node fs implementations of the three StorageProvider primitives:\n * FsBlobStorage — files under a root directory, key segments → subdirectories\n * FsKvStorage — each key stored as a JSON file under a kv directory\n * FsJournalStorage — append-only JSONL file\n *\n * All three are pure Node — no board-specific logic. They can be composed into\n * a StorageProvider and passed to any adapter factory.\n *\n * blobRef keys and KV keys must be logical (e.g. \"cards/abc123.json\"),\n * not physical fs paths. The adapters resolve them to fs paths internally.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { randomUUID, createHash } from 'crypto';\nimport { lockSync } from 'proper-lockfile';\n\n/**\n * On Windows, renameSync can fail with EPERM/EBUSY when the destination file\n * is held open by another process. Retry with exponential back-off (~280ms max).\n */\nfunction renameSync(src: string, dest: string): void {\n if (process.platform !== 'win32') { fs.renameSync(src, dest); return; }\n const delays = [10, 20, 40, 80, 160];\n for (let i = 0; i <= delays.length; i++) {\n try { fs.renameSync(src, dest); return; } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if ((code === 'EPERM' || code === 'EBUSY') && i < delays.length) {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delays[i]);\n continue;\n }\n throw err;\n }\n }\n}\n\nimport type { GraphEvent } from '../../event-graph/types.js';\nimport type {\n AtomicRelayLock,\n BlobStorage,\n JournalEntry,\n JournalReadResult,\n JournalStorage,\n JSONStorage,\n KVStorage,\n StorageProvider,\n} from '../common/storage-interface.js';\nimport type {\n CardIndex,\n LiveCard,\n StateSnapshotStorageAdapter,\n StateSnapshotReadView,\n} from '../common/board-live-cards-lib.js';\n\n// ============================================================================\n// FsBlobStorage\n//\n// key \"cards/abc123.json\" → <rootDir>/cards/abc123.json\n// write is atomic: write to tmp file then rename.\n// ============================================================================\n\nexport function createFsBlobStorage(rootDir: string): BlobStorage {\n function resolve(key: string): string {\n return path.join(rootDir, ...key.split('/'));\n }\n\n function toKey(fullPath: string): string {\n const rel = path.relative(rootDir, fullPath).replace(/\\\\/g, '/');\n return rel;\n }\n\n function walk(dir: string, out: string[]): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const p = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(p, out);\n continue;\n }\n if (!entry.isFile()) continue;\n out.push(toKey(p));\n }\n }\n\n return {\n read(key: string): string | null {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try { return fs.readFileSync(p, 'utf-8'); } catch { return null; }\n },\n\n write(key: string, content: string): void {\n const p = resolve(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, content, 'utf-8');\n renameSync(tmp, p);\n },\n\n exists(key: string): boolean {\n return fs.existsSync(resolve(key));\n },\n\n remove(key: string): void {\n const p = resolve(key);\n try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch { /* best-effort */ }\n },\n\n readBytes(key: string): Uint8Array | null {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try { return new Uint8Array(fs.readFileSync(p)); } catch { return null; }\n },\n\n writeBytes(key: string, content: Uint8Array): void {\n const p = resolve(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, Buffer.from(content));\n renameSync(tmp, p);\n },\n\n listKeys(prefix?: string): string[] {\n const all: string[] = [];\n walk(rootDir, all);\n const sorted = all.sort();\n if (!prefix) return sorted;\n return sorted.filter((k) => k.startsWith(prefix));\n },\n\n stat(key: string) {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try {\n const st = fs.statSync(p);\n return {\n key,\n size: Number(st.size || 0),\n updatedAt: new Date(st.mtimeMs).toISOString(),\n };\n } catch {\n return null;\n }\n },\n };\n}\n\n/**\n * Create a BlobStorage where the key IS the absolute file path.\n * Implements the full BlobStorage interface (read, write, exists, remove).\n * Use this for operations on known absolute paths (e.g., temp file cleanup).\n */\nexport function createFsAbsolutePathBlobStorage(): BlobStorage {\n return {\n read(key: string): string | null {\n if (!fs.existsSync(key)) return null;\n try { return fs.readFileSync(key, 'utf-8'); } catch { return null; }\n },\n write(key: string, content: string): void {\n const tmp = `${key}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(tmp, content, 'utf-8');\n renameSync(tmp, key);\n },\n exists(key: string): boolean {\n return fs.existsSync(key);\n },\n remove(key: string): void {\n try { if (fs.existsSync(key)) fs.unlinkSync(key); } catch { /* best-effort */ }\n },\n\n readBytes(key: string): Uint8Array | null {\n if (!fs.existsSync(key)) return null;\n try { return new Uint8Array(fs.readFileSync(key)); } catch { return null; }\n },\n\n writeBytes(key: string, content: Uint8Array): void {\n const tmp = `${key}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(tmp, Buffer.from(content));\n renameSync(tmp, key);\n },\n\n stat(key: string) {\n if (!fs.existsSync(key)) return null;\n try {\n const st = fs.statSync(key);\n return {\n key,\n size: Number(st.size || 0),\n updatedAt: new Date(st.mtimeMs).toISOString(),\n };\n } catch {\n return null;\n }\n },\n };\n}\n\n// ============================================================================\n// FsKvStorage\n//\n// key \"cards/abc123/runtime\" → <kvDir>/cards/abc123/runtime.json\n// Values are JSON-serialised on write and parsed on read.\n// listKeys(prefix) does a recursive walk and filters by prefix.\n// ============================================================================\n\nexport function createFsKvStorage(kvDir: string): KVStorage {\n function keyToPath(key: string): string {\n return path.join(kvDir, ...key.split('/')) + '.json';\n }\n\n function walkKeys(dir: string, relPrefix: string, prefix: string | undefined, results: string[]): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n walkKeys(path.join(dir, entry.name), rel, prefix, results);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.json')) continue;\n const key = rel.replace(/\\.json$/, '');\n if (!prefix || key.startsWith(prefix)) results.push(key);\n }\n }\n\n return {\n read(key: string): unknown | null {\n const p = keyToPath(key);\n if (!fs.existsSync(p)) return null;\n try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }\n },\n\n write(key: string, value: unknown): void {\n const p = keyToPath(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, JSON.stringify(value, null, 2), 'utf-8');\n renameSync(tmp, p);\n },\n\n delete(key: string): void {\n const p = keyToPath(key);\n try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch { /* best-effort */ }\n },\n\n listKeys(prefix?: string): string[] {\n const results: string[] = [];\n walkKeys(kvDir, '', prefix, results);\n return results.sort();\n },\n };\n}\n\n// ============================================================================\n// FsJournalStorage\n//\n// Each entry is a JSON line: { \"id\": \"<uuid>\", \"payload\": <any> }\n// readAfter(cursor) returns all entries after the entry with id === cursor.\n// A null/empty cursor returns all entries from the beginning.\n// ============================================================================\n\nexport function createFsJournalStorage(journalPath: string): JournalStorage {\n function readLines(): JournalEntry[] {\n if (!fs.existsSync(journalPath)) return [];\n const content = fs.readFileSync(journalPath, 'utf-8').trim();\n if (!content) return [];\n return content.split('\\n').filter(Boolean).map(l => JSON.parse(l) as JournalEntry);\n }\n\n return {\n append(payload: unknown): JournalEntry {\n const entry: JournalEntry = { id: randomUUID(), payload };\n fs.mkdirSync(path.dirname(journalPath), { recursive: true });\n fs.appendFileSync(journalPath, JSON.stringify(entry) + '\\n', 'utf-8');\n return entry;\n },\n\n readAll(): JournalEntry[] {\n return readLines();\n },\n\n readAfter(cursor: string | null): JournalReadResult {\n const all = readLines();\n if (!cursor) {\n return { entries: all, newCursor: all.length > 0 ? all[all.length - 1].id : null };\n }\n const idx = all.findIndex(e => e.id === cursor);\n const entries = idx === -1 ? all : all.slice(idx + 1);\n return {\n entries,\n newCursor: entries.length > 0 ? entries[entries.length - 1].id : cursor,\n };\n },\n };\n}\n\n// ============================================================================\n// createFsStorageProvider\n//\n// Convenience factory that wires up all three fs adapters under a board directory:\n// blob → boardDir (card/source blobs resolved relative to boardDir)\n// kv → boardDir/.kv/\n// journal → boardDir/<journalFile>\n// ============================================================================\n\n// ============================================================================\n// computeStableJsonHash — canonical content hash for any value\n//\n// Used by card-commands to dedup upserts without needing node:crypto at the\n// pure-logic layer.\n// ============================================================================\n\nfunction stableJson(value: unknown): string {\n if (value === null || value === undefined || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${(value as unknown[]).map(stableJson).join(',')}]`;\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map(k => `${JSON.stringify(k)}:${stableJson(obj[k])}`).join(',')}}`;\n}\n\nexport function computeStableJsonHash(value: unknown): string {\n return createHash('sha256').update(stableJson(value)).digest('hex');\n}\n\n// ============================================================================\n// createFsJsonStorage — KVStorage with JSON-aware merge and patch operations\n// ============================================================================\n\nfunction deepMergeObjects(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(patch)) {\n if (v !== null && typeof v === 'object' && !Array.isArray(v) &&\n result[k] !== null && typeof result[k] === 'object' && !Array.isArray(result[k])) {\n result[k] = deepMergeObjects(result[k] as Record<string, unknown>, v as Record<string, unknown>);\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n\nfunction applyJsonPath(obj: Record<string, unknown>, segments: string[], value: unknown): Record<string, unknown> {\n if (segments.length === 0) return obj;\n const [head, ...tail] = segments;\n if (tail.length === 0) return { ...obj, [head]: value };\n const nested = (obj[head] !== null && typeof obj[head] === 'object' && !Array.isArray(obj[head]))\n ? (obj[head] as Record<string, unknown>)\n : {};\n return { ...obj, [head]: applyJsonPath(nested, tail, value) };\n}\n\nexport function createFsJsonStorage(kvDir: string): JSONStorage {\n const kv = createFsKvStorage(kvDir);\n return {\n read: (key) => kv.read(key),\n get(key, jsonPath) {\n const obj = kv.read(key);\n if (obj === null) return null;\n let current: unknown = obj;\n for (const segment of jsonPath.split('.').filter(Boolean)) {\n if (current === null || typeof current !== 'object' || Array.isArray(current)) return null;\n current = (current as Record<string, unknown>)[segment] ?? null;\n }\n return current ?? null;\n },\n write: (key, value) => kv.write(key, value),\n delete: (key) => kv.delete(key),\n listKeys: (prefix?) => kv.listKeys(prefix),\n shallowMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, { ...existing, ...patch });\n },\n deepMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, deepMergeObjects(existing, patch));\n },\n patch(key, jsonPath, value) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n const segments = jsonPath.split('.').filter(Boolean);\n kv.write(key, applyJsonPath(existing, segments, value));\n },\n };\n}\n\n// ============================================================================\n// createFsJournalStorageAdapter — JournalStorageAdapter backed by a JSONL file\n// ============================================================================\n\nexport function createFsJournalStorageAdapter(boardDir: string): {\n readAllEntries(): { id: string; event: GraphEvent }[];\n appendEntry(entry: { id: string; event: GraphEvent }): void;\n generateId(): string;\n} {\n const journalPath = path.join(boardDir, 'board-journal.jsonl');\n return {\n readAllEntries() {\n if (!fs.existsSync(journalPath)) return [];\n const content = fs.readFileSync(journalPath, 'utf-8').trim();\n if (!content) return [];\n return content.split('\\n').filter(Boolean).map((l) => JSON.parse(l) as { id: string; event: GraphEvent });\n },\n appendEntry(entry) {\n fs.appendFileSync(journalPath, JSON.stringify(entry) + '\\n', 'utf-8');\n },\n generateId() { return randomUUID(); },\n };\n}\n\nexport function createFsStorageProvider(boardDir: string, journalFile: string): StorageProvider {\n return {\n blob: createFsBlobStorage(boardDir),\n kv: createFsKvStorage(path.join(boardDir, '.kv')),\n journal: createFsJournalStorage(path.join(boardDir, journalFile)),\n };\n}\n\n/**\n * FS implementation of AtomicRelayLock.\n * Uses proper-lockfile on the given file path as the lock target.\n * tryAcquire() is non-blocking (retries: 0) — returns null immediately if busy.\n */\nexport function createFsAtomicRelayLock(lockTargetPath: string): AtomicRelayLock {\n return {\n tryAcquire() {\n try {\n // proper-lockfile requires the target file to exist before locking.\n if (!fs.existsSync(lockTargetPath)) {\n fs.mkdirSync(path.dirname(lockTargetPath), { recursive: true });\n try { fs.writeFileSync(lockTargetPath, '{}', { flag: 'wx' }); } catch { /* race: another init won */ }\n }\n return lockSync(lockTargetPath, { retries: 0 });\n } catch {\n return null;\n }\n },\n };\n}\n\n// ============================================================================\n// createFsCardStorageAdapter — KV-backed card storage\n// kvDir is the KV storage directory (used directly, no hidden subdirectory added).\n// ============================================================================\n\nexport function createFsCardStorageAdapter(kvDir: string): {\n readIndex(): CardIndex | null;\n writeIndex(index: CardIndex): void;\n readCard(key: string): LiveCard | null;\n writeCard(key: string, card: LiveCard): string;\n cardExists(key: string): boolean;\n defaultCardKey(cardId: string): string;\n} {\n const json = createFsJsonStorage(kvDir);\n return {\n readIndex() {\n return json.read('_index') as CardIndex | null;\n },\n writeIndex(index) {\n json.write('_index', index);\n },\n readCard(id) {\n return json.read(id) as LiveCard | null;\n },\n writeCard(id, card) {\n json.write(id, card);\n return computeStableJsonHash(card);\n },\n cardExists(id) {\n return json.read(id) !== null;\n },\n defaultCardKey(cardId) {\n return cardId;\n },\n };\n}\n\n// ============================================================================\n// createFsStateSnapshotStorageAdapter — KV-backed state snapshot storage\n// Each key stored under <scopeDir>/.state-snapshot/\n// ============================================================================\n\nexport function createFsStateSnapshotStorageAdapter(): StateSnapshotStorageAdapter {\n return {\n readValues(scopeDir: string): StateSnapshotReadView {\n const kv = createFsKvStorage(path.join(scopeDir, '.state-snapshot'));\n const keys = kv.listKeys().sort();\n if (keys.length === 0) return { version: null, values: {} };\n const values: Record<string, unknown> = {};\n for (const key of keys) values[key] = kv.read(key);\n return { version: computeStableJsonHash(values), values };\n },\n writeValues(scopeDir: string, nextValues: Record<string, unknown>, deletedKeys: string[]): string {\n const kv = createFsKvStorage(path.join(scopeDir, '.state-snapshot'));\n for (const key of deletedKeys) kv.delete(key);\n for (const [key, value] of Object.entries(nextValues)) kv.write(key, value);\n return computeStableJsonHash(nextValues);\n },\n };\n}\n","/**\n * artifacts-store-lib.ts\n *\n * Backend-neutral artifact store built on BlobStorage.\n * Supports text + binary content, CRUD, listing, and lightweight metadata.\n */\n\nimport type { BlobStat, BlobStorage } from './storage-interface.js';\n\nexport interface ArtifactInfo {\n key: string;\n size?: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface ArtifactsStore {\n exists(key: string): boolean;\n putText(key: string, content: string, contentType?: string): ArtifactInfo;\n putBytes(key: string, content: Uint8Array, contentType?: string): ArtifactInfo;\n getText(key: string): string | null;\n getBytes(key: string): Uint8Array | null;\n head(key: string): ArtifactInfo | null;\n list(prefix?: string): ArtifactInfo[];\n remove(key: string): void;\n}\n\nexport interface ChatIndexRecord {\n serial: number;\n role: string;\n stored_name: string;\n path: string;\n updated_at?: string | null;\n}\n\nexport interface ChatRecord extends ChatIndexRecord {\n text: string;\n}\n\nexport interface ChatSignal {\n count: number;\n latest_mtime_ms: number;\n processing: boolean;\n}\n\nexport interface ChatArtifactsStore {\n indexKey(cardPrefix: string): string;\n loadIndex(cardPrefix: string): ChatIndexRecord[];\n saveIndex(cardPrefix: string, records: ChatIndexRecord[]): void;\n nextSerial(cardPrefix: string): number;\n appendIndexRecord(cardPrefix: string, record: ChatIndexRecord): void;\n readRecords(cardPrefix: string): ChatRecord[];\n clear(cardPrefix: string): void;\n readSignal(cardPrefix: string): ChatSignal;\n}\n\nexport interface FileArtifactsStore {\n nextSerial(cardPrefix: string, seedNames?: string[]): number;\n buildStoredName(displayName: string, serial: number, opts?: { maxLen?: number }): string;\n allocateStoredName(cardPrefix: string, displayName: string, opts?: { seedNames?: string[]; maxLen?: number }): string;\n}\n\nexport interface CardFileMetadata {\n name: string;\n stored_name: string;\n size: number | null;\n mime_type: string | null;\n path: string | null;\n uploaded_at: string | null;\n}\n\nexport type CardFileLookupResult =\n | { ok: true; file: CardFileMetadata }\n | { ok: false; reason: 'index_out_of_range' | 'missing_stored_name' | 'stale_reference' };\n\nexport interface CardFileMetadataStore {\n read(cardData: unknown): CardFileMetadata[];\n normalizeIncoming(payloadFiles: unknown, defaultUploadedAt?: string): CardFileMetadata[];\n merge(cardData: Record<string, unknown>, incoming: CardFileMetadata[]): CardFileMetadata[];\n resolve(cardData: unknown, index: number, expectedStoredName?: string | null): CardFileLookupResult;\n}\n\nconst INDEX_KEY = '.artifacts-index.json';\n\ninterface ArtifactIndexEntry {\n key: string;\n size?: number;\n updatedAt?: string;\n contentType?: string;\n}\n\ninterface ArtifactIndex {\n entries: Record<string, ArtifactIndexEntry>;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction utf8ByteLength(text: string): number {\n return new TextEncoder().encode(text).byteLength;\n}\n\nfunction loadIndex(blob: BlobStorage): ArtifactIndex {\n const raw = blob.read(INDEX_KEY);\n if (!raw) return { entries: {} };\n try {\n const parsed = JSON.parse(raw) as ArtifactIndex;\n if (parsed && parsed.entries && typeof parsed.entries === 'object') return parsed;\n } catch {\n // fall through\n }\n return { entries: {} };\n}\n\nfunction saveIndex(blob: BlobStorage, index: ArtifactIndex): void {\n blob.write(INDEX_KEY, JSON.stringify(index, null, 2));\n}\n\nfunction statToInfo(stat: BlobStat | null): ArtifactInfo | null {\n if (!stat) return null;\n return {\n key: stat.key,\n size: stat.size,\n updatedAt: stat.updatedAt,\n contentType: stat.contentType,\n };\n}\n\nfunction updateIndex(index: ArtifactIndex, key: string, info: ArtifactInfo): void {\n index.entries[key] = {\n key,\n size: info.size,\n updatedAt: info.updatedAt,\n contentType: info.contentType,\n };\n}\n\nfunction parseLeadingSerial(fileName: string): number {\n const m = String(fileName || '').match(/^(\\d+)[-_]/);\n return m ? parseInt(m[1], 10) : 0;\n}\n\nfunction normalizeDisplayFileName(name: string): string {\n const input = String(name || '').trim();\n if (!input) return 'upload.bin';\n const slash = Math.max(input.lastIndexOf('/'), input.lastIndexOf('\\\\'));\n const base = slash >= 0 ? input.slice(slash + 1) : input;\n return base || 'upload.bin';\n}\n\nfunction normalizeStem(rawStem: string): string {\n const normalized = String(rawStem || '')\n .toLowerCase()\n .replace(/\\s+/g, '_')\n .replace(/[^a-z0-9_-]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '');\n return normalized || 'file';\n}\n\nfunction normalizeExt(rawExt: string): string {\n if (!rawExt || rawExt === '.') return '';\n const extBody = String(rawExt).replace(/^\\./, '').toLowerCase().replace(/[^a-z0-9]/g, '');\n return extBody ? `.${extBody}` : '';\n}\n\nfunction splitBaseExt(name: string): { stem: string; ext: string } {\n const base = normalizeDisplayFileName(name);\n const dot = base.lastIndexOf('.');\n if (dot <= 0 || dot === base.length - 1) return { stem: base, ext: '' };\n return { stem: base.slice(0, dot), ext: base.slice(dot) };\n}\n\nfunction basenameFromKey(key: string): string {\n const slash = key.lastIndexOf('/');\n return slash >= 0 ? key.slice(slash + 1) : key;\n}\n\nexport function createArtifactsStore(blob: BlobStorage): ArtifactsStore {\n function head(key: string): ArtifactInfo | null {\n const fromStat = blob.stat ? statToInfo(blob.stat(key)) : null;\n if (fromStat) return fromStat;\n\n const index = loadIndex(blob);\n const entry = index.entries[key];\n if (entry) return { ...entry };\n\n if (!blob.exists(key)) return null;\n const content = blob.read(key);\n if (content === null) return { key };\n return {\n key,\n size: utf8ByteLength(content),\n };\n }\n\n return {\n exists(key: string): boolean {\n return blob.exists(key);\n },\n\n putText(key: string, content: string, contentType = 'text/plain; charset=utf-8'): ArtifactInfo {\n blob.write(key, content);\n const info = head(key) ?? { key };\n info.contentType = contentType;\n info.updatedAt = info.updatedAt ?? nowIso();\n info.size = info.size ?? utf8ByteLength(content);\n const index = loadIndex(blob);\n updateIndex(index, key, info);\n saveIndex(blob, index);\n return info;\n },\n\n putBytes(key: string, content: Uint8Array, contentType = 'application/octet-stream'): ArtifactInfo {\n if (blob.writeBytes) {\n blob.writeBytes(key, content);\n } else {\n // Fallback for text-only backends.\n const envelope = JSON.stringify({ __kind: 'bytes-array', data: [...content] });\n blob.write(key, envelope);\n }\n const info = head(key) ?? { key };\n info.contentType = contentType;\n info.updatedAt = info.updatedAt ?? nowIso();\n info.size = info.size ?? content.byteLength;\n const index = loadIndex(blob);\n updateIndex(index, key, info);\n saveIndex(blob, index);\n return info;\n },\n\n getText(key: string): string | null {\n const raw = blob.read(key);\n if (raw === null) {\n if (!blob.readBytes) return null;\n const bytes = blob.readBytes(key);\n if (bytes === null) return null;\n return Buffer.from(bytes).toString('utf-8');\n }\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: number[] };\n if (parsed && parsed.__kind === 'bytes-array' && Array.isArray(parsed.data)) {\n return new TextDecoder('utf-8').decode(new Uint8Array(parsed.data));\n }\n } catch {\n // plain text path\n }\n return raw;\n },\n\n getBytes(key: string): Uint8Array | null {\n if (blob.readBytes) {\n const bytes = blob.readBytes(key);\n if (bytes !== null) return bytes;\n }\n const raw = blob.read(key);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: number[] };\n if (parsed && parsed.__kind === 'bytes-array' && Array.isArray(parsed.data)) {\n return new Uint8Array(parsed.data);\n }\n } catch {\n // plain text path\n }\n return new TextEncoder().encode(raw);\n },\n\n head,\n\n list(prefix = ''): ArtifactInfo[] {\n const infoByKey = new Map<string, ArtifactInfo>();\n\n if (blob.listKeys) {\n for (const key of blob.listKeys(prefix)) {\n if (key === INDEX_KEY) continue;\n const info = head(key) ?? { key };\n infoByKey.set(key, info);\n }\n }\n\n const index = loadIndex(blob);\n for (const [key, entry] of Object.entries(index.entries)) {\n if (key === INDEX_KEY || (prefix && !key.startsWith(prefix))) continue;\n if (!infoByKey.has(key)) infoByKey.set(key, { ...entry });\n }\n\n return [...infoByKey.values()].sort((a, b) => a.key.localeCompare(b.key));\n },\n\n remove(key: string): void {\n blob.remove(key);\n const index = loadIndex(blob);\n delete index.entries[key];\n saveIndex(blob, index);\n },\n };\n}\n\nexport function createChatArtifactsStore(\n store: ArtifactsStore,\n opts?: { indexFileName?: string },\n): ChatArtifactsStore {\n const indexFileName = opts?.indexFileName || '.index.json';\n\n function indexKey(cardPrefix: string): string {\n return `${cardPrefix}/${indexFileName}`;\n }\n\n function loadIndex(cardPrefix: string): ChatIndexRecord[] {\n const raw = store.getText(indexKey(cardPrefix));\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed\n .filter((row) => row && typeof row.stored_name === 'string')\n .map((row) => ({\n serial: Number(row.serial || parseLeadingSerial(String(row.stored_name)) || 0),\n role: String(row.role || 'system').toLowerCase(),\n stored_name: String(row.stored_name),\n path: typeof row.path === 'string' ? row.path : `${cardPrefix}/chats/${String(row.stored_name)}`,\n updated_at: typeof row.updated_at === 'string' ? row.updated_at : null,\n }));\n } catch {\n return [];\n }\n }\n\n function saveIndex(cardPrefix: string, records: ChatIndexRecord[]): void {\n store.putText(indexKey(cardPrefix), JSON.stringify(records, null, 2), 'application/json; charset=utf-8');\n }\n\n function nextSerial(cardPrefix: string): number {\n const index = loadIndex(cardPrefix);\n let maxSeen = 0;\n for (const row of index) {\n const serial = Number(row.serial || 0);\n if (Number.isFinite(serial) && serial > maxSeen) maxSeen = serial;\n }\n return maxSeen + 1;\n }\n\n function appendIndexRecord(cardPrefix: string, record: ChatIndexRecord): void {\n const index = loadIndex(cardPrefix);\n index.push(record);\n saveIndex(cardPrefix, index);\n }\n\n function readRecords(cardPrefix: string): ChatRecord[] {\n const index = loadIndex(cardPrefix);\n const out: ChatRecord[] = [];\n for (const row of index) {\n const key = `${cardPrefix}/${row.stored_name}`;\n const text = store.getText(key);\n if (text === null) continue;\n out.push({\n serial: Number(row.serial || parseLeadingSerial(row.stored_name) || 0),\n role: String(row.role || 'system').toLowerCase(),\n text,\n path: typeof row.path === 'string' ? row.path : `${cardPrefix}/chats/${row.stored_name}`,\n stored_name: row.stored_name,\n updated_at: row.updated_at || null,\n });\n }\n out.sort((a, b) => a.serial - b.serial || a.stored_name.localeCompare(b.stored_name));\n return out;\n }\n\n function clear(cardPrefix: string): void {\n const prefix = `${cardPrefix}/`;\n for (const entry of store.list(prefix)) store.remove(entry.key);\n }\n\n function readSignal(cardPrefix: string): ChatSignal {\n const prefix = `${cardPrefix}/`;\n const entries = store.list(prefix);\n let count = 0;\n let latestMtimeMs = 0;\n let processing = false;\n for (const entry of entries) {\n const name = entry.key.slice(prefix.length);\n if (name === '.processing') {\n processing = true;\n continue;\n }\n if (!/^(\\d+)[-_]([a-z0-9_-]+)\\.txt$/i.test(name)) continue;\n count += 1;\n const mtimeMs = entry.updatedAt ? Number(new Date(entry.updatedAt).getTime() || 0) : 0;\n if (mtimeMs > latestMtimeMs) latestMtimeMs = mtimeMs;\n }\n return { count, latest_mtime_ms: latestMtimeMs, processing };\n }\n\n return {\n indexKey,\n loadIndex,\n saveIndex,\n nextSerial,\n appendIndexRecord,\n readRecords,\n clear,\n readSignal,\n };\n}\n\nexport function createFileArtifactsStore(store: ArtifactsStore): FileArtifactsStore {\n function nextSerial(cardPrefix: string, seedNames?: string[]): number {\n let maxSeen = 0;\n const names: string[] = [];\n if (Array.isArray(seedNames)) names.push(...seedNames);\n for (const entry of store.list(`${cardPrefix}/`)) {\n names.push(basenameFromKey(entry.key));\n }\n for (const name of names) {\n const serial = parseLeadingSerial(name);\n if (Number.isFinite(serial) && serial > maxSeen) maxSeen = serial;\n }\n return maxSeen + 1;\n }\n\n function buildStoredName(displayName: string, serial: number, opts?: { maxLen?: number }): string {\n const maxLen = Number(opts?.maxLen || 32);\n const { stem, ext } = splitBaseExt(displayName);\n const safeExt = normalizeExt(ext);\n const safeStem = normalizeStem(stem);\n const prefix = `${String(serial).padStart(3, '0')}-`;\n\n let keepExt = safeExt;\n let stemBudget = maxLen - prefix.length - keepExt.length;\n if (stemBudget < 1) {\n keepExt = '';\n stemBudget = maxLen - prefix.length;\n }\n\n const outStem = safeStem.slice(0, Math.max(1, stemBudget));\n let out = `${prefix}${outStem}${keepExt}`;\n if (out.length > maxLen) out = out.slice(0, maxLen).replace(/\\.$/, '');\n return out;\n }\n\n function allocateStoredName(cardPrefix: string, displayName: string, opts?: { seedNames?: string[]; maxLen?: number }): string {\n let serial = nextSerial(cardPrefix, opts?.seedNames);\n let out = buildStoredName(displayName, serial, { maxLen: opts?.maxLen });\n while (store.exists(`${cardPrefix}/${out}`)) {\n serial += 1;\n out = buildStoredName(displayName, serial, { maxLen: opts?.maxLen });\n }\n return out;\n }\n\n return {\n nextSerial,\n buildStoredName,\n allocateStoredName,\n };\n}\n\nexport function createCardFileMetadataStore(): CardFileMetadataStore {\n function normalizeIncoming(payloadFiles: unknown, defaultUploadedAt?: string): CardFileMetadata[] {\n if (!Array.isArray(payloadFiles)) return [];\n const out: CardFileMetadata[] = [];\n for (const raw of payloadFiles) {\n if (!raw || typeof raw !== 'object') continue;\n const row = raw as Record<string, unknown>;\n if (typeof row.stored_name !== 'string') continue;\n out.push({\n name: typeof row.name === 'string' ? row.name : row.stored_name,\n stored_name: row.stored_name,\n size: typeof row.size === 'number' && Number.isFinite(row.size) ? row.size : null,\n mime_type: typeof row.mime_type === 'string' ? row.mime_type : null,\n path: typeof row.path === 'string' ? row.path : null,\n uploaded_at: typeof row.uploaded_at === 'string' ? row.uploaded_at : (defaultUploadedAt || null),\n });\n }\n return out;\n }\n\n function read(cardData: unknown): CardFileMetadata[] {\n if (!cardData || typeof cardData !== 'object') return [];\n const row = cardData as Record<string, unknown>;\n return normalizeIncoming(row.files, undefined);\n }\n\n function merge(cardData: Record<string, unknown>, incoming: CardFileMetadata[]): CardFileMetadata[] {\n const existing = read(cardData);\n if (incoming.length === 0) {\n cardData.files = existing;\n return existing;\n }\n const known = new Set(existing.map((f) => f.stored_name));\n for (const file of incoming) {\n if (known.has(file.stored_name)) continue;\n existing.push(file);\n known.add(file.stored_name);\n }\n cardData.files = existing;\n return existing;\n }\n\n function resolve(cardData: unknown, index: number, expectedStoredName?: string | null): CardFileLookupResult {\n const files = read(cardData);\n if (!Number.isInteger(index) || index < 0 || index >= files.length) {\n return { ok: false, reason: 'index_out_of_range' };\n }\n const file = files[index];\n if (!file || !file.stored_name) return { ok: false, reason: 'missing_stored_name' };\n if (expectedStoredName && expectedStoredName !== file.stored_name) {\n return { ok: false, reason: 'stale_reference' };\n }\n return { ok: true, file };\n }\n\n return {\n read,\n normalizeIncoming,\n merge,\n resolve,\n };\n}\n","/**\n * artifacts-store-lib-public.ts\n *\n * Public API wrapper for ArtifactsStore, following CommandInput/CommandResult.\n */\n\nimport type { CommandInput, CommandResult } from './board-live-cards-public.js';\nimport type { ArtifactInfo, ArtifactsStore } from './artifacts-store-lib.js';\n\nexport interface ArtifactsStorePublic {\n list(input: CommandInput): CommandResult<{ artifacts: ArtifactInfo[] }>;\n head(input: CommandInput): CommandResult<{ artifact: ArtifactInfo | null }>;\n put(input: CommandInput): CommandResult<{ artifact: ArtifactInfo }>;\n get(input: CommandInput): CommandResult<{ key: string; contentType?: string; size?: number; text?: string; bytes?: number[] }>;\n del(input: CommandInput): CommandResult<{ ok: true }>;\n}\n\nexport function createArtifactsStorePublic(store: ArtifactsStore): ArtifactsStorePublic {\n function ok<T>(data: T): CommandResult<T> {\n return { status: 'success', data } as CommandResult<T>;\n }\n function fail<T>(error: string): CommandResult<T> {\n return { status: 'fail', error } as CommandResult<T>;\n }\n function oops<T>(e: unknown): CommandResult<T> {\n return { status: 'error', error: e instanceof Error ? e.message : String(e) } as CommandResult<T>;\n }\n\n return {\n list(input: CommandInput): CommandResult<{ artifacts: ArtifactInfo[] }> {\n try {\n const prefix = (input.params?.['prefix'] as string | undefined) ?? '';\n return ok({ artifacts: store.list(prefix) });\n } catch (e) { return oops(e); }\n },\n\n head(input: CommandInput): CommandResult<{ artifact: ArtifactInfo | null }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n if (!key) return fail('head requires params.key');\n return ok({ artifact: store.head(key) });\n } catch (e) { return oops(e); }\n },\n\n put(input: CommandInput): CommandResult<{ artifact: ArtifactInfo }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n const contentType = input.params?.['contentType'] as string | undefined;\n if (!key) return fail('put requires params.key');\n\n const body = input.body;\n if (typeof body === 'string') {\n return ok({ artifact: store.putText(key, body, contentType) });\n }\n\n if (body && typeof body === 'object' && typeof (body as { text?: unknown }).text === 'string') {\n return ok({ artifact: store.putText(key, (body as { text: string }).text, contentType) });\n }\n\n if (body && typeof body === 'object' && Array.isArray((body as { bytes?: unknown }).bytes)) {\n const byteValues = (body as { bytes: number[] }).bytes;\n const bytes = new Uint8Array(byteValues.map((n) => Math.max(0, Math.min(255, Number(n) || 0))));\n return ok({ artifact: store.putBytes(key, bytes, contentType) });\n }\n\n return fail('put requires body as string, {text}, or {bytes:number[]}');\n } catch (e) { return oops(e); }\n },\n\n get(input: CommandInput): CommandResult<{ key: string; contentType?: string; size?: number; text?: string; bytes?: number[] }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n const as = (input.params?.['as'] as string | undefined) ?? 'base64';\n if (!key) return fail('get requires params.key');\n\n const head = store.head(key);\n if (!head) return fail(`artifact \"${key}\" not found`);\n\n if (as === 'text') {\n const text = store.getText(key);\n if (text === null) return fail(`artifact \"${key}\" not found`);\n return ok({ key, contentType: head.contentType, size: head.size, text });\n }\n\n const bytes = store.getBytes(key);\n if (bytes === null) return fail(`artifact \"${key}\" not found`);\n return ok({ key, contentType: head.contentType, size: head.size, bytes: [...bytes] });\n } catch (e) { return oops(e); }\n },\n\n del(input: CommandInput): CommandResult<{ ok: true }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n if (!key) return fail('del requires params.key');\n store.remove(key);\n return ok({ ok: true });\n } catch (e) { return oops(e); }\n },\n };\n}\n","/**\n * process-runner.ts — Single source of truth for child process execution.\n *\n * All CLI execution paths (task-executor, source.cli, inference-adapter,\n * detached background workers) route through these helpers.\n *\n * DESIGN:\n * - CommandSpec is the structured command form: { command, args, cwd, env, timeoutMs }\n * - runSync / runAsync use execFileSync / execFile (no ambient shell)\n * - runDetached handles OS differences in one place\n * - parseCommandSpec reads both legacy string form and new { command, args } form\n *\n * WHY NO SHELL BY DEFAULT:\n * - Shell interpretation is platform-dependent (cmd.exe vs /bin/sh vs bash)\n * - Shell parsing of argument strings is fragile and platform-fragile\n * - execFile / execFileSync avoids all quoting and escaping issues\n *\n * BACKWARD COMPAT:\n * - parseCommandSpec(\"node my-tool.js --flag\") → { command: process.execPath, args: ['my-tool.js', '--flag'] }\n * - Legacy .task-executor / .inference-adapter / source.cli string values still load correctly\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as net from 'node:net';\nimport { fileURLToPath } from 'node:url';\nimport { execFileSync, execFile, spawn } from 'node:child_process';\nimport { randomUUID, createHash } from 'node:crypto';\n\nimport type { CommandSpec } from '../../continuous-event-graph/handlers.js';\nimport type { KindValueRef } from '../common/storage-interface.js';\nimport { serializeRef } from '../common/storage-interface.js';\nexport type { CommandSpec };\n\n// ============================================================================\n// makeBoardTempFilePath — board-scoped temp file path for external process handoff\n// ============================================================================\n\n/**\n * Return a unique file path under `<boardDir>/.tmp/` suitable for passing\n * to an external binary (task-executor, inference-adapter) as `--in`, `--out`,\n * or `--err` arguments.\n *\n * - Files are co-located with the board they belong to (not global os.tmpdir()).\n * - The `.tmp/` directory is created on demand.\n * - The file itself is NOT created here — the caller writes it before use.\n * - `ext` defaults to `.json`; use `.txt` for plain-text error files.\n */\nexport function makeBoardTempFilePath(boardDir: string, label: string, ext = '.json'): string {\n const tmpDir = path.join(boardDir, '.tmp');\n fs.mkdirSync(tmpDir, { recursive: true });\n return path.join(tmpDir, `${label}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`);\n}\n\n/** Join path segments — thin wrapper so callers don't need to import node:path. */\nexport function joinPath(...segments: string[]): string { return path.join(...segments); }\n\n/** Resolve a path to absolute — thin wrapper so callers don't need to import node:path. */\nexport function resolvePath(...segments: string[]): string { return path.resolve(...segments); }\n\n/** Return the directory name of a path. */\nexport function dirnamePath(p: string): string { return path.dirname(p); }\n\n/** Return true if the path is absolute. */\nexport function isAbsolutePath(p: string): boolean { return path.isAbsolute(p); }\n\n/** Generate a new random UUID. */\nexport function genUUID(): string { return randomUUID(); }\n\n/** SHA-256 hex hash of a string. */\nexport function getHash(x: string): string { return createHash('sha256').update(x).digest('hex'); }\n\n/** Resolve the directory of an ESM module from its import.meta.url. */\nexport function resolveModuleDir(importMetaUrl: string): string { return path.dirname(fileURLToPath(importMetaUrl)); }\n\n// ============================================================================\n// parseCommandSpec — legacy string or structured CommandSpec → normalized form\n// ============================================================================\n\n/**\n * Parse a legacy string command or pass through a structured CommandSpec.\n *\n * - Legacy string: \"node script.js --flag value\"\n * → { command: process.execPath, args: ['script.js', '--flag', 'value'] }\n *\n * - Structured: { command: 'node', args: ['script.js', '--flag', 'value'] }\n * → { command: process.execPath, args: ['script.js', '--flag', 'value'] }\n *\n * After parsing, 'node'/'node.exe' is resolved to process.execPath, and bare\n * '.js'/'.mjs' paths are wrapped in a node invocation.\n */\nexport function parseCommandSpec(raw: string | CommandSpec): CommandSpec {\n if (typeof raw === 'object' && raw !== null) {\n const { command, args = [], ...rest } = raw;\n const resolved = _resolveNode(command, args);\n return { ...rest, command: resolved.command, args: resolved.args };\n }\n const parts = splitCommandLine(raw);\n if (parts.length === 0) throw new Error(`Empty command spec: ${JSON.stringify(raw)}`);\n return _resolveNode(parts[0], parts.slice(1));\n}\n\nfunction _resolveNode(cmd: string, args: string[]): { command: string; args: string[] } {\n if (/^(node|node\\.exe)$/i.test(cmd)) return { command: process.execPath, args };\n if (/\\.m?js$/i.test(cmd)) return { command: process.execPath, args: [cmd, ...args] };\n return { command: cmd, args };\n}\n\n// ============================================================================\n// splitCommandLine — shell-style string splitting (legacy compat only)\n// ============================================================================\n\n/**\n * Split a shell-style command string into tokens, respecting single/double quotes.\n * Used only for backward-compat parsing of legacy string-format config values.\n */\nexport function splitCommandLine(command: string): string[] {\n const tokens: string[] = [];\n let current = '';\n let quote: '\"' | '\\'' | null = null;\n\n for (const ch of command.trim()) {\n if (quote) {\n if (ch === quote) { quote = null; } else { current += ch; }\n continue;\n }\n if (ch === '\"' || ch === '\\'') { quote = ch; continue; }\n if (/\\s/.test(ch)) {\n if (current) { tokens.push(current); current = ''; }\n continue;\n }\n current += ch;\n }\n\n if (quote) throw new Error(`Unterminated quote in command: ${command}`);\n if (current) tokens.push(current);\n return tokens;\n}\n\n// ============================================================================\n// .cmd/.bat on Windows needs shell: true\n// ============================================================================\n\nfunction _needsWindowsShell(cmd: string): boolean {\n return process.platform === 'win32' && /\\.(cmd|bat)$/i.test(cmd);\n}\n\n// ============================================================================\n// runSync — synchronous process execution\n// ============================================================================\n\n/**\n * Run a command synchronously and return stdout as a string.\n * Uses execFileSync — no ambient shell. Safe on all platforms.\n */\nexport function runSync(spec: CommandSpec, options?: { encoding?: BufferEncoding; input?: string }): string {\n const { command, args = [], cwd, env, timeoutMs } = spec;\n const output = execFileSync(command, args, {\n shell: _needsWindowsShell(command),\n timeout: timeoutMs,\n encoding: options?.encoding ?? 'utf-8',\n cwd,\n windowsHide: true,\n env: env ? { ...process.env, ...env } : undefined,\n input: options?.input,\n });\n return output as string;\n}\n\n// ============================================================================\n// runAsync — async process execution with callback\n// ============================================================================\n\n/**\n * Run a command asynchronously, calling back with (err, stdout, stderr).\n * Uses execFile — no ambient shell. Safe on all platforms.\n */\nexport function runAsync(\n spec: CommandSpec,\n callback: (err: Error | null, stdout: string, stderr: string) => void,\n): void {\n const { command, args = [], cwd, env, timeoutMs = 30_000 } = spec;\n execFile(\n command,\n args,\n {\n shell: _needsWindowsShell(command),\n encoding: 'utf8',\n windowsHide: true,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024,\n cwd,\n env: env ? { ...process.env, ...env } : undefined,\n },\n (err, stdout, stderr) => callback(err ?? null, stdout, stderr),\n );\n}\n\n// ============================================================================\n// Git Bash detection (Windows only — needed for runDetached)\n// ============================================================================\n\nlet _gitBashPath: string | false | undefined;\nconst _GIT_BASH_CACHE = path.join(os.tmpdir(), '.board-live-cards-git-bash-cache.json');\n\nexport function findGitBash(): string | false {\n if (_gitBashPath !== undefined) return _gitBashPath;\n if (process.platform !== 'win32') return (_gitBashPath = false);\n\n try {\n const cached = JSON.parse(fs.readFileSync(_GIT_BASH_CACHE, 'utf8')) as { path: string | false };\n if (cached.path === false || (typeof cached.path === 'string' && fs.existsSync(cached.path))) {\n return (_gitBashPath = cached.path);\n }\n } catch { /* cache miss */ }\n\n const candidates: Array<string | undefined> = [\n process.env.SHELL,\n process.env.PROGRAMFILES\n ? path.join(process.env.PROGRAMFILES, 'Git', 'usr', 'bin', 'bash.exe')\n : undefined,\n process.env.PROGRAMFILES\n ? path.join(process.env.PROGRAMFILES, 'Git', 'bin', 'bash.exe')\n : undefined,\n process.env['PROGRAMFILES(X86)']\n ? path.join(process.env['PROGRAMFILES(X86)']!, 'Git', 'bin', 'bash.exe')\n : undefined,\n process.env.LOCALAPPDATA\n ? path.join(process.env.LOCALAPPDATA, 'Programs', 'Git', 'bin', 'bash.exe')\n : undefined,\n ];\n\n for (const c of candidates) {\n if (c && /bash(\\.exe)?$/i.test(c) && fs.existsSync(c)) {\n _gitBashPath = c;\n try { fs.writeFileSync(_GIT_BASH_CACHE, JSON.stringify({ path: c })); } catch { /* best-effort */ }\n return _gitBashPath;\n }\n }\n\n _gitBashPath = false;\n try { fs.writeFileSync(_GIT_BASH_CACHE, JSON.stringify({ path: false })); } catch { /* best-effort */ }\n return _gitBashPath;\n}\n\nfunction _shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n// ============================================================================\n// runDetached — fire-and-forget background spawn\n// ============================================================================\n\n/**\n * Spawn a detached background process that survives parent exit.\n * Handles Windows (Git Bash / cmd /c start /b) and Linux/macOS transparently.\n */\nexport function runDetached(spec: CommandSpec): void {\n const { command, args = [] } = spec;\n\n if (process.platform === 'win32') {\n const child = spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n windowsHide: true,\n shell: _needsWindowsShell(command),\n });\n child.unref();\n return;\n }\n\n const child = spawn(command, args, { detached: true, stdio: 'ignore' });\n child.unref();\n}\n\n// ============================================================================\n// buildBoardCliInvocation — resolve how to invoke board-live-cards-cli\n//\n// cliDir is the directory containing board-live-cards-cli.ts / .js.\n// Probe order: compiled .js → tsx dev → npx tsx fallback.\n// ============================================================================\n\n/**\n * Return { cmd, args } that invokes `board-live-cards-cli <command> [...args]`\n * in whatever environment is available (compiled dist, dev tsx, npx fallback).\n *\n * Pass `__dirname` (from the calling file's own directory) as `cliDir`.\n */\nexport function buildBoardCliInvocation(\n cliDir: string,\n command: string,\n args: string[],\n): { cmd: string; args: string[] } {\n const jsPath = path.join(cliDir, 'board-live-cards-cli.js');\n if (fs.existsSync(jsPath)) {\n return { cmd: process.execPath, args: [jsPath, command, ...args] };\n }\n\n const tsPath = path.join(cliDir, 'board-live-cards-cli.ts');\n const tsxMjs = path.join(cliDir, '..', '..', 'node_modules', 'tsx', 'dist', 'cli.mjs');\n const tsxBin = path.join(cliDir, '..', '..', 'node_modules', '.bin', 'tsx');\n const tsx = fs.existsSync(tsxMjs) ? tsxMjs : tsxBin;\n if (fs.existsSync(tsPath) && fs.existsSync(tsx)) {\n return { cmd: process.execPath, args: [tsx, tsPath, command, ...args] };\n }\n\n const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';\n return { cmd: npxCmd, args: ['tsx', tsPath, command, ...args] };\n}\n\n/**\n * Resolve a stable script path for callback-style invocations where the callee\n * only accepts `node <script> ...` (no separate runtime args like tsx).\n *\n * Prefer the repo wrapper when present because it can bridge src/dist modes.\n * Fall back to the compiled JS CLI entrypoint in cliDir when available.\n */\nexport function resolveBoardCliCallbackTarget(cliDir: string): string {\n // 3 levels up: dist/cli/node -> repo root (compiled mode)\n const repoBoardCliWrapper = path.join(cliDir, '..', '..', '..', 'board-live-cards-cli.js');\n if (fs.existsSync(repoBoardCliWrapper)) return repoBoardCliWrapper;\n\n // 2 levels up: tests/cli -> repo root (test/source mode)\n const repoBoardCliWrapper2 = path.join(cliDir, '..', '..', 'board-live-cards-cli.js');\n if (fs.existsSync(repoBoardCliWrapper2)) return repoBoardCliWrapper2;\n\n const jsPath = path.join(cliDir, 'board-live-cards-cli.js');\n if (fs.existsSync(jsPath)) return jsPath;\n\n throw new Error(\n `resolveBoardCliCallbackTarget: cannot find callback target in ${cliDir} ` +\n `(expected ../board-live-cards-cli.js wrapper or ${jsPath})`\n );\n}\n\n// ============================================================================\n// requestProcessAccumulatedDetached — fire-and-forget dispatch of next drain pass\n// ============================================================================\n\n/**\n * Spawn a detached board-live-cards process-accumulated-events pass for the given board.\n */\nexport function requestProcessAccumulatedDetached(cliDir: string, baseRef: KindValueRef, notifyChannel?: string): void {\n const cliArgs = ['--base-ref', serializeRef(baseRef)];\n if (notifyChannel) cliArgs.push('--notify-channel', notifyChannel);\n const { cmd, args } = buildBoardCliInvocation(cliDir, 'process-accumulated-events', cliArgs);\n runDetached({ command: cmd, args });\n}\n\n// ============================================================================\n// Named-pipe event transport (cross-process board notifications)\n// ============================================================================\n\n/** Return canonical named-pipe/socket path for the given channel name. */\nexport function getNamedPipePath(pipeName: string): string {\n if (process.platform === 'win32') return `\\\\\\\\.\\\\pipe\\\\${pipeName}`;\n return path.join(os.tmpdir(), `${pipeName}.sock`);\n}\n\n/**\n * Publish a batch of JSON notifications as newline-delimited records to a named pipe.\n * Best-effort: if the pipe is unavailable, logs via onWarn and drops the batch.\n *\n * All payloads are concatenated into a single `socket.write()` call so the consumer\n * receives them atomically (no interleaving from other drain cycles).\n * Uses a per-pipeName persistent connection so ordering is preserved across calls.\n */\nconst _pipeClients = new Map<string, { socket: net.Socket; ready: boolean; queue: string[] }>();\n\nexport function publishJsonEventsToNamedPipe(\n pipeName: string,\n payloads: unknown[],\n onWarn?: (msg: string) => void,\n): void {\n if (payloads.length === 0) return;\n const chunk = payloads.map(p => JSON.stringify(p)).join('\\n') + '\\n';\n let entry = _pipeClients.get(pipeName);\n\n if (entry && !entry.socket.destroyed) {\n if (entry.ready) {\n entry.socket.write(chunk);\n } else {\n entry.queue.push(chunk);\n }\n return;\n }\n\n // Create a new persistent connection\n const pipePath = getNamedPipePath(pipeName);\n const socket = net.createConnection(pipePath);\n entry = { socket, ready: false, queue: [chunk] };\n _pipeClients.set(pipeName, entry);\n\n socket.on('connect', () => {\n entry!.ready = true;\n for (const queued of entry!.queue) socket.write(queued);\n entry!.queue.length = 0;\n });\n\n socket.on('error', (e) => {\n onWarn?.(`[named-pipe publish] ${pipePath}: ${e instanceof Error ? e.message : String(e)}`);\n _pipeClients.delete(pipeName);\n });\n\n socket.on('close', () => {\n _pipeClients.delete(pipeName);\n });\n}\n\n// ============================================================================\n// createNodeCommandExecutor — Node implementation of CommandExecutor\n//\n// Wraps runSync / runAsync / runDetached / parseCommandSpec / splitCommandLine\n// into a single injectable object. Pass to command handlers instead of the\n// individual execCommandSync / execCommandAsync / resolveCommandInvocation /\n// splitCommandLine / spawnDetachedCommand dep functions.\n// ============================================================================\n\nimport type { CommandExecutor, ExecOptions } from '../common/process-interface.js';\n\nexport function createNodeCommandExecutor(): CommandExecutor {\n return {\n executeSync(cmd: string, args: string[], options?: ExecOptions): string {\n return runSync(\n { command: cmd, args, cwd: options?.cwd, timeoutMs: options?.timeout, env: options?.env as Record<string, string> | undefined },\n { encoding: options?.encoding as BufferEncoding | undefined, input: options?.input },\n );\n },\n executeAsync(cmd: string, args: string[], callback: (err: Error | null, stdout: string, stderr: string) => void): void {\n runAsync({ command: cmd, args }, callback);\n },\n resolveInvocation(rawCmd: string, rawArgs: string[]): { cmd: string; args: string[] } {\n const spec = parseCommandSpec({ command: rawCmd, args: rawArgs });\n return { cmd: spec.command, args: spec.args ?? [] };\n },\n splitCommand: splitCommandLine,\n spawnDetached(cmd: string, args: string[]): void {\n runDetached({ command: cmd, args });\n },\n };\n}\n","/**\n * artifacts-store-cli.ts\n *\n * Thin arg parser for ArtifactsStore public API.\n */\n\nimport * as fs from 'node:fs';\nimport { parseRef } from '../common/storage-interface.js';\nimport { createFsBlobStorage } from './storage-fs-adapters.js';\nimport { createArtifactsStore } from '../common/artifacts-store-lib.js';\nimport { createArtifactsStorePublic } from '../common/artifacts-store-lib-public.js';\nimport { resolvePath } from './process-runner.js';\n\nfunction requireFlag(args: string[], flag: string, usage: string): string {\n const idx = args.indexOf(flag);\n const val = idx !== -1 ? args[idx + 1] : undefined;\n if (!val) throw new Error(`Missing ${flag}\\nUsage: ${usage}`);\n return val;\n}\n\nfunction optFlag(args: string[], flag: string): string | undefined {\n const idx = args.indexOf(flag);\n return idx !== -1 ? args[idx + 1] : undefined;\n}\n\nasync function readStdinBytes(): Promise<Uint8Array> {\n const parts: Buffer[] = [];\n for await (const chunk of process.stdin) {\n parts.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array));\n }\n return new Uint8Array(Buffer.concat(parts));\n}\n\nconst HELP = [\n 'artifacts-store — generic artifact CRUD on a blob-backed store',\n '',\n ' artifacts-store put --store-ref <ref> --key <key> [--file <path> | --text <text>] [--content-type <mime>]',\n ' artifacts-store get --store-ref <ref> --key <key> [--out <path>] [--as text|bytes]',\n ' artifacts-store head --store-ref <ref> --key <key>',\n ' artifacts-store list --store-ref <ref> [--prefix <prefix>]',\n ' artifacts-store del --store-ref <ref> --key <key>',\n].join('\\n');\n\nexport async function cli(argv: string[]): Promise<void> {\n const cmd = argv[0];\n const rest = argv.slice(1);\n\n if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {\n console.error(HELP);\n return;\n }\n\n const ref = requireFlag(rest, '--store-ref', `artifacts-store ${cmd} --store-ref <b64-ref>`);\n const root = parseRef(ref).value;\n const store = createArtifactsStorePublic(createArtifactsStore(createFsBlobStorage(root)));\n\n if (cmd === 'put') {\n const key = requireFlag(rest, '--key', 'artifacts-store put --store-ref <ref> --key <key>');\n const contentType = optFlag(rest, '--content-type');\n const filePath = optFlag(rest, '--file');\n const text = optFlag(rest, '--text');\n\n let body: unknown;\n if (filePath) {\n const bytes = new Uint8Array(fs.readFileSync(filePath));\n body = { bytes: [...bytes] };\n } else if (typeof text === 'string') {\n body = { text };\n } else if (!process.stdin.isTTY) {\n const bytes = await readStdinBytes();\n body = { bytes: [...bytes] };\n } else {\n throw new Error('put requires --file, --text, or stdin bytes');\n }\n\n const result = store.put({ params: { key, ...(contentType ? { contentType } : {}) }, body });\n if (result.status !== 'success') throw new Error(result.error || 'put failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'get') {\n const key = requireFlag(rest, '--key', 'artifacts-store get --store-ref <ref> --key <key>');\n const as = (optFlag(rest, '--as') || 'bytes').toLowerCase();\n const outPath = optFlag(rest, '--out');\n const result = store.get({ params: { key, as } });\n if (result.status !== 'success') throw new Error(result.error || 'get failed');\n\n if (as === 'text') {\n const text = result.data.text ?? '';\n if (outPath) fs.writeFileSync(outPath, text, 'utf-8');\n else process.stdout.write(text);\n return;\n }\n\n const bytes = new Uint8Array(result.data.bytes ?? []);\n if (outPath) fs.writeFileSync(outPath, Buffer.from(bytes));\n else process.stdout.write(JSON.stringify({ ...result.data, bytes: undefined, byteLength: bytes.byteLength }, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'head') {\n const key = requireFlag(rest, '--key', 'artifacts-store head --store-ref <ref> --key <key>');\n const result = store.head({ params: { key } });\n if (result.status !== 'success') throw new Error(result.error || 'head failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'list') {\n const prefix = optFlag(rest, '--prefix') || '';\n const result = store.list({ params: prefix ? { prefix } : {} });\n if (result.status !== 'success') throw new Error(result.error || 'list failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'del' || cmd === 'delete' || cmd === 'rm') {\n const key = requireFlag(rest, '--key', 'artifacts-store del --store-ref <ref> --key <key>');\n const result = store.del({ params: { key } });\n if (result.status !== 'success') throw new Error(result.error || 'del failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n throw new Error(`Unknown command \"${cmd}\"\\n\\n${HELP}`);\n}\n\nconst isMain = process.argv[1] && resolvePath(process.argv[1]) === resolvePath(new URL(import.meta.url).pathname.replace(/^\\/([A-Z]:)/, '$1'));\nif (isMain) {\n cli(process.argv.slice(2)).catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n });\n}\n"]}