zidane 5.5.4 → 5.6.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 (69) hide show
  1. package/README.md +7 -1
  2. package/dist/{agent-CMAklak7.d.ts → agent-B26FuGew.d.ts} +90 -2
  3. package/dist/agent-B26FuGew.d.ts.map +1 -0
  4. package/dist/chat.d.ts +133 -22
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +3 -3
  7. package/dist/{errors-C5VSakmT.js → errors-DdZXnyXE.js} +38 -2
  8. package/dist/errors-DdZXnyXE.js.map +1 -0
  9. package/dist/{index-CF5QwBiz.d.ts → index-CE7z_11T.d.ts} +2 -2
  10. package/dist/{index-CF5QwBiz.d.ts.map → index-CE7z_11T.d.ts.map} +1 -1
  11. package/dist/{index-kroGomhj.d.ts → index-CROWxXo9.d.ts} +23 -2
  12. package/dist/index-CROWxXo9.d.ts.map +1 -0
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +10 -10
  15. package/dist/{interpolate-Cvjy8gpk.js → interpolate-j5V-wcAQ.js} +2 -2
  16. package/dist/{interpolate-Cvjy8gpk.js.map → interpolate-j5V-wcAQ.js.map} +1 -1
  17. package/dist/{login-B_kfoGMP.js → login-D5lQWoFx.js} +3 -3
  18. package/dist/{login-B_kfoGMP.js.map → login-D5lQWoFx.js.map} +1 -1
  19. package/dist/{mcp-BE43Viwi.js → mcp-ngMS0S6N.js} +2 -2
  20. package/dist/{mcp-BE43Viwi.js.map → mcp-ngMS0S6N.js.map} +1 -1
  21. package/dist/mcp.d.ts +1 -1
  22. package/dist/mcp.js +1 -1
  23. package/dist/{messages-BBWakTN6.js → messages-B5k4DAXy.js} +2 -2
  24. package/dist/{messages-BBWakTN6.js.map → messages-B5k4DAXy.js.map} +1 -1
  25. package/dist/{presets-BDvBZuYI.js → presets-BDCthpyD.js} +2 -2
  26. package/dist/{presets-BDvBZuYI.js.map → presets-BDCthpyD.js.map} +1 -1
  27. package/dist/presets.d.ts +2 -2
  28. package/dist/presets.js +1 -1
  29. package/dist/{providers-CsUyN_FJ.js → providers-CaJE2ToS.js} +3 -3
  30. package/dist/{providers-CsUyN_FJ.js.map → providers-CaJE2ToS.js.map} +1 -1
  31. package/dist/providers.d.ts +1 -1
  32. package/dist/providers.js +2 -2
  33. package/dist/restate.d.ts +1 -1
  34. package/dist/session/sqlite.d.ts +1 -1
  35. package/dist/session/sqlite.d.ts.map +1 -1
  36. package/dist/session/sqlite.js +226 -51
  37. package/dist/session/sqlite.js.map +1 -1
  38. package/dist/{session-DzfRacU_.js → session-BoEW_wCR.js} +2 -2
  39. package/dist/{session-DzfRacU_.js.map → session-BoEW_wCR.js.map} +1 -1
  40. package/dist/session.d.ts +1 -1
  41. package/dist/session.js +2 -2
  42. package/dist/skills.d.ts +2 -2
  43. package/dist/skills.js +1 -1
  44. package/dist/{tools-Bbd0Ivwn.js → tools-Co3VYhgM.js} +154 -15
  45. package/dist/tools-Co3VYhgM.js.map +1 -0
  46. package/dist/tools.d.ts +2 -2
  47. package/dist/tools.js +1 -1
  48. package/dist/{transcript-anchors-C79AszkC.d.ts → transcript-anchors-CTTeQJzy.d.ts} +12 -4
  49. package/dist/{transcript-anchors-C79AszkC.d.ts.map → transcript-anchors-CTTeQJzy.d.ts.map} +1 -1
  50. package/dist/tui.d.ts +2 -2
  51. package/dist/tui.d.ts.map +1 -1
  52. package/dist/tui.js +432 -83
  53. package/dist/tui.js.map +1 -1
  54. package/dist/{turn-operations-CGf7wWF0.js → turn-operations-fhinWY4m.js} +134 -18
  55. package/dist/turn-operations-fhinWY4m.js.map +1 -0
  56. package/dist/types-oKPBdCmL.js.map +1 -1
  57. package/dist/types.d.ts +3 -3
  58. package/dist/types.js +2 -2
  59. package/docs/ARCHITECTURE.md +5 -2
  60. package/docs/CHAT.md +10 -3
  61. package/docs/RESTATE.md +190 -0
  62. package/docs/SKILL.md +27 -2
  63. package/docs/TUI.md +3 -3
  64. package/package.json +1 -1
  65. package/dist/agent-CMAklak7.d.ts.map +0 -1
  66. package/dist/errors-C5VSakmT.js.map +0 -1
  67. package/dist/index-kroGomhj.d.ts.map +0 -1
  68. package/dist/tools-Bbd0Ivwn.js.map +0 -1
  69. package/dist/turn-operations-CGf7wWF0.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"session-DzfRacU_.js","names":[],"sources":["../src/session/file-map.ts","../src/session/memory.ts","../src/session/remote.ts","../src/session/index.ts"],"sourcesContent":["/**\n * File-map session store.\n *\n * Wraps a narrow 3-method adapter (`get` / `save` / `delete`) that exchanges a flat\n * map of filename → string content. Useful for embedding zidane sessions inside\n * host-provided session backends that only speak in file maps (not zidane's native\n * `SessionStore` shape).\n *\n * Serialization format:\n * - `turns.jsonl` — one `SessionTurn` per line.\n * - `meta.json` — session metadata (id, agentId, status, runs, metadata, timestamps).\n *\n * JSONL for turns keeps history inspectable with tools like `jq` and resilient to\n * partial corruption — parse up to the first bad line and you still have a valid\n * prefix. Metadata lives in its own file so large turn logs don't bloat the\n * metadata path.\n *\n * Scope: each `createFileMapStore` handles a **single session** — the adapter's\n * file map holds at most one zidane session at a time. This matches how host SDKs\n * scope their session stores per conversation.\n *\n * Divergences from the built-in memory / sqlite stores:\n * - `appendTurns` / `updateStatus` / `updateRun` auto-create a minimal `SessionData`\n * record on first write, instead of silently no-oping when the session hasn't been\n * explicitly `save()`-ed. This matches the host-SDK integration path where\n * `createSession(...)` → `agent.run(...)` directly without an explicit `save()` call.\n * - `updateRun` inserts the run if not found in the cached record (rather than\n * silently dropping). Run records therefore always reach the adapter.\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\n/**\n * Host-provided file-map adapter. Three methods exchanging `Record<string, string>`\n * payloads — the whole persistence surface the wrapper needs.\n */\nexport interface FileMapAdapter {\n /** Load the current file map. Returns an empty `files` record when nothing is persisted. */\n get: () => Promise<{ files: Record<string, string> }>\n /** Replace the persisted file map. Full-rewrite semantics. */\n save: (files: Record<string, string>) => Promise<void>\n /** Delete all persisted state. */\n delete: () => Promise<void>\n}\n\nexport interface FileMapStoreOptions {\n /** Filename for the JSONL turns log. Default: `turns.jsonl`. */\n turnsFile?: string\n /** Filename for the metadata JSON. Default: `meta.json`. */\n metaFile?: string\n}\n\ninterface MetaShape {\n id: string\n agentId?: string\n runs: SessionRun[]\n status: SessionData['status']\n metadata: Record<string, unknown>\n createdAt: number\n updatedAt: number\n}\n\nfunction toMeta(data: SessionData): MetaShape {\n return {\n id: data.id,\n agentId: data.agentId,\n runs: data.runs,\n status: data.status,\n metadata: data.metadata,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n }\n}\n\nfunction toData(meta: MetaShape, turns: SessionTurn[]): SessionData {\n return {\n id: meta.id,\n agentId: meta.agentId,\n turns,\n runs: meta.runs,\n status: meta.status,\n metadata: meta.metadata,\n createdAt: meta.createdAt,\n updatedAt: meta.updatedAt,\n }\n}\n\nfunction parseTurnsJsonl(jsonl: string): SessionTurn[] {\n if (!jsonl)\n return []\n const turns: SessionTurn[] = []\n for (const line of jsonl.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed)\n continue\n try {\n turns.push(JSON.parse(trimmed) as SessionTurn)\n }\n catch {\n // Skip malformed lines — preserves the valid prefix on partial corruption.\n }\n }\n return turns\n}\n\nfunction serializeTurnsJsonl(turns: SessionTurn[]): string {\n if (turns.length === 0)\n return ''\n return `${turns.map(t => JSON.stringify(t)).join('\\n')}\\n`\n}\n\n/**\n * Create a single-session `SessionStore` backed by a file-map adapter.\n *\n * @example\n * ```ts\n * const session = await createSession({\n * store: createFileMapStore(hostSessionStore),\n * })\n * ```\n */\nexport function createFileMapStore(\n adapter: FileMapAdapter,\n options: FileMapStoreOptions = {},\n): SessionStore {\n const turnsFile = options.turnsFile ?? 'turns.jsonl'\n const metaFile = options.metaFile ?? 'meta.json'\n\n // Cached view of the persisted session. Populated lazily on first access so the\n // factory itself doesn't do I/O.\n let cached: SessionData | null = null\n let hydrated = false\n\n async function hydrate(): Promise<void> {\n if (hydrated)\n return\n const { files } = await adapter.get()\n const metaRaw = files[metaFile]\n if (metaRaw) {\n let meta: MetaShape | null = null\n try {\n meta = JSON.parse(metaRaw) as MetaShape\n }\n catch {\n meta = null\n }\n if (meta) {\n cached = toData(meta, parseTurnsJsonl(files[turnsFile] ?? ''))\n }\n }\n hydrated = true\n }\n\n async function persist(data: SessionData): Promise<void> {\n const meta = toMeta(data)\n await adapter.save({\n [metaFile]: JSON.stringify(meta, null, 2),\n [turnsFile]: serializeTurnsJsonl(data.turns),\n })\n }\n\n // Ensure `cached` exists for `sessionId`, creating a minimal record when first written.\n // Returns false when `cached` already holds a different sessionId (request ignored).\n async function ensureCachedFor(sessionId: string): Promise<boolean> {\n await hydrate()\n if (cached) {\n return cached.id === sessionId\n }\n const now = Date.now()\n cached = {\n id: sessionId,\n turns: [],\n runs: [],\n status: 'idle',\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }\n hydrated = true\n return true\n }\n\n return {\n async load(sessionId: string): Promise<SessionData | null> {\n await hydrate()\n if (!cached || cached.id !== sessionId)\n return null\n return structuredClone(cached)\n },\n\n async save(data: SessionData): Promise<void> {\n cached = structuredClone(data)\n hydrated = true\n await persist(cached)\n },\n\n async delete(sessionId: string): Promise<void> {\n await hydrate()\n if (cached && cached.id !== sessionId)\n return\n cached = null\n await adapter.delete()\n },\n\n async list(filter): Promise<string[]> {\n await hydrate()\n if (!cached)\n return []\n if (filter?.agentId && cached.agentId !== filter.agentId)\n return []\n // file-map stores exactly one session, so the projectRoot filter\n // either keeps it (match / axis off) or drops it entirely.\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null && cached.projectRoot != null)\n return []\n if (typeof v === 'string' && cached.projectRoot !== v)\n return []\n }\n return [cached.id]\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n cached!.turns.push(...structuredClone(turns))\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number): Promise<SessionTurn[]> {\n await hydrate()\n if (!cached || cached.id !== sessionId)\n return []\n const slice = cached.turns.slice(from, limit !== undefined ? from + limit : undefined)\n return structuredClone(slice) as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n const idx = cached!.runs.findIndex(r => r.id === run.id)\n if (idx >= 0)\n cached!.runs[idx] = structuredClone(run)\n else\n cached!.runs.push(structuredClone(run))\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n cached!.status = status\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n }\n}\n","/**\n * In-memory session store.\n * Useful for development and testing. Data is lost when the process exits.\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\nexport function createMemoryStore(): SessionStore {\n const sessions = new Map<string, SessionData>()\n\n return {\n async load(sessionId: string) {\n const data = sessions.get(sessionId)\n return data ? structuredClone(data) : null\n },\n\n async save(session: SessionData) {\n sessions.set(session.id, structuredClone(session))\n },\n\n async delete(sessionId: string) {\n sessions.delete(sessionId)\n },\n\n async list(filter) {\n let ids = Array.from(sessions.keys())\n if (filter?.agentId) {\n ids = ids.filter(id => sessions.get(id)?.agentId === filter.agentId)\n }\n // `projectRoot` mirrors the SQLite store's tri-state contract:\n // string → only sessions tagged with that root\n // null → only UNTAGGED sessions (legacy / pre-v3 rows)\n // undefined → axis ignored (tagged + untagged both returned)\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null)\n ids = ids.filter(id => sessions.get(id)?.projectRoot == null)\n else if (typeof v === 'string')\n ids = ids.filter(id => sessions.get(id)?.projectRoot === v)\n }\n if (filter?.limit) {\n ids = ids.slice(0, filter.limit)\n }\n return ids\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]) {\n const data = sessions.get(sessionId)\n if (data) {\n data.turns.push(...structuredClone(turns))\n data.updatedAt = Date.now()\n }\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number) {\n const data = sessions.get(sessionId)\n if (!data)\n return []\n const sliced = data.turns.slice(from, limit !== undefined ? from + limit : undefined)\n return structuredClone(sliced) as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun) {\n const data = sessions.get(sessionId)\n if (data) {\n // Upsert — see the matching comment in sqlite.ts's\n // `txnUpdateRun` for the failure mode. tl;dr: the agent's\n // `startRun()` only updates in-memory state; the store first\n // sees the run via `updateRun()` at finalize time, so an\n // update-only impl silently drops every new run.\n const idx = data.runs.findIndex(r => r.id === run.id)\n if (idx >= 0)\n data.runs[idx] = structuredClone(run)\n else\n data.runs.push(structuredClone(run))\n data.updatedAt = Date.now()\n }\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']) {\n const data = sessions.get(sessionId)\n if (data) {\n data.status = status\n data.updatedAt = Date.now()\n }\n },\n }\n}\n","/**\n * Remote session store via HTTP API.\n *\n * Expects a REST API with:\n * GET {url}/sessions/{id} -> SessionData | 404\n * PUT {url}/sessions/{id} -> save SessionData\n * DELETE {url}/sessions/{id} -> delete\n * GET {url}/sessions?agentId=&limit=&projectRoot= -> { ids: string[] }\n * `projectRoot=__null__` is the wire encoding for \"untagged only\".\n * POST {url}/sessions/{id}/turns -> append turns\n * GET {url}/sessions/{id}/turns?from=&limit= -> SessionTurn[]\n * PUT {url}/sessions/{id}/runs/{runId} -> update run\n * PATCH {url}/sessions/{id} -> { status }\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\nexport interface RemoteStoreOptions {\n /** Base URL of the session API */\n url: string\n /** Optional headers (e.g. for authentication) */\n headers?: Record<string, string>\n}\n\nconst TRAILING_SLASH = /\\/$/\n\nexport function createRemoteStore(options: RemoteStoreOptions): SessionStore {\n const baseUrl = options.url.replace(TRAILING_SLASH, '')\n const defaultHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...options.headers,\n }\n\n async function request(path: string, init?: RequestInit): Promise<Response> {\n const url = `${baseUrl}${path}`\n const res = await fetch(url, {\n ...init,\n headers: { ...defaultHeaders, ...init?.headers },\n })\n return res\n }\n\n return {\n async load(sessionId: string) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`)\n if (!res.ok) {\n if (res.status === 404)\n return null\n throw new Error(`Remote session load failed: ${res.status} ${res.statusText}`)\n }\n return await res.json() as SessionData\n },\n\n async save(session: SessionData) {\n const res = await request(`/sessions/${encodeURIComponent(session.id)}`, {\n method: 'PUT',\n body: JSON.stringify(session),\n })\n if (!res.ok) {\n throw new Error(`Remote session save failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async delete(sessionId: string) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`, {\n method: 'DELETE',\n })\n if (!res.ok && res.status !== 404) {\n throw new Error(`Remote session delete failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async list(filter) {\n const params = new URLSearchParams()\n if (filter?.agentId)\n params.set('agentId', filter.agentId)\n if (filter?.limit)\n params.set('limit', String(filter.limit))\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null)\n params.set('projectRoot', '__null__')\n else if (typeof v === 'string')\n params.set('projectRoot', v)\n }\n\n const query = params.toString()\n const path = query ? `/sessions?${query}` : '/sessions'\n const res = await request(path)\n\n if (!res.ok) {\n throw new Error(`Remote session list failed: ${res.status} ${res.statusText}`)\n }\n\n const body = await res.json() as { ids: string[] }\n return body.ids\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}/turns`, {\n method: 'POST',\n body: JSON.stringify(turns),\n })\n if (!res.ok) {\n throw new Error(`Remote appendTurns failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number) {\n const params = new URLSearchParams()\n if (from)\n params.set('from', String(from))\n if (limit !== undefined)\n params.set('limit', String(limit))\n\n const query = params.toString()\n const path = `/sessions/${encodeURIComponent(sessionId)}/turns${query ? `?${query}` : ''}`\n const res = await request(path)\n\n if (!res.ok) {\n throw new Error(`Remote getTurns failed: ${res.status} ${res.statusText}`)\n }\n\n return await res.json() as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun) {\n const res = await request(\n `/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(run.id)}`,\n {\n method: 'PUT',\n body: JSON.stringify(run),\n },\n )\n if (!res.ok) {\n throw new Error(`Remote updateRun failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n })\n if (!res.ok) {\n throw new Error(`Remote updateStatus failed: ${res.status} ${res.statusText}`)\n }\n },\n }\n}\n","/**\n * Session management for agents.\n *\n * A session tracks identity, turn history, and run metadata.\n * Plug in any storage backend by implementing the SessionStore interface,\n * or use one of the built-in stores: memory, sqlite, remote.\n */\n\nimport type { SessionTurn, TurnUsage } from '../types'\n\nexport type { SessionContentBlock, SessionMessage, SessionTurn } from '../types'\nexport { createFileMapStore } from './file-map'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SessionRun {\n id: string\n startedAt: number\n endedAt?: number\n prompt: string\n status: 'running' | 'completed' | 'aborted' | 'error'\n turns?: number\n tokensIn?: number\n tokensOut?: number\n error?: string\n /** Per-turn usage breakdown */\n turnUsage?: TurnUsage[]\n /** Total usage across all turns */\n totalUsage?: TurnUsage\n /** Estimated cost in USD */\n cost?: number\n /**\n * The run that spawned this one, when the agent is a subagent sharing its\n * parent's session. Undefined on top-level `agent.run()`. Consumers can walk\n * `runs` by `parentRunId` to reconstruct the subagent tree.\n */\n parentRunId?: string\n /**\n * Zero-based subagent depth. 0 = top-level run, 1 = direct child, …\n * Recorded here so hosts can query/filter by level without walking the tree.\n */\n depth?: number\n}\n\nexport interface SessionData {\n id: string\n agentId?: string\n /**\n * Absolute path of the project this session belongs to — typically\n * the git root resolved from `cwd` at creation time, falling back to\n * `cwd` itself when not in a git repo. Set ONCE on creation and never\n * mutated thereafter (the session \"belongs\" to that project forever).\n *\n * Used by the TUI's sessions list to filter rows by current project,\n * so the user only sees conversations relevant to where they are\n * working — without needing one `.{prefix}/` directory per project.\n *\n * `undefined` on pre-tagging legacy sessions; the chat layer treats\n * those as \"untagged\" and hides them unless `Settings.showAllProjects`\n * is on.\n */\n projectRoot?: string\n turns: SessionTurn[]\n runs: SessionRun[]\n status: 'idle' | 'running' | 'completed' | 'error'\n metadata: Record<string, unknown>\n createdAt: number\n updatedAt: number\n}\n\n// ---------------------------------------------------------------------------\n// SessionStore interface (pluggable backend)\n// ---------------------------------------------------------------------------\n\nexport interface SessionStore {\n /** Optional: generate a session ID server-side (e.g. Supabase UUID). */\n generateSessionId?: () => string | Promise<string>\n\n /** Optional: generate a turn ID server-side. */\n generateTurnId?: () => string | Promise<string>\n\n /** Load a session by ID. Returns null if not found. */\n load: (sessionId: string) => Promise<SessionData | null>\n\n /** Save a session (create or update, full document). */\n save: (session: SessionData) => Promise<void>\n\n /** Delete a session. */\n delete: (sessionId: string) => Promise<void>\n\n /**\n * List session IDs, optionally filtered. `projectRoot` restricts to\n * sessions whose `SessionData.projectRoot` matches exactly — untagged\n * (legacy) sessions are NOT returned under that filter; pass `null`\n * explicitly to ask for untagged ones, or omit the field to ignore\n * the axis entirely. `agentId` filters by recorded agent; the two\n * conditions AND together when both are set.\n */\n list: (filter?: { agentId?: string, limit?: number, projectRoot?: string | null }) => Promise<string[]>\n\n /** Append new turns to a session (incremental, avoids full re-save). */\n appendTurns: (sessionId: string, turns: SessionTurn[]) => Promise<void>\n\n /** Return a slice of turns for a session. */\n getTurns: (sessionId: string, from?: number, limit?: number) => Promise<SessionTurn[]>\n\n /**\n * Persist a run record (called after completeRun / abortRun / errorRun).\n *\n * **Upsert semantics required.** The agent's run lifecycle calls\n * `session.startRun()` to register a new run in IN-MEMORY state and\n * then `updateRun()` at finalize. The store doesn't see the run any\n * earlier — `appendTurns` is the only intermediate call, and it\n * persists turns but not runs. An update-only implementation would\n * silently drop every newly-created run record at finalize, leaving\n * the on-disk `runs[]` missing entries that the persisted turns\n * reference (which `eventsFromTurns` then mis-classifies via\n * `ancestryOf` → broken depth, broken `child-N` labels, broken\n * `userPrompts` history).\n *\n * Implementations MUST insert when the id doesn't exist and update\n * when it does. The bundled `sqlite` / `memory` / `file-map` stores\n * all follow this contract; remote stores using HTTP PUT get upsert\n * for free via REST semantics.\n */\n updateRun: (sessionId: string, run: SessionRun) => Promise<void>\n\n /** Update the top-level status of a session. */\n updateStatus: (sessionId: string, status: SessionData['status']) => Promise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Session (live instance wrapping a SessionData)\n// ---------------------------------------------------------------------------\n\nexport interface Session {\n /** Session ID */\n readonly id: string\n\n /** Agent ID (optional label) */\n readonly agentId?: string\n\n /**\n * Project this session was created under — see {@link SessionData.projectRoot}.\n * Set once on creation; surfaces here for read-only inspection.\n */\n readonly projectRoot?: string\n\n /** Current turn history */\n readonly turns: SessionTurn[]\n\n /**\n * True when this session has no turns yet.\n *\n * Use this as a first-prompt signal when setting up a run — e.g. writing initial\n * configuration only on fresh sessions. Equivalent to `turns.length === 0`.\n */\n readonly isEmpty: boolean\n\n /** Top-level session status */\n readonly status: SessionData['status']\n\n /** All runs in this session */\n readonly runs: SessionRun[]\n\n /** Arbitrary metadata */\n readonly metadata: Record<string, unknown>\n\n /**\n * Start tracking a new run. `extras.parentRunId` + `extras.depth` are\n * populated by the spawn tool when a child agent shares its parent's\n * session; regular top-level `agent.run()` calls omit them.\n */\n startRun: (runId: string, prompt?: string, extras?: { parentRunId?: string, depth?: number }) => void\n\n /** Mark a run as completed */\n completeRun: (runId: string, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Mark a run as aborted */\n /**\n * Optional `stats` lets the agent backfill the run's token totals when\n * the abort happened *after* the loop accumulated meaningful usage —\n * common when the user presses esc mid-streaming. Without it, the run\n * record reads `0 in / 0 out` on reload regardless of how much was\n * spent before the abort. Same shape as `completeRun`'s stats so the\n * persisted `totalUsage` aggregate stays consistent across paths.\n */\n abortRun: (runId: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Mark a run as errored */\n /** Optional `stats` — same rationale as `abortRun.stats`. */\n errorRun: (runId: string, error: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Append turns to in-memory history AND persist via store.appendTurns (if store present) */\n appendTurns: (turns: SessionTurn[]) => Promise<void>\n\n /** Replace all turns in-memory (does not persist — use save() for that) */\n setTurns: (turns: SessionTurn[]) => void\n\n /**\n * Replace all runs in-memory (does not persist — use save() for that).\n * Mirrors {@link setTurns} for the fork / restore case: callers that\n * bootstrap a session from an externally-derived snapshot (e.g.\n * `onForkTurn` copying parent runs into a child session) need this so\n * the cloned runs land in `data.runs` before the first `save()`.\n * Production agent runs continue to mutate runs via `startRun` /\n * `completeRun` / `updateRun`; this is the bulk-replace escape hatch.\n */\n setRuns: (runs: SessionRun[]) => void\n\n /** Update the session status in memory AND via store.updateStatus (if store present) */\n updateStatus: (status: SessionData['status']) => Promise<void>\n\n /** Persist an updated run record via store.updateRun (if store present) */\n updateRun: (run: SessionRun) => Promise<void>\n\n /** Generate a turn ID using store.generateTurnId if available, else crypto.randomUUID() */\n generateTurnId: () => string | Promise<string>\n\n /** Set metadata key */\n setMeta: (key: string, value: unknown) => void\n\n /** Persist the full session document to the store */\n save: () => Promise<void>\n\n /** Serialize to SessionData */\n toJSON: () => SessionData\n}\n\n// ---------------------------------------------------------------------------\n// createSession\n// ---------------------------------------------------------------------------\n\nexport interface CreateSessionOptions {\n /** Session ID. If omitted and store provides generateSessionId, that is used. */\n id?: string\n /** Agent ID label */\n agentId?: string\n /**\n * Project tag — see {@link SessionData.projectRoot}. Stamped once on\n * creation; ignored when `_data` is set (restoring an existing\n * session preserves whatever was already persisted there). The TUI\n * resolves this from `findGitRoot(cwd) ?? cwd` so sessions started\n * from the same repo (no matter which subdir) share one tag.\n */\n projectRoot?: string\n /** Initial metadata */\n metadata?: Record<string, unknown>\n /** Storage backend (optional, enables save/load) */\n store?: SessionStore\n // @internal: restore from existing data (bypasses id/agentId/metadata options)\n _data?: SessionData\n}\n\n/**\n * Create a new session.\n * Async so stores that generate IDs server-side (e.g. Supabase) can be supported.\n */\nexport async function createSession(options: CreateSessionOptions = {}): Promise<Session> {\n const store = options.store\n const now = Date.now()\n\n let sessionId = options.id\n if (!sessionId && store?.generateSessionId) {\n sessionId = await store.generateSessionId()\n }\n if (!sessionId) {\n sessionId = generateId()\n }\n\n const data: SessionData = options._data ?? {\n id: sessionId,\n agentId: options.agentId,\n // Stamp the project tag at creation only — restored sessions (the\n // `_data` branch above) keep whatever was already persisted, even\n // if `options.projectRoot` differs. A session's project identity\n // is sticky for its lifetime; loading the same session from a\n // different cwd doesn't re-home it.\n ...(options.projectRoot ? { projectRoot: options.projectRoot } : {}),\n turns: [],\n runs: [],\n status: 'idle',\n metadata: options.metadata ?? {},\n createdAt: now,\n updatedAt: now,\n }\n\n function touch() {\n data.updatedAt = Date.now()\n }\n\n function findRun(runId: string): SessionRun | undefined {\n return data.runs.find(r => r.id === runId)\n }\n\n /**\n * Apply per-run usage stats onto a SessionRun. Shared by `completeRun`,\n * `abortRun`, and `errorRun` so the on-disk shape stays consistent across\n * exit paths — historically only `completeRun` filled these in, and\n * aborted/errored runs rendered `0 in / 0 out` on reload regardless of\n * how much was actually consumed.\n */\n function applyRunStats(run: SessionRun, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n run.turns = stats.turns\n run.tokensIn = stats.tokensIn\n run.tokensOut = stats.tokensOut\n if (stats.turnUsage) {\n run.turnUsage = stats.turnUsage\n const total = stats.turnUsage.reduce((acc, t) => ({\n input: acc.input + t.input,\n output: acc.output + t.output,\n cacheCreation: (acc.cacheCreation ?? 0) + (t.cacheCreation ?? 0),\n cacheRead: (acc.cacheRead ?? 0) + (t.cacheRead ?? 0),\n thinking: (acc.thinking ?? 0) + (t.thinking ?? 0),\n }), { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, thinking: 0 })\n run.totalUsage = {\n input: total.input,\n output: total.output,\n ...(total.cacheCreation ? { cacheCreation: total.cacheCreation } : {}),\n ...(total.cacheRead ? { cacheRead: total.cacheRead } : {}),\n ...(total.thinking ? { thinking: total.thinking } : {}),\n }\n }\n if (stats.cost !== undefined)\n run.cost = stats.cost\n }\n\n const session: Session = {\n get id() { return data.id },\n get agentId() { return data.agentId },\n get projectRoot() { return data.projectRoot },\n get turns() { return data.turns },\n get isEmpty() { return data.turns.length === 0 },\n get status() { return data.status },\n get runs() { return data.runs },\n get metadata() { return data.metadata },\n\n startRun(runId: string, prompt?: string, extras?: { parentRunId?: string, depth?: number }) {\n data.runs.push({\n id: runId,\n startedAt: Date.now(),\n prompt: prompt ?? '',\n status: 'running',\n ...(extras?.parentRunId ? { parentRunId: extras.parentRunId } : {}),\n ...(typeof extras?.depth === 'number' ? { depth: extras.depth } : {}),\n })\n touch()\n },\n\n completeRun(runId: string, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'completed'\n run.endedAt = Date.now()\n applyRunStats(run, stats)\n }\n touch()\n },\n\n abortRun(runId: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'aborted'\n run.endedAt = Date.now()\n // Backfill tokens when available so an aborted run's session\n // ledger reflects actual consumption rather than `0 in / 0 out`.\n if (stats)\n applyRunStats(run, stats)\n }\n touch()\n },\n\n errorRun(runId: string, error: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'error'\n run.endedAt = Date.now()\n run.error = error\n if (stats)\n applyRunStats(run, stats)\n }\n touch()\n },\n\n async appendTurns(turns: SessionTurn[]) {\n data.turns.push(...turns)\n touch()\n if (store) {\n await store.appendTurns(data.id, turns)\n }\n },\n\n setTurns(turns: SessionTurn[]) {\n data.turns = turns\n touch()\n },\n\n setRuns(runs: SessionRun[]) {\n data.runs = runs\n touch()\n },\n\n async updateStatus(status: SessionData['status']) {\n data.status = status\n touch()\n if (store) {\n await store.updateStatus(data.id, status)\n }\n },\n\n async updateRun(run: SessionRun) {\n if (store) {\n await store.updateRun(data.id, run)\n }\n },\n\n generateTurnId() {\n if (store?.generateTurnId) {\n return store.generateTurnId()\n }\n return crypto.randomUUID()\n },\n\n setMeta(key: string, value: unknown) {\n data.metadata[key] = value\n touch()\n },\n\n async save() {\n if (!store) {\n throw new Error('No SessionStore configured. Pass a store to createSession() to enable persistence.')\n }\n await store.save(data)\n },\n\n toJSON() {\n return structuredClone(data)\n },\n }\n\n return session\n}\n\n/**\n * Load an existing session from a store.\n */\nexport async function loadSession(store: SessionStore, sessionId: string): Promise<Session | null> {\n const loaded = await store.load(sessionId)\n if (!loaded)\n return null\n\n return createSession({ store, _data: loaded })\n}\n\n// ---------------------------------------------------------------------------\n// Re-export stores\n// ---------------------------------------------------------------------------\n\nexport type { FileMapAdapter, FileMapStoreOptions } from './file-map'\nexport { createMemoryStore } from './memory'\nexport { autoDetectAndConvert, fromAnthropic, fromOpenAI, toAnthropic, toOpenAI } from './messages'\nexport { createRemoteStore } from './remote'\nexport type { RemoteStoreOptions } from './remote'\n\n// NOTE: `createSqliteStore` is intentionally NOT re-exported here. It lives behind\n// the dedicated `zidane/session/sqlite` subpath so that non-Bun consumers don't\n// transitively evaluate `bun:sqlite` when they import from `zidane/session`.\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction generateId(): string {\n return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-BoEW_wCR.js","names":[],"sources":["../src/session/file-map.ts","../src/session/memory.ts","../src/session/remote.ts","../src/session/index.ts"],"sourcesContent":["/**\n * File-map session store.\n *\n * Wraps a narrow 3-method adapter (`get` / `save` / `delete`) that exchanges a flat\n * map of filename → string content. Useful for embedding zidane sessions inside\n * host-provided session backends that only speak in file maps (not zidane's native\n * `SessionStore` shape).\n *\n * Serialization format:\n * - `turns.jsonl` — one `SessionTurn` per line.\n * - `meta.json` — session metadata (id, agentId, status, runs, metadata, timestamps).\n *\n * JSONL for turns keeps history inspectable with tools like `jq` and resilient to\n * partial corruption — parse up to the first bad line and you still have a valid\n * prefix. Metadata lives in its own file so large turn logs don't bloat the\n * metadata path.\n *\n * Scope: each `createFileMapStore` handles a **single session** — the adapter's\n * file map holds at most one zidane session at a time. This matches how host SDKs\n * scope their session stores per conversation.\n *\n * Divergences from the built-in memory / sqlite stores:\n * - `appendTurns` / `updateStatus` / `updateRun` auto-create a minimal `SessionData`\n * record on first write, instead of silently no-oping when the session hasn't been\n * explicitly `save()`-ed. This matches the host-SDK integration path where\n * `createSession(...)` → `agent.run(...)` directly without an explicit `save()` call.\n * - `updateRun` inserts the run if not found in the cached record (rather than\n * silently dropping). Run records therefore always reach the adapter.\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\n/**\n * Host-provided file-map adapter. Three methods exchanging `Record<string, string>`\n * payloads — the whole persistence surface the wrapper needs.\n */\nexport interface FileMapAdapter {\n /** Load the current file map. Returns an empty `files` record when nothing is persisted. */\n get: () => Promise<{ files: Record<string, string> }>\n /** Replace the persisted file map. Full-rewrite semantics. */\n save: (files: Record<string, string>) => Promise<void>\n /** Delete all persisted state. */\n delete: () => Promise<void>\n}\n\nexport interface FileMapStoreOptions {\n /** Filename for the JSONL turns log. Default: `turns.jsonl`. */\n turnsFile?: string\n /** Filename for the metadata JSON. Default: `meta.json`. */\n metaFile?: string\n}\n\ninterface MetaShape {\n id: string\n agentId?: string\n runs: SessionRun[]\n status: SessionData['status']\n metadata: Record<string, unknown>\n createdAt: number\n updatedAt: number\n}\n\nfunction toMeta(data: SessionData): MetaShape {\n return {\n id: data.id,\n agentId: data.agentId,\n runs: data.runs,\n status: data.status,\n metadata: data.metadata,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n }\n}\n\nfunction toData(meta: MetaShape, turns: SessionTurn[]): SessionData {\n return {\n id: meta.id,\n agentId: meta.agentId,\n turns,\n runs: meta.runs,\n status: meta.status,\n metadata: meta.metadata,\n createdAt: meta.createdAt,\n updatedAt: meta.updatedAt,\n }\n}\n\nfunction parseTurnsJsonl(jsonl: string): SessionTurn[] {\n if (!jsonl)\n return []\n const turns: SessionTurn[] = []\n for (const line of jsonl.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed)\n continue\n try {\n turns.push(JSON.parse(trimmed) as SessionTurn)\n }\n catch {\n // Skip malformed lines — preserves the valid prefix on partial corruption.\n }\n }\n return turns\n}\n\nfunction serializeTurnsJsonl(turns: SessionTurn[]): string {\n if (turns.length === 0)\n return ''\n return `${turns.map(t => JSON.stringify(t)).join('\\n')}\\n`\n}\n\n/**\n * Create a single-session `SessionStore` backed by a file-map adapter.\n *\n * @example\n * ```ts\n * const session = await createSession({\n * store: createFileMapStore(hostSessionStore),\n * })\n * ```\n */\nexport function createFileMapStore(\n adapter: FileMapAdapter,\n options: FileMapStoreOptions = {},\n): SessionStore {\n const turnsFile = options.turnsFile ?? 'turns.jsonl'\n const metaFile = options.metaFile ?? 'meta.json'\n\n // Cached view of the persisted session. Populated lazily on first access so the\n // factory itself doesn't do I/O.\n let cached: SessionData | null = null\n let hydrated = false\n\n async function hydrate(): Promise<void> {\n if (hydrated)\n return\n const { files } = await adapter.get()\n const metaRaw = files[metaFile]\n if (metaRaw) {\n let meta: MetaShape | null = null\n try {\n meta = JSON.parse(metaRaw) as MetaShape\n }\n catch {\n meta = null\n }\n if (meta) {\n cached = toData(meta, parseTurnsJsonl(files[turnsFile] ?? ''))\n }\n }\n hydrated = true\n }\n\n async function persist(data: SessionData): Promise<void> {\n const meta = toMeta(data)\n await adapter.save({\n [metaFile]: JSON.stringify(meta, null, 2),\n [turnsFile]: serializeTurnsJsonl(data.turns),\n })\n }\n\n // Ensure `cached` exists for `sessionId`, creating a minimal record when first written.\n // Returns false when `cached` already holds a different sessionId (request ignored).\n async function ensureCachedFor(sessionId: string): Promise<boolean> {\n await hydrate()\n if (cached) {\n return cached.id === sessionId\n }\n const now = Date.now()\n cached = {\n id: sessionId,\n turns: [],\n runs: [],\n status: 'idle',\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }\n hydrated = true\n return true\n }\n\n return {\n async load(sessionId: string): Promise<SessionData | null> {\n await hydrate()\n if (!cached || cached.id !== sessionId)\n return null\n return structuredClone(cached)\n },\n\n async save(data: SessionData): Promise<void> {\n cached = structuredClone(data)\n hydrated = true\n await persist(cached)\n },\n\n async delete(sessionId: string): Promise<void> {\n await hydrate()\n if (cached && cached.id !== sessionId)\n return\n cached = null\n await adapter.delete()\n },\n\n async list(filter): Promise<string[]> {\n await hydrate()\n if (!cached)\n return []\n if (filter?.agentId && cached.agentId !== filter.agentId)\n return []\n // file-map stores exactly one session, so the projectRoot filter\n // either keeps it (match / axis off) or drops it entirely.\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null && cached.projectRoot != null)\n return []\n if (typeof v === 'string' && cached.projectRoot !== v)\n return []\n }\n return [cached.id]\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n cached!.turns.push(...structuredClone(turns))\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number): Promise<SessionTurn[]> {\n await hydrate()\n if (!cached || cached.id !== sessionId)\n return []\n const slice = cached.turns.slice(from, limit !== undefined ? from + limit : undefined)\n return structuredClone(slice) as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n const idx = cached!.runs.findIndex(r => r.id === run.id)\n if (idx >= 0)\n cached!.runs[idx] = structuredClone(run)\n else\n cached!.runs.push(structuredClone(run))\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n cached!.status = status\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n }\n}\n","/**\n * In-memory session store.\n * Useful for development and testing. Data is lost when the process exits.\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\nexport function createMemoryStore(): SessionStore {\n const sessions = new Map<string, SessionData>()\n\n return {\n async load(sessionId: string) {\n const data = sessions.get(sessionId)\n return data ? structuredClone(data) : null\n },\n\n async save(session: SessionData) {\n sessions.set(session.id, structuredClone(session))\n },\n\n async delete(sessionId: string) {\n sessions.delete(sessionId)\n },\n\n async list(filter) {\n let ids = Array.from(sessions.keys())\n if (filter?.agentId) {\n ids = ids.filter(id => sessions.get(id)?.agentId === filter.agentId)\n }\n // `projectRoot` mirrors the SQLite store's tri-state contract:\n // string → only sessions tagged with that root\n // null → only UNTAGGED sessions (legacy / pre-v3 rows)\n // undefined → axis ignored (tagged + untagged both returned)\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null)\n ids = ids.filter(id => sessions.get(id)?.projectRoot == null)\n else if (typeof v === 'string')\n ids = ids.filter(id => sessions.get(id)?.projectRoot === v)\n }\n if (filter?.limit) {\n ids = ids.slice(0, filter.limit)\n }\n return ids\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]) {\n const data = sessions.get(sessionId)\n if (data) {\n data.turns.push(...structuredClone(turns))\n data.updatedAt = Date.now()\n }\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number) {\n const data = sessions.get(sessionId)\n if (!data)\n return []\n const sliced = data.turns.slice(from, limit !== undefined ? from + limit : undefined)\n return structuredClone(sliced) as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun) {\n const data = sessions.get(sessionId)\n if (data) {\n // Upsert — see the matching comment in sqlite.ts's\n // `txnUpdateRun` for the failure mode. tl;dr: the agent's\n // `startRun()` only updates in-memory state; the store first\n // sees the run via `updateRun()` at finalize time, so an\n // update-only impl silently drops every new run.\n const idx = data.runs.findIndex(r => r.id === run.id)\n if (idx >= 0)\n data.runs[idx] = structuredClone(run)\n else\n data.runs.push(structuredClone(run))\n data.updatedAt = Date.now()\n }\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']) {\n const data = sessions.get(sessionId)\n if (data) {\n data.status = status\n data.updatedAt = Date.now()\n }\n },\n }\n}\n","/**\n * Remote session store via HTTP API.\n *\n * Expects a REST API with:\n * GET {url}/sessions/{id} -> SessionData | 404\n * PUT {url}/sessions/{id} -> save SessionData\n * DELETE {url}/sessions/{id} -> delete\n * GET {url}/sessions?agentId=&limit=&projectRoot= -> { ids: string[] }\n * `projectRoot=__null__` is the wire encoding for \"untagged only\".\n * POST {url}/sessions/{id}/turns -> append turns\n * GET {url}/sessions/{id}/turns?from=&limit= -> SessionTurn[]\n * PUT {url}/sessions/{id}/runs/{runId} -> update run\n * PATCH {url}/sessions/{id} -> { status }\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\nexport interface RemoteStoreOptions {\n /** Base URL of the session API */\n url: string\n /** Optional headers (e.g. for authentication) */\n headers?: Record<string, string>\n}\n\nconst TRAILING_SLASH = /\\/$/\n\nexport function createRemoteStore(options: RemoteStoreOptions): SessionStore {\n const baseUrl = options.url.replace(TRAILING_SLASH, '')\n const defaultHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...options.headers,\n }\n\n async function request(path: string, init?: RequestInit): Promise<Response> {\n const url = `${baseUrl}${path}`\n const res = await fetch(url, {\n ...init,\n headers: { ...defaultHeaders, ...init?.headers },\n })\n return res\n }\n\n return {\n async load(sessionId: string) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`)\n if (!res.ok) {\n if (res.status === 404)\n return null\n throw new Error(`Remote session load failed: ${res.status} ${res.statusText}`)\n }\n return await res.json() as SessionData\n },\n\n async save(session: SessionData) {\n const res = await request(`/sessions/${encodeURIComponent(session.id)}`, {\n method: 'PUT',\n body: JSON.stringify(session),\n })\n if (!res.ok) {\n throw new Error(`Remote session save failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async delete(sessionId: string) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`, {\n method: 'DELETE',\n })\n if (!res.ok && res.status !== 404) {\n throw new Error(`Remote session delete failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async list(filter) {\n const params = new URLSearchParams()\n if (filter?.agentId)\n params.set('agentId', filter.agentId)\n if (filter?.limit)\n params.set('limit', String(filter.limit))\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null)\n params.set('projectRoot', '__null__')\n else if (typeof v === 'string')\n params.set('projectRoot', v)\n }\n\n const query = params.toString()\n const path = query ? `/sessions?${query}` : '/sessions'\n const res = await request(path)\n\n if (!res.ok) {\n throw new Error(`Remote session list failed: ${res.status} ${res.statusText}`)\n }\n\n const body = await res.json() as { ids: string[] }\n return body.ids\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}/turns`, {\n method: 'POST',\n body: JSON.stringify(turns),\n })\n if (!res.ok) {\n throw new Error(`Remote appendTurns failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number) {\n const params = new URLSearchParams()\n if (from)\n params.set('from', String(from))\n if (limit !== undefined)\n params.set('limit', String(limit))\n\n const query = params.toString()\n const path = `/sessions/${encodeURIComponent(sessionId)}/turns${query ? `?${query}` : ''}`\n const res = await request(path)\n\n if (!res.ok) {\n throw new Error(`Remote getTurns failed: ${res.status} ${res.statusText}`)\n }\n\n return await res.json() as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun) {\n const res = await request(\n `/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(run.id)}`,\n {\n method: 'PUT',\n body: JSON.stringify(run),\n },\n )\n if (!res.ok) {\n throw new Error(`Remote updateRun failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n })\n if (!res.ok) {\n throw new Error(`Remote updateStatus failed: ${res.status} ${res.statusText}`)\n }\n },\n }\n}\n","/**\n * Session management for agents.\n *\n * A session tracks identity, turn history, and run metadata.\n * Plug in any storage backend by implementing the SessionStore interface,\n * or use one of the built-in stores: memory, sqlite, remote.\n */\n\nimport type { SessionTurn, TurnUsage } from '../types'\n\nexport type { SessionContentBlock, SessionMessage, SessionTurn } from '../types'\nexport { createFileMapStore } from './file-map'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SessionRun {\n id: string\n startedAt: number\n endedAt?: number\n prompt: string\n status: 'running' | 'completed' | 'aborted' | 'error'\n turns?: number\n tokensIn?: number\n tokensOut?: number\n error?: string\n /** Per-turn usage breakdown */\n turnUsage?: TurnUsage[]\n /** Total usage across all turns */\n totalUsage?: TurnUsage\n /** Estimated cost in USD */\n cost?: number\n /**\n * The run that spawned this one, when the agent is a subagent sharing its\n * parent's session. Undefined on top-level `agent.run()`. Consumers can walk\n * `runs` by `parentRunId` to reconstruct the subagent tree.\n */\n parentRunId?: string\n /**\n * Zero-based subagent depth. 0 = top-level run, 1 = direct child, …\n * Recorded here so hosts can query/filter by level without walking the tree.\n */\n depth?: number\n}\n\nexport interface SessionData {\n id: string\n agentId?: string\n /**\n * Absolute path of the project this session belongs to — typically\n * the git root resolved from `cwd` at creation time, falling back to\n * `cwd` itself when not in a git repo. Set ONCE on creation and never\n * mutated thereafter (the session \"belongs\" to that project forever).\n *\n * Used by the TUI's sessions list to filter rows by current project,\n * so the user only sees conversations relevant to where they are\n * working — without needing one `.{prefix}/` directory per project.\n *\n * `undefined` on pre-tagging legacy sessions; the chat layer treats\n * those as \"untagged\" and hides them unless `Settings.showAllProjects`\n * is on.\n */\n projectRoot?: string\n turns: SessionTurn[]\n runs: SessionRun[]\n status: 'idle' | 'running' | 'completed' | 'error'\n metadata: Record<string, unknown>\n createdAt: number\n updatedAt: number\n}\n\n// ---------------------------------------------------------------------------\n// SessionStore interface (pluggable backend)\n// ---------------------------------------------------------------------------\n\nexport interface SessionStore {\n /** Optional: generate a session ID server-side (e.g. Supabase UUID). */\n generateSessionId?: () => string | Promise<string>\n\n /** Optional: generate a turn ID server-side. */\n generateTurnId?: () => string | Promise<string>\n\n /** Load a session by ID. Returns null if not found. */\n load: (sessionId: string) => Promise<SessionData | null>\n\n /** Save a session (create or update, full document). */\n save: (session: SessionData) => Promise<void>\n\n /** Delete a session. */\n delete: (sessionId: string) => Promise<void>\n\n /**\n * List session IDs, optionally filtered. `projectRoot` restricts to\n * sessions whose `SessionData.projectRoot` matches exactly — untagged\n * (legacy) sessions are NOT returned under that filter; pass `null`\n * explicitly to ask for untagged ones, or omit the field to ignore\n * the axis entirely. `agentId` filters by recorded agent; the two\n * conditions AND together when both are set.\n */\n list: (filter?: { agentId?: string, limit?: number, projectRoot?: string | null }) => Promise<string[]>\n\n /** Append new turns to a session (incremental, avoids full re-save). */\n appendTurns: (sessionId: string, turns: SessionTurn[]) => Promise<void>\n\n /** Return a slice of turns for a session. */\n getTurns: (sessionId: string, from?: number, limit?: number) => Promise<SessionTurn[]>\n\n /**\n * Persist a run record (called after completeRun / abortRun / errorRun).\n *\n * **Upsert semantics required.** The agent's run lifecycle calls\n * `session.startRun()` to register a new run in IN-MEMORY state and\n * then `updateRun()` at finalize. The store doesn't see the run any\n * earlier — `appendTurns` is the only intermediate call, and it\n * persists turns but not runs. An update-only implementation would\n * silently drop every newly-created run record at finalize, leaving\n * the on-disk `runs[]` missing entries that the persisted turns\n * reference (which `eventsFromTurns` then mis-classifies via\n * `ancestryOf` → broken depth, broken `child-N` labels, broken\n * `userPrompts` history).\n *\n * Implementations MUST insert when the id doesn't exist and update\n * when it does. The bundled `sqlite` / `memory` / `file-map` stores\n * all follow this contract; remote stores using HTTP PUT get upsert\n * for free via REST semantics.\n */\n updateRun: (sessionId: string, run: SessionRun) => Promise<void>\n\n /** Update the top-level status of a session. */\n updateStatus: (sessionId: string, status: SessionData['status']) => Promise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Session (live instance wrapping a SessionData)\n// ---------------------------------------------------------------------------\n\nexport interface Session {\n /** Session ID */\n readonly id: string\n\n /** Agent ID (optional label) */\n readonly agentId?: string\n\n /**\n * Project this session was created under — see {@link SessionData.projectRoot}.\n * Set once on creation; surfaces here for read-only inspection.\n */\n readonly projectRoot?: string\n\n /** Current turn history */\n readonly turns: SessionTurn[]\n\n /**\n * True when this session has no turns yet.\n *\n * Use this as a first-prompt signal when setting up a run — e.g. writing initial\n * configuration only on fresh sessions. Equivalent to `turns.length === 0`.\n */\n readonly isEmpty: boolean\n\n /** Top-level session status */\n readonly status: SessionData['status']\n\n /** All runs in this session */\n readonly runs: SessionRun[]\n\n /** Arbitrary metadata */\n readonly metadata: Record<string, unknown>\n\n /**\n * Start tracking a new run. `extras.parentRunId` + `extras.depth` are\n * populated by the spawn tool when a child agent shares its parent's\n * session; regular top-level `agent.run()` calls omit them.\n */\n startRun: (runId: string, prompt?: string, extras?: { parentRunId?: string, depth?: number }) => void\n\n /** Mark a run as completed */\n completeRun: (runId: string, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Mark a run as aborted */\n /**\n * Optional `stats` lets the agent backfill the run's token totals when\n * the abort happened *after* the loop accumulated meaningful usage —\n * common when the user presses esc mid-streaming. Without it, the run\n * record reads `0 in / 0 out` on reload regardless of how much was\n * spent before the abort. Same shape as `completeRun`'s stats so the\n * persisted `totalUsage` aggregate stays consistent across paths.\n */\n abortRun: (runId: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Mark a run as errored */\n /** Optional `stats` — same rationale as `abortRun.stats`. */\n errorRun: (runId: string, error: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Append turns to in-memory history AND persist via store.appendTurns (if store present) */\n appendTurns: (turns: SessionTurn[]) => Promise<void>\n\n /** Replace all turns in-memory (does not persist — use save() for that) */\n setTurns: (turns: SessionTurn[]) => void\n\n /**\n * Replace all runs in-memory (does not persist — use save() for that).\n * Mirrors {@link setTurns} for the fork / restore case: callers that\n * bootstrap a session from an externally-derived snapshot (e.g.\n * `onForkTurn` copying parent runs into a child session) need this so\n * the cloned runs land in `data.runs` before the first `save()`.\n * Production agent runs continue to mutate runs via `startRun` /\n * `completeRun` / `updateRun`; this is the bulk-replace escape hatch.\n */\n setRuns: (runs: SessionRun[]) => void\n\n /** Update the session status in memory AND via store.updateStatus (if store present) */\n updateStatus: (status: SessionData['status']) => Promise<void>\n\n /** Persist an updated run record via store.updateRun (if store present) */\n updateRun: (run: SessionRun) => Promise<void>\n\n /** Generate a turn ID using store.generateTurnId if available, else crypto.randomUUID() */\n generateTurnId: () => string | Promise<string>\n\n /** Set metadata key */\n setMeta: (key: string, value: unknown) => void\n\n /** Persist the full session document to the store */\n save: () => Promise<void>\n\n /** Serialize to SessionData */\n toJSON: () => SessionData\n}\n\n// ---------------------------------------------------------------------------\n// createSession\n// ---------------------------------------------------------------------------\n\nexport interface CreateSessionOptions {\n /** Session ID. If omitted and store provides generateSessionId, that is used. */\n id?: string\n /** Agent ID label */\n agentId?: string\n /**\n * Project tag — see {@link SessionData.projectRoot}. Stamped once on\n * creation; ignored when `_data` is set (restoring an existing\n * session preserves whatever was already persisted there). The TUI\n * resolves this from `findGitRoot(cwd) ?? cwd` so sessions started\n * from the same repo (no matter which subdir) share one tag.\n */\n projectRoot?: string\n /** Initial metadata */\n metadata?: Record<string, unknown>\n /** Storage backend (optional, enables save/load) */\n store?: SessionStore\n // @internal: restore from existing data (bypasses id/agentId/metadata options)\n _data?: SessionData\n}\n\n/**\n * Create a new session.\n * Async so stores that generate IDs server-side (e.g. Supabase) can be supported.\n */\nexport async function createSession(options: CreateSessionOptions = {}): Promise<Session> {\n const store = options.store\n const now = Date.now()\n\n let sessionId = options.id\n if (!sessionId && store?.generateSessionId) {\n sessionId = await store.generateSessionId()\n }\n if (!sessionId) {\n sessionId = generateId()\n }\n\n const data: SessionData = options._data ?? {\n id: sessionId,\n agentId: options.agentId,\n // Stamp the project tag at creation only — restored sessions (the\n // `_data` branch above) keep whatever was already persisted, even\n // if `options.projectRoot` differs. A session's project identity\n // is sticky for its lifetime; loading the same session from a\n // different cwd doesn't re-home it.\n ...(options.projectRoot ? { projectRoot: options.projectRoot } : {}),\n turns: [],\n runs: [],\n status: 'idle',\n metadata: options.metadata ?? {},\n createdAt: now,\n updatedAt: now,\n }\n\n function touch() {\n data.updatedAt = Date.now()\n }\n\n function findRun(runId: string): SessionRun | undefined {\n return data.runs.find(r => r.id === runId)\n }\n\n /**\n * Apply per-run usage stats onto a SessionRun. Shared by `completeRun`,\n * `abortRun`, and `errorRun` so the on-disk shape stays consistent across\n * exit paths — historically only `completeRun` filled these in, and\n * aborted/errored runs rendered `0 in / 0 out` on reload regardless of\n * how much was actually consumed.\n */\n function applyRunStats(run: SessionRun, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n run.turns = stats.turns\n run.tokensIn = stats.tokensIn\n run.tokensOut = stats.tokensOut\n if (stats.turnUsage) {\n run.turnUsage = stats.turnUsage\n const total = stats.turnUsage.reduce((acc, t) => ({\n input: acc.input + t.input,\n output: acc.output + t.output,\n cacheCreation: (acc.cacheCreation ?? 0) + (t.cacheCreation ?? 0),\n cacheRead: (acc.cacheRead ?? 0) + (t.cacheRead ?? 0),\n thinking: (acc.thinking ?? 0) + (t.thinking ?? 0),\n }), { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, thinking: 0 })\n run.totalUsage = {\n input: total.input,\n output: total.output,\n ...(total.cacheCreation ? { cacheCreation: total.cacheCreation } : {}),\n ...(total.cacheRead ? { cacheRead: total.cacheRead } : {}),\n ...(total.thinking ? { thinking: total.thinking } : {}),\n }\n }\n if (stats.cost !== undefined)\n run.cost = stats.cost\n }\n\n const session: Session = {\n get id() { return data.id },\n get agentId() { return data.agentId },\n get projectRoot() { return data.projectRoot },\n get turns() { return data.turns },\n get isEmpty() { return data.turns.length === 0 },\n get status() { return data.status },\n get runs() { return data.runs },\n get metadata() { return data.metadata },\n\n startRun(runId: string, prompt?: string, extras?: { parentRunId?: string, depth?: number }) {\n data.runs.push({\n id: runId,\n startedAt: Date.now(),\n prompt: prompt ?? '',\n status: 'running',\n ...(extras?.parentRunId ? { parentRunId: extras.parentRunId } : {}),\n ...(typeof extras?.depth === 'number' ? { depth: extras.depth } : {}),\n })\n touch()\n },\n\n completeRun(runId: string, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'completed'\n run.endedAt = Date.now()\n applyRunStats(run, stats)\n }\n touch()\n },\n\n abortRun(runId: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'aborted'\n run.endedAt = Date.now()\n // Backfill tokens when available so an aborted run's session\n // ledger reflects actual consumption rather than `0 in / 0 out`.\n if (stats)\n applyRunStats(run, stats)\n }\n touch()\n },\n\n errorRun(runId: string, error: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'error'\n run.endedAt = Date.now()\n run.error = error\n if (stats)\n applyRunStats(run, stats)\n }\n touch()\n },\n\n async appendTurns(turns: SessionTurn[]) {\n data.turns.push(...turns)\n touch()\n if (store) {\n await store.appendTurns(data.id, turns)\n }\n },\n\n setTurns(turns: SessionTurn[]) {\n data.turns = turns\n touch()\n },\n\n setRuns(runs: SessionRun[]) {\n data.runs = runs\n touch()\n },\n\n async updateStatus(status: SessionData['status']) {\n data.status = status\n touch()\n if (store) {\n await store.updateStatus(data.id, status)\n }\n },\n\n async updateRun(run: SessionRun) {\n if (store) {\n await store.updateRun(data.id, run)\n }\n },\n\n generateTurnId() {\n if (store?.generateTurnId) {\n return store.generateTurnId()\n }\n return crypto.randomUUID()\n },\n\n setMeta(key: string, value: unknown) {\n data.metadata[key] = value\n touch()\n },\n\n async save() {\n if (!store) {\n throw new Error('No SessionStore configured. Pass a store to createSession() to enable persistence.')\n }\n await store.save(data)\n },\n\n toJSON() {\n return structuredClone(data)\n },\n }\n\n return session\n}\n\n/**\n * Load an existing session from a store.\n */\nexport async function loadSession(store: SessionStore, sessionId: string): Promise<Session | null> {\n const loaded = await store.load(sessionId)\n if (!loaded)\n return null\n\n return createSession({ store, _data: loaded })\n}\n\n// ---------------------------------------------------------------------------\n// Re-export stores\n// ---------------------------------------------------------------------------\n\nexport type { FileMapAdapter, FileMapStoreOptions } from './file-map'\nexport { createMemoryStore } from './memory'\nexport { autoDetectAndConvert, fromAnthropic, fromOpenAI, toAnthropic, toOpenAI } from './messages'\nexport { createRemoteStore } from './remote'\nexport type { RemoteStoreOptions } from './remote'\n\n// NOTE: `createSqliteStore` is intentionally NOT re-exported here. It lives behind\n// the dedicated `zidane/session/sqlite` subpath so that non-Bun consumers don't\n// transitively evaluate `bun:sqlite` when they import from `zidane/session`.\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction generateId(): string {\n return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`\n}\n"],"mappings":";;AA+DA,SAAS,OAAO,MAA8B;CAC5C,OAAO;EACL,IAAI,KAAK;EACT,SAAS,KAAK;EACd,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,WAAW,KAAK;EAChB,WAAW,KAAK;EACjB;;AAGH,SAAS,OAAO,MAAiB,OAAmC;CAClE,OAAO;EACL,IAAI,KAAK;EACT,SAAS,KAAK;EACd;EACA,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,WAAW,KAAK;EAChB,WAAW,KAAK;EACjB;;AAGH,SAAS,gBAAgB,OAA8B;CACrD,IAAI,CAAC,OACH,OAAO,EAAE;CACX,MAAM,QAAuB,EAAE;CAC/B,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK,EAAE;EACpC,MAAM,UAAU,KAAK,MAAM;EAC3B,IAAI,CAAC,SACH;EACF,IAAI;GACF,MAAM,KAAK,KAAK,MAAM,QAAQ,CAAgB;UAE1C;;CAIR,OAAO;;AAGT,SAAS,oBAAoB,OAA8B;CACzD,IAAI,MAAM,WAAW,GACnB,OAAO;CACT,OAAO,GAAG,MAAM,KAAI,MAAK,KAAK,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC;;;;;;;;;;;;AAazD,SAAgB,mBACd,SACA,UAA+B,EAAE,EACnB;CACd,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,WAAW,QAAQ,YAAY;CAIrC,IAAI,SAA6B;CACjC,IAAI,WAAW;CAEf,eAAe,UAAyB;EACtC,IAAI,UACF;EACF,MAAM,EAAE,UAAU,MAAM,QAAQ,KAAK;EACrC,MAAM,UAAU,MAAM;EACtB,IAAI,SAAS;GACX,IAAI,OAAyB;GAC7B,IAAI;IACF,OAAO,KAAK,MAAM,QAAQ;WAEtB;IACJ,OAAO;;GAET,IAAI,MACF,SAAS,OAAO,MAAM,gBAAgB,MAAM,cAAc,GAAG,CAAC;;EAGlE,WAAW;;CAGb,eAAe,QAAQ,MAAkC;EACvD,MAAM,OAAO,OAAO,KAAK;EACzB,MAAM,QAAQ,KAAK;IAChB,WAAW,KAAK,UAAU,MAAM,MAAM,EAAE;IACxC,YAAY,oBAAoB,KAAK,MAAM;GAC7C,CAAC;;CAKJ,eAAe,gBAAgB,WAAqC;EAClE,MAAM,SAAS;EACf,IAAI,QACF,OAAO,OAAO,OAAO;EAEvB,MAAM,MAAM,KAAK,KAAK;EACtB,SAAS;GACP,IAAI;GACJ,OAAO,EAAE;GACT,MAAM,EAAE;GACR,QAAQ;GACR,UAAU,EAAE;GACZ,WAAW;GACX,WAAW;GACZ;EACD,WAAW;EACX,OAAO;;CAGT,OAAO;EACL,MAAM,KAAK,WAAgD;GACzD,MAAM,SAAS;GACf,IAAI,CAAC,UAAU,OAAO,OAAO,WAC3B,OAAO;GACT,OAAO,gBAAgB,OAAO;;EAGhC,MAAM,KAAK,MAAkC;GAC3C,SAAS,gBAAgB,KAAK;GAC9B,WAAW;GACX,MAAM,QAAQ,OAAO;;EAGvB,MAAM,OAAO,WAAkC;GAC7C,MAAM,SAAS;GACf,IAAI,UAAU,OAAO,OAAO,WAC1B;GACF,SAAS;GACT,MAAM,QAAQ,QAAQ;;EAGxB,MAAM,KAAK,QAA2B;GACpC,MAAM,SAAS;GACf,IAAI,CAAC,QACH,OAAO,EAAE;GACX,IAAI,QAAQ,WAAW,OAAO,YAAY,OAAO,SAC/C,OAAO,EAAE;GAGX,IAAI,UAAU,iBAAiB,QAAQ;IACrC,MAAM,IAAI,OAAO;IACjB,IAAI,MAAM,QAAQ,OAAO,eAAe,MACtC,OAAO,EAAE;IACX,IAAI,OAAO,MAAM,YAAY,OAAO,gBAAgB,GAClD,OAAO,EAAE;;GAEb,OAAO,CAAC,OAAO,GAAG;;EAGpB,MAAM,YAAY,WAAmB,OAAqC;GAExE,IAAI,CAAC,MADY,gBAAgB,UAAU,EAEzC;GACF,OAAQ,MAAM,KAAK,GAAG,gBAAgB,MAAM,CAAC;GAC7C,OAAQ,YAAY,KAAK,KAAK;GAC9B,MAAM,QAAQ,OAAQ;;EAGxB,MAAM,SAAS,WAAmB,OAAO,GAAG,OAAwC;GAClF,MAAM,SAAS;GACf,IAAI,CAAC,UAAU,OAAO,OAAO,WAC3B,OAAO,EAAE;GACX,MAAM,QAAQ,OAAO,MAAM,MAAM,MAAM,UAAU,KAAA,IAAY,OAAO,QAAQ,KAAA,EAAU;GACtF,OAAO,gBAAgB,MAAM;;EAG/B,MAAM,UAAU,WAAmB,KAAgC;GAEjE,IAAI,CAAC,MADY,gBAAgB,UAAU,EAEzC;GACF,MAAM,MAAM,OAAQ,KAAK,WAAU,MAAK,EAAE,OAAO,IAAI,GAAG;GACxD,IAAI,OAAO,GACT,OAAQ,KAAK,OAAO,gBAAgB,IAAI;QAExC,OAAQ,KAAK,KAAK,gBAAgB,IAAI,CAAC;GACzC,OAAQ,YAAY,KAAK,KAAK;GAC9B,MAAM,QAAQ,OAAQ;;EAGxB,MAAM,aAAa,WAAmB,QAA8C;GAElF,IAAI,CAAC,MADY,gBAAgB,UAAU,EAEzC;GACF,OAAQ,SAAS;GACjB,OAAQ,YAAY,KAAK,KAAK;GAC9B,MAAM,QAAQ,OAAQ;;EAEzB;;;;AC7PH,SAAgB,oBAAkC;CAChD,MAAM,2BAAW,IAAI,KAA0B;CAE/C,OAAO;EACL,MAAM,KAAK,WAAmB;GAC5B,MAAM,OAAO,SAAS,IAAI,UAAU;GACpC,OAAO,OAAO,gBAAgB,KAAK,GAAG;;EAGxC,MAAM,KAAK,SAAsB;GAC/B,SAAS,IAAI,QAAQ,IAAI,gBAAgB,QAAQ,CAAC;;EAGpD,MAAM,OAAO,WAAmB;GAC9B,SAAS,OAAO,UAAU;;EAG5B,MAAM,KAAK,QAAQ;GACjB,IAAI,MAAM,MAAM,KAAK,SAAS,MAAM,CAAC;GACrC,IAAI,QAAQ,SACV,MAAM,IAAI,QAAO,OAAM,SAAS,IAAI,GAAG,EAAE,YAAY,OAAO,QAAQ;GAMtE,IAAI,UAAU,iBAAiB,QAAQ;IACrC,MAAM,IAAI,OAAO;IACjB,IAAI,MAAM,MACR,MAAM,IAAI,QAAO,OAAM,SAAS,IAAI,GAAG,EAAE,eAAe,KAAK;SAC1D,IAAI,OAAO,MAAM,UACpB,MAAM,IAAI,QAAO,OAAM,SAAS,IAAI,GAAG,EAAE,gBAAgB,EAAE;;GAE/D,IAAI,QAAQ,OACV,MAAM,IAAI,MAAM,GAAG,OAAO,MAAM;GAElC,OAAO;;EAGT,MAAM,YAAY,WAAmB,OAAsB;GACzD,MAAM,OAAO,SAAS,IAAI,UAAU;GACpC,IAAI,MAAM;IACR,KAAK,MAAM,KAAK,GAAG,gBAAgB,MAAM,CAAC;IAC1C,KAAK,YAAY,KAAK,KAAK;;;EAI/B,MAAM,SAAS,WAAmB,OAAO,GAAG,OAAgB;GAC1D,MAAM,OAAO,SAAS,IAAI,UAAU;GACpC,IAAI,CAAC,MACH,OAAO,EAAE;GACX,MAAM,SAAS,KAAK,MAAM,MAAM,MAAM,UAAU,KAAA,IAAY,OAAO,QAAQ,KAAA,EAAU;GACrF,OAAO,gBAAgB,OAAO;;EAGhC,MAAM,UAAU,WAAmB,KAAiB;GAClD,MAAM,OAAO,SAAS,IAAI,UAAU;GACpC,IAAI,MAAM;IAMR,MAAM,MAAM,KAAK,KAAK,WAAU,MAAK,EAAE,OAAO,IAAI,GAAG;IACrD,IAAI,OAAO,GACT,KAAK,KAAK,OAAO,gBAAgB,IAAI;SAErC,KAAK,KAAK,KAAK,gBAAgB,IAAI,CAAC;IACtC,KAAK,YAAY,KAAK,KAAK;;;EAI/B,MAAM,aAAa,WAAmB,QAA+B;GACnE,MAAM,OAAO,SAAS,IAAI,UAAU;GACpC,IAAI,MAAM;IACR,KAAK,SAAS;IACd,KAAK,YAAY,KAAK,KAAK;;;EAGhC;;;;AC9DH,MAAM,iBAAiB;AAEvB,SAAgB,kBAAkB,SAA2C;CAC3E,MAAM,UAAU,QAAQ,IAAI,QAAQ,gBAAgB,GAAG;CACvD,MAAM,iBAAyC;EAC7C,gBAAgB;EAChB,GAAG,QAAQ;EACZ;CAED,eAAe,QAAQ,MAAc,MAAuC;EAC1E,MAAM,MAAM,GAAG,UAAU;EAKzB,OAAO,MAJW,MAAM,KAAK;GAC3B,GAAG;GACH,SAAS;IAAE,GAAG;IAAgB,GAAG,MAAM;IAAS;GACjD,CAAC;;CAIJ,OAAO;EACL,MAAM,KAAK,WAAmB;GAC5B,MAAM,MAAM,MAAM,QAAQ,aAAa,mBAAmB,UAAU,GAAG;GACvE,IAAI,CAAC,IAAI,IAAI;IACX,IAAI,IAAI,WAAW,KACjB,OAAO;IACT,MAAM,IAAI,MAAM,+BAA+B,IAAI,OAAO,GAAG,IAAI,aAAa;;GAEhF,OAAO,MAAM,IAAI,MAAM;;EAGzB,MAAM,KAAK,SAAsB;GAC/B,MAAM,MAAM,MAAM,QAAQ,aAAa,mBAAmB,QAAQ,GAAG,IAAI;IACvE,QAAQ;IACR,MAAM,KAAK,UAAU,QAAQ;IAC9B,CAAC;GACF,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,+BAA+B,IAAI,OAAO,GAAG,IAAI,aAAa;;EAIlF,MAAM,OAAO,WAAmB;GAC9B,MAAM,MAAM,MAAM,QAAQ,aAAa,mBAAmB,UAAU,IAAI,EACtE,QAAQ,UACT,CAAC;GACF,IAAI,CAAC,IAAI,MAAM,IAAI,WAAW,KAC5B,MAAM,IAAI,MAAM,iCAAiC,IAAI,OAAO,GAAG,IAAI,aAAa;;EAIpF,MAAM,KAAK,QAAQ;GACjB,MAAM,SAAS,IAAI,iBAAiB;GACpC,IAAI,QAAQ,SACV,OAAO,IAAI,WAAW,OAAO,QAAQ;GACvC,IAAI,QAAQ,OACV,OAAO,IAAI,SAAS,OAAO,OAAO,MAAM,CAAC;GAC3C,IAAI,UAAU,iBAAiB,QAAQ;IACrC,MAAM,IAAI,OAAO;IACjB,IAAI,MAAM,MACR,OAAO,IAAI,eAAe,WAAW;SAClC,IAAI,OAAO,MAAM,UACpB,OAAO,IAAI,eAAe,EAAE;;GAGhC,MAAM,QAAQ,OAAO,UAAU;GAE/B,MAAM,MAAM,MAAM,QADL,QAAQ,aAAa,UAAU,YACb;GAE/B,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,+BAA+B,IAAI,OAAO,GAAG,IAAI,aAAa;GAIhF,QAAO,MADY,IAAI,MAAM,EACjB;;EAGd,MAAM,YAAY,WAAmB,OAAsB;GACzD,MAAM,MAAM,MAAM,QAAQ,aAAa,mBAAmB,UAAU,CAAC,SAAS;IAC5E,QAAQ;IACR,MAAM,KAAK,UAAU,MAAM;IAC5B,CAAC;GACF,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,8BAA8B,IAAI,OAAO,GAAG,IAAI,aAAa;;EAIjF,MAAM,SAAS,WAAmB,OAAO,GAAG,OAAgB;GAC1D,MAAM,SAAS,IAAI,iBAAiB;GACpC,IAAI,MACF,OAAO,IAAI,QAAQ,OAAO,KAAK,CAAC;GAClC,IAAI,UAAU,KAAA,GACZ,OAAO,IAAI,SAAS,OAAO,MAAM,CAAC;GAEpC,MAAM,QAAQ,OAAO,UAAU;GAE/B,MAAM,MAAM,MAAM,QAAQ,aADA,mBAAmB,UAAU,CAAC,QAAQ,QAAQ,IAAI,UAAU,KACvD;GAE/B,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,2BAA2B,IAAI,OAAO,GAAG,IAAI,aAAa;GAG5E,OAAO,MAAM,IAAI,MAAM;;EAGzB,MAAM,UAAU,WAAmB,KAAiB;GAClD,MAAM,MAAM,MAAM,QAChB,aAAa,mBAAmB,UAAU,CAAC,QAAQ,mBAAmB,IAAI,GAAG,IAC7E;IACE,QAAQ;IACR,MAAM,KAAK,UAAU,IAAI;IAC1B,CACF;GACD,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,4BAA4B,IAAI,OAAO,GAAG,IAAI,aAAa;;EAI/E,MAAM,aAAa,WAAmB,QAA+B;GACnE,MAAM,MAAM,MAAM,QAAQ,aAAa,mBAAmB,UAAU,IAAI;IACtE,QAAQ;IACR,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;IACjC,CAAC;GACF,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,+BAA+B,IAAI,OAAO,GAAG,IAAI,aAAa;;EAGnF;;;;;;;;AC+GH,eAAsB,cAAc,UAAgC,EAAE,EAAoB;CACxF,MAAM,QAAQ,QAAQ;CACtB,MAAM,MAAM,KAAK,KAAK;CAEtB,IAAI,YAAY,QAAQ;CACxB,IAAI,CAAC,aAAa,OAAO,mBACvB,YAAY,MAAM,MAAM,mBAAmB;CAE7C,IAAI,CAAC,WACH,YAAY,YAAY;CAG1B,MAAM,OAAoB,QAAQ,SAAS;EACzC,IAAI;EACJ,SAAS,QAAQ;EAMjB,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,aAAa,GAAG,EAAE;EACnE,OAAO,EAAE;EACT,MAAM,EAAE;EACR,QAAQ;EACR,UAAU,QAAQ,YAAY,EAAE;EAChC,WAAW;EACX,WAAW;EACZ;CAED,SAAS,QAAQ;EACf,KAAK,YAAY,KAAK,KAAK;;CAG7B,SAAS,QAAQ,OAAuC;EACtD,OAAO,KAAK,KAAK,MAAK,MAAK,EAAE,OAAO,MAAM;;;;;;;;;CAU5C,SAAS,cAAc,KAAiB,OAAuG;EAC7I,IAAI,QAAQ,MAAM;EAClB,IAAI,WAAW,MAAM;EACrB,IAAI,YAAY,MAAM;EACtB,IAAI,MAAM,WAAW;GACnB,IAAI,YAAY,MAAM;GACtB,MAAM,QAAQ,MAAM,UAAU,QAAQ,KAAK,OAAO;IAChD,OAAO,IAAI,QAAQ,EAAE;IACrB,QAAQ,IAAI,SAAS,EAAE;IACvB,gBAAgB,IAAI,iBAAiB,MAAM,EAAE,iBAAiB;IAC9D,YAAY,IAAI,aAAa,MAAM,EAAE,aAAa;IAClD,WAAW,IAAI,YAAY,MAAM,EAAE,YAAY;IAChD,GAAG;IAAE,OAAO;IAAG,QAAQ;IAAG,eAAe;IAAG,WAAW;IAAG,UAAU;IAAG,CAAC;GACzE,IAAI,aAAa;IACf,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,GAAI,MAAM,gBAAgB,EAAE,eAAe,MAAM,eAAe,GAAG,EAAE;IACrE,GAAI,MAAM,YAAY,EAAE,WAAW,MAAM,WAAW,GAAG,EAAE;IACzD,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,UAAU,GAAG,EAAE;IACvD;;EAEH,IAAI,MAAM,SAAS,KAAA,GACjB,IAAI,OAAO,MAAM;;CAoHrB,OAAO;EAhHL,IAAI,KAAK;GAAE,OAAO,KAAK;;EACvB,IAAI,UAAU;GAAE,OAAO,KAAK;;EAC5B,IAAI,cAAc;GAAE,OAAO,KAAK;;EAChC,IAAI,QAAQ;GAAE,OAAO,KAAK;;EAC1B,IAAI,UAAU;GAAE,OAAO,KAAK,MAAM,WAAW;;EAC7C,IAAI,SAAS;GAAE,OAAO,KAAK;;EAC3B,IAAI,OAAO;GAAE,OAAO,KAAK;;EACzB,IAAI,WAAW;GAAE,OAAO,KAAK;;EAE7B,SAAS,OAAe,QAAiB,QAAmD;GAC1F,KAAK,KAAK,KAAK;IACb,IAAI;IACJ,WAAW,KAAK,KAAK;IACrB,QAAQ,UAAU;IAClB,QAAQ;IACR,GAAI,QAAQ,cAAc,EAAE,aAAa,OAAO,aAAa,GAAG,EAAE;IAClE,GAAI,OAAO,QAAQ,UAAU,WAAW,EAAE,OAAO,OAAO,OAAO,GAAG,EAAE;IACrE,CAAC;GACF,OAAO;;EAGT,YAAY,OAAe,OAAuG;GAChI,MAAM,MAAM,QAAQ,MAAM;GAC1B,IAAI,KAAK;IACP,IAAI,SAAS;IACb,IAAI,UAAU,KAAK,KAAK;IACxB,cAAc,KAAK,MAAM;;GAE3B,OAAO;;EAGT,SAAS,OAAe,OAAwG;GAC9H,MAAM,MAAM,QAAQ,MAAM;GAC1B,IAAI,KAAK;IACP,IAAI,SAAS;IACb,IAAI,UAAU,KAAK,KAAK;IAGxB,IAAI,OACF,cAAc,KAAK,MAAM;;GAE7B,OAAO;;EAGT,SAAS,OAAe,OAAe,OAAwG;GAC7I,MAAM,MAAM,QAAQ,MAAM;GAC1B,IAAI,KAAK;IACP,IAAI,SAAS;IACb,IAAI,UAAU,KAAK,KAAK;IACxB,IAAI,QAAQ;IACZ,IAAI,OACF,cAAc,KAAK,MAAM;;GAE7B,OAAO;;EAGT,MAAM,YAAY,OAAsB;GACtC,KAAK,MAAM,KAAK,GAAG,MAAM;GACzB,OAAO;GACP,IAAI,OACF,MAAM,MAAM,YAAY,KAAK,IAAI,MAAM;;EAI3C,SAAS,OAAsB;GAC7B,KAAK,QAAQ;GACb,OAAO;;EAGT,QAAQ,MAAoB;GAC1B,KAAK,OAAO;GACZ,OAAO;;EAGT,MAAM,aAAa,QAA+B;GAChD,KAAK,SAAS;GACd,OAAO;GACP,IAAI,OACF,MAAM,MAAM,aAAa,KAAK,IAAI,OAAO;;EAI7C,MAAM,UAAU,KAAiB;GAC/B,IAAI,OACF,MAAM,MAAM,UAAU,KAAK,IAAI,IAAI;;EAIvC,iBAAiB;GACf,IAAI,OAAO,gBACT,OAAO,MAAM,gBAAgB;GAE/B,OAAO,OAAO,YAAY;;EAG5B,QAAQ,KAAa,OAAgB;GACnC,KAAK,SAAS,OAAO;GACrB,OAAO;;EAGT,MAAM,OAAO;GACX,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,qFAAqF;GAEvG,MAAM,MAAM,KAAK,KAAK;;EAGxB,SAAS;GACP,OAAO,gBAAgB,KAAK;;EAIlB;;;;;AAMhB,eAAsB,YAAY,OAAqB,WAA4C;CACjG,MAAM,SAAS,MAAM,MAAM,KAAK,UAAU;CAC1C,IAAI,CAAC,QACH,OAAO;CAET,OAAO,cAAc;EAAE;EAAO,OAAO;EAAQ,CAAC;;AAqBhD,SAAS,aAAqB;CAC5B,OAAO,OAAO,KAAK,KAAK,CAAC,SAAS,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE"}
package/dist/session.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as fromOpenAI, B as createRemoteStore, F as SessionRun, I as SessionStore, J as autoDetectAndConvert, Jt as SessionContentBlock, L as createSession, M as CreateSessionOptions, N as Session, P as SessionData, Q as fromAnthropic, Qt as SessionTurn, R as loadSession, Zt as SessionMessage, at as createFileMapStore, et as toAnthropic, it as FileMapStoreOptions, nt as createMemoryStore, rt as FileMapAdapter, tt as toOpenAI, z as RemoteStoreOptions } from "./agent-CMAklak7.js";
1
+ import { $ as fromOpenAI, B as createRemoteStore, F as SessionRun, I as SessionStore, J as autoDetectAndConvert, Jt as SessionContentBlock, L as createSession, M as CreateSessionOptions, N as Session, P as SessionData, Q as fromAnthropic, Qt as SessionTurn, R as loadSession, Zt as SessionMessage, at as createFileMapStore, et as toAnthropic, it as FileMapStoreOptions, nt as createMemoryStore, rt as FileMapAdapter, tt as toOpenAI, z as RemoteStoreOptions } from "./agent-B26FuGew.js";
2
2
  export { CreateSessionOptions, FileMapAdapter, FileMapStoreOptions, RemoteStoreOptions, Session, SessionContentBlock, SessionData, SessionMessage, SessionRun, SessionStore, SessionTurn, autoDetectAndConvert, createFileMapStore, createMemoryStore, createRemoteStore, createSession, fromAnthropic, fromOpenAI, loadSession, toAnthropic, toOpenAI };
package/dist/session.js CHANGED
@@ -1,3 +1,3 @@
1
- import { c as fromAnthropic, d as toOpenAI, i as autoDetectAndConvert, l as fromOpenAI, u as toAnthropic } from "./messages-BBWakTN6.js";
2
- import { a as createFileMapStore, i as createMemoryStore, n as loadSession, r as createRemoteStore, t as createSession } from "./session-DzfRacU_.js";
1
+ import { c as fromAnthropic, d as toOpenAI, i as autoDetectAndConvert, l as fromOpenAI, u as toAnthropic } from "./messages-B5k4DAXy.js";
2
+ import { a as createFileMapStore, i as createMemoryStore, n as loadSession, r as createRemoteStore, t as createSession } from "./session-BoEW_wCR.js";
3
3
  export { autoDetectAndConvert, createFileMapStore, createMemoryStore, createRemoteStore, createSession, fromAnthropic, fromOpenAI, loadSession, toAnthropic, toOpenAI };
package/dist/skills.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { A as SkillSource, D as SkillConfig, O as SkillDiagnostic, c as DeactivationReason, d as createSkillActivationState, j as SkillsConfig, k as SkillResource, l as SkillActivationState, o as ActivationVia, s as ActiveSkill, u as SkillActivationStateOptions } from "./agent-CMAklak7.js";
2
- import { S as installAllowedToolsGate, _ as inferSource, a as SkillValidationResult, b as buildCatalog, c as parseAllowedToolPattern, d as validateSkillName, f as resolveSkills, g as getDefaultScanPaths, h as discoverSkills, i as SkillValidationIssue, l as validateResourcePath, m as SourcedScanPath, n as writeSkillToDisk, o as isToolAllowedByUnion, p as interpolateShellCommands, r as writeSkillsToDisk, s as matchesAllowedTool, t as defineSkill, u as validateSkillForWrite, v as parseFrontmatter, x as IMPLICITLY_ALLOWED_SKILL_TOOLS, y as parseSkillFile } from "./index-CF5QwBiz.js";
1
+ import { A as SkillSource, D as SkillConfig, O as SkillDiagnostic, c as DeactivationReason, d as createSkillActivationState, j as SkillsConfig, k as SkillResource, l as SkillActivationState, o as ActivationVia, s as ActiveSkill, u as SkillActivationStateOptions } from "./agent-B26FuGew.js";
2
+ import { S as installAllowedToolsGate, _ as inferSource, a as SkillValidationResult, b as buildCatalog, c as parseAllowedToolPattern, d as validateSkillName, f as resolveSkills, g as getDefaultScanPaths, h as discoverSkills, i as SkillValidationIssue, l as validateResourcePath, m as SourcedScanPath, n as writeSkillToDisk, o as isToolAllowedByUnion, p as interpolateShellCommands, r as writeSkillsToDisk, s as matchesAllowedTool, t as defineSkill, u as validateSkillForWrite, v as parseFrontmatter, x as IMPLICITLY_ALLOWED_SKILL_TOOLS, y as parseSkillFile } from "./index-CE7z_11T.js";
3
3
  export { ActivationVia, ActiveSkill, DeactivationReason, IMPLICITLY_ALLOWED_SKILL_TOOLS, SkillActivationState, SkillActivationStateOptions, SkillConfig, SkillDiagnostic, SkillResource, SkillSource, SkillValidationIssue, SkillValidationResult, SkillsConfig, SourcedScanPath, buildCatalog, createSkillActivationState, defineSkill, discoverSkills, getDefaultScanPaths, inferSource, installAllowedToolsGate, interpolateShellCommands, isToolAllowedByUnion, matchesAllowedTool, parseAllowedToolPattern, parseFrontmatter, parseSkillFile, resolveSkills, validateResourcePath, validateSkillForWrite, validateSkillName, writeSkillToDisk, writeSkillsToDisk };
package/dist/skills.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as validateResourcePath, a as discoverSkills, b as createSkillActivationState, c as parseFrontmatter, f as IMPLICITLY_ALLOWED_SKILL_TOOLS, g as parseAllowedToolPattern, h as matchesAllowedTool, i as writeSkillsToDisk, l as parseSkillFile, m as isToolAllowedByUnion, n as resolveSkills, o as getDefaultScanPaths, p as installAllowedToolsGate, r as writeSkillToDisk, s as inferSource, t as interpolateShellCommands, u as buildCatalog, v as validateSkillForWrite, y as validateSkillName } from "./interpolate-Cvjy8gpk.js";
1
+ import { _ as validateResourcePath, a as discoverSkills, b as createSkillActivationState, c as parseFrontmatter, f as IMPLICITLY_ALLOWED_SKILL_TOOLS, g as parseAllowedToolPattern, h as matchesAllowedTool, i as writeSkillsToDisk, l as parseSkillFile, m as isToolAllowedByUnion, n as resolveSkills, o as getDefaultScanPaths, p as installAllowedToolsGate, r as writeSkillToDisk, s as inferSource, t as interpolateShellCommands, u as buildCatalog, v as validateSkillForWrite, y as validateSkillName } from "./interpolate-j5V-wcAQ.js";
2
2
  //#region src/skills/index.ts
3
3
  /**
4
4
  * Define an inline skill configuration.
@@ -1,14 +1,14 @@
1
1
  import { n as createProcessContext } from "./contexts-BOtMvzli.js";
2
- import { a as AgentToolPairingError, l as toTypedError, r as AgentProviderError, s as errorMessage, t as AgentAbortedError } from "./errors-C5VSakmT.js";
2
+ import { c as errorMessage, i as AgentProviderError, n as AgentBudgetExceededError, o as AgentToolPairingError, t as AgentAbortedError, u as toTypedError } from "./errors-DdZXnyXE.js";
3
3
  import { n as toolOutputByteLength, t as DEFAULT_AGENT_CLOCK } from "./types-oKPBdCmL.js";
4
- import { a as detectTurnInterruption, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, o as ensureToolResultPairing, s as filterUnresolvedToolUses } from "./messages-BBWakTN6.js";
5
- import { t as connectMcpServers } from "./mcp-BE43Viwi.js";
6
- import { _ as validateResourcePath, b as createSkillActivationState, d as escapeXml, n as resolveSkills, p as installAllowedToolsGate, t as interpolateShellCommands, u as buildCatalog } from "./interpolate-Cvjy8gpk.js";
4
+ import { a as detectTurnInterruption, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, o as ensureToolResultPairing, s as filterUnresolvedToolUses } from "./messages-B5k4DAXy.js";
5
+ import { t as connectMcpServers } from "./mcp-ngMS0S6N.js";
6
+ import { _ as validateResourcePath, b as createSkillActivationState, d as escapeXml, n as resolveSkills, p as installAllowedToolsGate, t as interpolateShellCommands, u as buildCatalog } from "./interpolate-j5V-wcAQ.js";
7
7
  import { n as formatTokenUsage, t as flattenTurns } from "./stats-Lc3zL3RM.js";
8
8
  import { dirname, isAbsolute, join, resolve } from "node:path";
9
9
  import { createHooks } from "hookable";
10
10
  import { homedir } from "node:os";
11
- import { mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
11
+ import { mkdir, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
12
12
  import { Buffer } from "node:buffer";
13
13
  //#region src/aliasing.ts
14
14
  /**
@@ -478,16 +478,99 @@ async function maybePersistToolResult(input) {
478
478
  error: err instanceof Error ? err : new Error(String(err))
479
479
  };
480
480
  }
481
+ const stub = buildPersistedStub({
482
+ toolName: input.toolName,
483
+ originalBytes,
484
+ persistedPath,
485
+ output: input.output
486
+ });
487
+ let evicted;
488
+ if (typeof input.maxBytes === "number" && Number.isFinite(input.maxBytes) && input.maxBytes > 0) evicted = await enforcePersistDirCap(input.persistDir, input.maxBytes);
481
489
  return {
482
490
  kind: "persisted",
483
- output: buildPersistedStub({
484
- toolName: input.toolName,
485
- originalBytes,
486
- persistedPath,
487
- output: input.output
488
- }),
491
+ output: stub,
489
492
  originalBytes,
490
- persistedPath
493
+ persistedPath,
494
+ ...evicted && evicted.files > 0 ? { evicted } : {}
495
+ };
496
+ }
497
+ /**
498
+ * Enforce a byte-cap on a persist directory by removing oldest `*.txt`
499
+ * blobs (by mtime) until the remaining total is at or below `maxBytes`.
500
+ *
501
+ * Exported for tests + for hosts that want to run a periodic sweep
502
+ * decoupled from the per-call hook. {@link maybePersistToolResult} calls
503
+ * this automatically when `maxBytes` is set on its input, so production
504
+ * callers usually don't invoke it directly.
505
+ *
506
+ * Failure modes:
507
+ *
508
+ * - `persistDir` missing → no-op (nothing to evict).
509
+ * - `readdir` / `stat` errors → swallowed (best-effort); returns
510
+ * `{ files: 0, bytes: 0 }`.
511
+ * - Individual `unlink` failures → counted in the result only if they
512
+ * succeeded; the loop continues to the next candidate so a single
513
+ * permission blip doesn't abort the whole sweep.
514
+ *
515
+ * The mtime-asc sort is stable enough for our purposes: two blobs
516
+ * written in the same millisecond are evicted in `readdir` order, which
517
+ * is filesystem-dependent but consistent within a run. We never need
518
+ * strict total ordering — the LRU is a soft cap, not a transactional
519
+ * guarantee.
520
+ */
521
+ async function enforcePersistDirCap(persistDir, maxBytes) {
522
+ if (!isAbsolute(persistDir) || !Number.isFinite(maxBytes) || maxBytes <= 0) return {
523
+ files: 0,
524
+ bytes: 0
525
+ };
526
+ let entries;
527
+ try {
528
+ entries = await readdir(persistDir);
529
+ } catch {
530
+ return {
531
+ files: 0,
532
+ bytes: 0
533
+ };
534
+ }
535
+ const txtFiles = entries.filter((name) => name.endsWith(".txt"));
536
+ const metas = [];
537
+ for (const name of txtFiles) {
538
+ const path = join(persistDir, name);
539
+ try {
540
+ const s = await stat(path);
541
+ if (!s.isFile()) continue;
542
+ metas.push({
543
+ path,
544
+ size: s.size,
545
+ mtime: s.mtimeMs
546
+ });
547
+ } catch {}
548
+ }
549
+ const totalBytes = metas.reduce((sum, m) => sum + m.size, 0);
550
+ if (totalBytes <= maxBytes) return {
551
+ files: 0,
552
+ bytes: 0
553
+ };
554
+ metas.sort((a, b) => a.mtime - b.mtime);
555
+ let running = totalBytes;
556
+ let evictedFiles = 0;
557
+ let evictedBytes = 0;
558
+ for (let i = 0; i < metas.length; i++) {
559
+ if (running <= maxBytes) break;
560
+ if (i === metas.length - 1) break;
561
+ const meta = metas[i];
562
+ try {
563
+ await unlink(meta.path);
564
+ running -= meta.size;
565
+ evictedFiles += 1;
566
+ evictedBytes += meta.size;
567
+ } catch (err) {
568
+ if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/persistence] evict "${meta.path}" failed: ${errorMessage(err)}\n`);
569
+ }
570
+ }
571
+ return {
572
+ files: evictedFiles,
573
+ bytes: evictedBytes
491
574
  };
492
575
  }
493
576
  /**
@@ -1367,6 +1450,13 @@ async function runLoop(ctx) {
1367
1450
  await ctx.hooks.callHook("agent:abort", {});
1368
1451
  break;
1369
1452
  }
1453
+ const breach = checkRunBudget({
1454
+ maxCostUsd: ctx.maxCostUsd,
1455
+ maxTotalTokens: ctx.maxTotalTokens,
1456
+ cost: turnUsages.reduce((s, t) => s + (t.cost ?? 0), 0),
1457
+ tokens: totalIn + totalOut
1458
+ });
1459
+ if (breach) throw new AgentBudgetExceededError(breach);
1370
1460
  if (ctx.steeringQueue.length > 0) {
1371
1461
  const steerMsg = ctx.steeringQueue.shift();
1372
1462
  await ctx.hooks.callHook("steer:inject", { message: steerMsg });
@@ -1430,6 +1520,35 @@ async function runLoop(ctx) {
1430
1520
  * to wrapping in `AgentProviderError`. Abort signals always produce `AgentAbortedError`
1431
1521
  * regardless of the provider classification.
1432
1522
  */
1523
+ /**
1524
+ * Pure predicate for the run-level budget circuit breaker. Returns a
1525
+ * structured breach descriptor when EITHER the cost or token ledger has
1526
+ * crossed its configured ceiling, otherwise `null`.
1527
+ *
1528
+ * Order of checks: cost first when both are set, because cost is the
1529
+ * direct dollar accounting operators set ceilings against — emitting a
1530
+ * `'tokens'` breach when both knobs would have tripped on the same turn
1531
+ * would hide the more user-actionable signal.
1532
+ *
1533
+ * `undefined` / `0` / non-positive / non-finite ceilings disable the
1534
+ * corresponding axis without protest, matching the established
1535
+ * `behavior.toolOutputBudget` semantic. Exported only for tests; the
1536
+ * loop is the sole production caller.
1537
+ */
1538
+ function checkRunBudget(input) {
1539
+ const { maxCostUsd, maxTotalTokens, cost, tokens } = input;
1540
+ if (typeof maxCostUsd === "number" && Number.isFinite(maxCostUsd) && maxCostUsd > 0 && cost >= maxCostUsd) return {
1541
+ limit: "cost",
1542
+ limitValue: maxCostUsd,
1543
+ actualValue: cost
1544
+ };
1545
+ if (typeof maxTotalTokens === "number" && Number.isFinite(maxTotalTokens) && maxTotalTokens > 0 && tokens >= maxTotalTokens) return {
1546
+ limit: "tokens",
1547
+ limitValue: maxTotalTokens,
1548
+ actualValue: tokens
1549
+ };
1550
+ return null;
1551
+ }
1433
1552
  function wrapProviderError(err, ctx) {
1434
1553
  if (ctx.signal.aborted || err instanceof Error && err.name === "AbortError") return new AgentAbortedError("Agent run aborted", { cause: err });
1435
1554
  const classification = ctx.provider.classifyError?.(err);
@@ -2130,7 +2249,8 @@ async function emitToolResult(ctx, params) {
2130
2249
  output,
2131
2250
  threshold,
2132
2251
  persistDir,
2133
- ...ctx.persistExcludeTools ? { excludeTools: ctx.persistExcludeTools } : {}
2252
+ ...ctx.persistExcludeTools ? { excludeTools: ctx.persistExcludeTools } : {},
2253
+ ...typeof ctx.persistMaxBytes === "number" ? { maxBytes: ctx.persistMaxBytes } : {}
2134
2254
  });
2135
2255
  if (outcome.kind === "persisted") output = outcome.output;
2136
2256
  else if (outcome.kind === "error" && process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/loop] persistence write failed for ${name}/${callId}: ${outcome.error.message}\n`);
@@ -3488,6 +3608,8 @@ function resolveBehavior(agentBehavior, runBehavior) {
3488
3608
  return {
3489
3609
  maxConcurrentTools: runBehavior?.maxConcurrentTools ?? agentBehavior?.maxConcurrentTools,
3490
3610
  maxTurns: runBehavior?.maxTurns ?? agentBehavior?.maxTurns,
3611
+ maxCostUsd: runBehavior?.maxCostUsd ?? agentBehavior?.maxCostUsd,
3612
+ maxTotalTokens: runBehavior?.maxTotalTokens ?? agentBehavior?.maxTotalTokens,
3491
3613
  maxTokens: runBehavior?.maxTokens ?? agentBehavior?.maxTokens,
3492
3614
  thinkingBudget: runBehavior?.thinkingBudget ?? agentBehavior?.thinkingBudget,
3493
3615
  schema: runBehavior?.schema ?? agentBehavior?.schema,
@@ -3509,6 +3631,7 @@ function resolveBehavior(agentBehavior, runBehavior) {
3509
3631
  persistThreshold: runBehavior?.persistThreshold ?? agentBehavior?.persistThreshold,
3510
3632
  persistExcludeTools: runBehavior?.persistExcludeTools ?? agentBehavior?.persistExcludeTools,
3511
3633
  persistDir: runBehavior?.persistDir ?? agentBehavior?.persistDir,
3634
+ persistMaxBytes: runBehavior?.persistMaxBytes ?? agentBehavior?.persistMaxBytes,
3512
3635
  tasksDir: runBehavior?.tasksDir ?? agentBehavior?.tasksDir,
3513
3636
  disableBackgroundTasks: runBehavior?.disableBackgroundTasks ?? agentBehavior?.disableBackgroundTasks,
3514
3637
  strictToolPairing: runBehavior?.strictToolPairing ?? agentBehavior?.strictToolPairing ?? false
@@ -3820,7 +3943,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
3820
3943
  const thinking = options.thinking ?? "off";
3821
3944
  const model = options.model ?? provider.meta.defaultModel;
3822
3945
  const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
3823
- const { maxConcurrentTools, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, toolOutputBudgetExcludeTools, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch, persistThreshold, persistExcludeTools, persistDir, strictToolPairing } = resolvedBehavior;
3946
+ const { maxConcurrentTools, maxTurns, maxCostUsd, maxTotalTokens, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, toolOutputBudgetExcludeTools, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch, persistThreshold, persistExcludeTools, persistDir, persistMaxBytes, strictToolPairing } = resolvedBehavior;
3824
3947
  let system = options.system || agentSystem || "You are a helpful assistant.";
3825
3948
  if (skillsCatalog) system = `${system}\n\n${skillsCatalog}`;
3826
3949
  const runBaseTools = options.tools !== void 0 ? options.tools : mcpConnection ? {
@@ -4015,6 +4138,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
4015
4138
  generateTurnId: () => session?.generateTurnId() ?? clock.randomUUID(),
4016
4139
  clock,
4017
4140
  maxTurns,
4141
+ ...maxCostUsd !== void 0 ? { maxCostUsd } : {},
4142
+ ...maxTotalTokens !== void 0 ? { maxTotalTokens } : {},
4018
4143
  maxTokens,
4019
4144
  ...session ? { session } : {},
4020
4145
  ...agentReadState ? { readState: agentReadState } : {},
@@ -4033,6 +4158,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
4033
4158
  ...persistThreshold !== void 0 ? { persistThreshold } : {},
4034
4159
  ...persistExcludeTools !== void 0 ? { persistExcludeTools } : {},
4035
4160
  ...persistDir !== void 0 ? { persistDir } : {},
4161
+ ...persistMaxBytes !== void 0 ? { persistMaxBytes } : {},
4036
4162
  ...strictToolPairing ? { strictToolPairing: true } : {},
4037
4163
  providerName: provider.name,
4038
4164
  runStartMs,
@@ -4101,6 +4227,19 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
4101
4227
  await hooks.callHook("agent:done", stats);
4102
4228
  return stats;
4103
4229
  }
4230
+ if (err instanceof AgentBudgetExceededError) {
4231
+ session?.abortRun(runId);
4232
+ await finalizeSession("aborted");
4233
+ await hooks.callHook("agent:done", {
4234
+ totalIn: 0,
4235
+ totalOut: 0,
4236
+ totalCacheRead: 0,
4237
+ totalCacheCreation: 0,
4238
+ turns: 0,
4239
+ elapsed: 0
4240
+ });
4241
+ throw err;
4242
+ }
4104
4243
  session?.errorRun(runId, errorMessage(err));
4105
4244
  await finalizeSession("error");
4106
4245
  throw err;
@@ -5915,4 +6054,4 @@ const writeFile$1 = {
5915
6054
  //#endregion
5916
6055
  export { resolvePersistDir as A, formatTaskStatus as B, TOOL_USE_SKIPPED_MESSAGE as C, buildPersistedStub as D, PERSISTENCE_PREVIEW_BYTES as E, resolveReadStateMap as F, previewLine as H, ageString as I, compactPath as L, getReadState as M, hashContent as N, cleanupPersistedSession as O, readStateKey as P, fmtTokens as R, TOOL_USE_CANCELLED_MESSAGE as S, PERSISTED_STUB_PREFIX as T, shortId as U, formatTaskSummary as V, createSkillsReadTool as _, multiEdit as a, INTERRUPT_MESSAGE_FOR_TOOL_USE as b, grep as c, resolveOldString as d, styleReplacementForVia as f, createSkillsRunScriptTool as g, createSkillsUseTool as h, readFile$1 as i, resolveTasksDir as j, maybePersistToolResult as k, glob as l, createToolSearchTool as m, createSpawnTool as n, listFiles as o, createAgent as p, shellKill as r, createInteractionTool as s, writeFile$1 as t, edit as u, createShellTool as v, validateToolArgs as w, SHELL_CASCADE_CANCEL_MESSAGE as x, shell as y, formatDuration as z };
5917
6056
 
5918
- //# sourceMappingURL=tools-Bbd0Ivwn.js.map
6057
+ //# sourceMappingURL=tools-Co3VYhgM.js.map