{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/core/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@apholdings/jensen-agent-core\";\nimport type { ImageContent, TextContent } from \"@apholdings/jensen-ai\";\n\nexport interface MemoryItem {\n\tkey: string;\n\tvalue: string;\n\ttimestamp: string;\n}\n\nexport interface TodoSnapshotItem {\n\tid: string;\n\tcontent: string;\n\tactiveForm: string;\n\tstatus: \"pending\" | \"in_progress\" | \"completed\";\n}\n\nexport interface SessionMemoryState {\n\titems: MemoryItem[];\n}\n\nexport const SESSION_MEMORY_CUSTOM_TYPE = \"session_memory\";\nexport const SESSION_TODOS_CUSTOM_TYPE = \"session_todos\";\nexport const SESSION_TODO_ENGINE_CUSTOM_TYPE = \"session_todo_engine\";\nexport const SESSION_TASKS_CUSTOM_TYPE = \"session_tasks\";\nexport const SESSION_RELIABILITY_CUSTOM_TYPE = \"session_reliability\";\n\nexport const SESSION_MEMORY_PREFIX = `Session memory recorded during this conversation. Treat it as active working context, but prefer newer user instructions if they conflict.\\n\\n<memory>\\n`;\nexport const SESSION_MEMORY_SUFFIX = `\\n</memory>`;\n\nexport function normalizeMemoryKey(key: string): string {\n\treturn key.trim().replace(/\\s+/g, \".\").toLowerCase();\n}\n\nexport function normalizeMemoryValue(value: string): string {\n\treturn value.trim();\n}\n\nexport function upsertMemoryItems(items: MemoryItem[], key: string, value: string, timestamp: string): MemoryItem[] {\n\tconst normalizedKey = normalizeMemoryKey(key);\n\tconst normalizedValue = normalizeMemoryValue(value);\n\tif (!normalizedKey || !normalizedValue) {\n\t\treturn items;\n\t}\n\n\tconst next = items.filter((item) => item.key !== normalizedKey);\n\tnext.push({ key: normalizedKey, value: normalizedValue, timestamp });\n\tnext.sort((a, b) => a.key.localeCompare(b.key));\n\treturn next;\n}\n\nexport function clearMemoryItems(): MemoryItem[] {\n\treturn [];\n}\n\nexport function deleteMemoryItem(items: MemoryItem[], key: string): MemoryItem[] {\n\tconst normalizedKey = normalizeMemoryKey(key);\n\treturn items.filter((item) => item.key !== normalizedKey);\n}\n\nexport function getMemoryItem(items: readonly MemoryItem[], key: string): MemoryItem | undefined {\n\tconst normalizedKey = normalizeMemoryKey(key);\n\treturn items.find((item) => item.key === normalizedKey);\n}\n\nexport function memoryItemsToText(items: MemoryItem[]): string {\n\tif (items.length === 0) {\n\t\treturn \"(none)\";\n\t}\n\n\treturn items.map((item) => `- ${item.key}: ${item.value}`).join(\"\\n\");\n}\n\nexport function createMemoryContextMessage(items: MemoryItem[]): AgentMessage | undefined {\n\tif (items.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType: SESSION_MEMORY_CUSTOM_TYPE,\n\t\tcontent: `${SESSION_MEMORY_PREFIX}${memoryItemsToText(items)}${SESSION_MEMORY_SUFFIX}`,\n\t\tdisplay: false,\n\t\tdetails: { items },\n\t\ttimestamp: Date.now(),\n\t};\n}\n\nexport function serializeMemoryItems(items: MemoryItem[]): string | (TextContent | ImageContent)[] {\n\treturn memoryItemsToText(items);\n}\n\nexport function parseMemoryItems(data: unknown): MemoryItem[] {\n\tif (!Array.isArray(data)) return [];\n\tconst items: MemoryItem[] = [];\n\tfor (const item of data) {\n\t\tif (typeof item !== \"object\" || item === null) continue;\n\t\tconst candidate = item as Record<string, unknown>;\n\t\tif (typeof candidate.key !== \"string\" || typeof candidate.value !== \"string\") continue;\n\t\titems.push({\n\t\t\tkey: normalizeMemoryKey(candidate.key),\n\t\t\tvalue: normalizeMemoryValue(candidate.value),\n\t\t\ttimestamp: typeof candidate.timestamp === \"string\" ? candidate.timestamp : new Date().toISOString(),\n\t\t});\n\t}\n\treturn items.filter((item) => item.key.length > 0 && item.value.length > 0);\n}\n\nexport function parseTodoSnapshot(data: unknown): TodoSnapshotItem[] {\n\tif (!Array.isArray(data)) return [];\n\tconst todos: TodoSnapshotItem[] = [];\n\tfor (const item of data) {\n\t\tif (typeof item !== \"object\" || item === null) continue;\n\t\tconst candidate = item as Record<string, unknown>;\n\t\tif (\n\t\t\ttypeof candidate.content !== \"string\" ||\n\t\t\ttypeof candidate.activeForm !== \"string\" ||\n\t\t\t(candidate.status !== \"pending\" && candidate.status !== \"in_progress\" && candidate.status !== \"completed\")\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst id = typeof candidate.id === \"string\" && candidate.id.length > 0 ? candidate.id : generateTodoId();\n\t\ttodos.push({\n\t\t\tid,\n\t\t\tcontent: candidate.content,\n\t\t\tactiveForm: candidate.activeForm,\n\t\t\tstatus: candidate.status,\n\t\t});\n\t}\n\treturn todos;\n}\n\nlet _todoIdCounter = 0;\n\n/** Generate a stable, unique todo ID. Not a secret, not content-derived. */\nexport function generateTodoId(): string {\n\tconst timestamp = Date.now().toString(36);\n\tconst counter = (++_todoIdCounter).toString(36);\n\tconst random = Math.random().toString(36).slice(2, 8);\n\treturn `td_${timestamp}_${counter}_${random}`;\n}\n\n/**\n * Snapshot of session memory at a point in time.\n * History is derived from persisted session entries with customType === \"session_memory\".\n * Because persistence is snapshot-based, not event-sourced, each snapshot represents\n * the complete memory state at that point, not individual add/update/delete events.\n */\nexport interface MemoryHistorySnapshot {\n\t/** Session entry id for this snapshot */\n\tentryId: string;\n\t/** Parent entry id in the session tree (null for root) */\n\tparentId: string | null;\n\t/** When this snapshot was recorded (ISO timestamp) */\n\trecordedAt: string;\n\t/** Complete memory state at this point in time */\n\titems: MemoryItem[];\n\t/** True if this is the latest snapshot on the current branch */\n\tisCurrent: boolean;\n}\n\n// ============================================================================\n// Task Model\n// ============================================================================\n\n/** Valid task statuses */\nexport type TaskStatus = \"pending\" | \"in_progress\" | \"completed\";\n\n/**\n * Structured task for multi-step work tracking.\n * Separate from todo_write items; tasks have explicit id, subject, and description\n * for model-visible work tracking across an extended session.\n */\nexport interface Task {\n\t/** Unique identifier for this task */\n\tid: string;\n\t/** Brief title/subject of the task */\n\tsubject: string;\n\t/** Detailed description of what the task involves */\n\tdescription: string;\n\t/** Current status */\n\tstatus: TaskStatus;\n\t/** Active-form phrasing shown when task is in_progress (e.g., \"Implementing feature X\") */\n\tactiveForm?: string;\n\t/** Optional free-form metadata */\n\tmetadata?: Record<string, unknown>;\n}\n\n/** Input for creating a new task (excludes auto-assigned fields) */\nexport interface TaskCreateInput {\n\tsubject: string;\n\tdescription: string;\n\tactiveForm?: string;\n\tmetadata?: Record<string, unknown>;\n}\n\n/** Input for updating an existing task */\nexport interface TaskUpdateInput {\n\tsubject?: string;\n\tdescription?: string;\n\tstatus?: TaskStatus;\n\tactiveForm?: string;\n\tmetadata?: Record<string, unknown>;\n}\n\nlet _taskIdCounter = 0;\n\n/**\n * Generate a unique task ID.\n * Uses a counter to ensure uniqueness within the session.\n */\nexport function generateTaskId(): string {\n\t_taskIdCounter++;\n\treturn `task_${Date.now()}_${_taskIdCounter}`;\n}\n\n/**\n * Parse a raw session entry data blob into an array of Task objects.\n * Skips malformed entries.\n */\nexport function parseTasks(data: unknown): Task[] {\n\tif (!Array.isArray(data)) return [];\n\tconst tasks: Task[] = [];\n\tfor (const item of data) {\n\t\tif (typeof item !== \"object\" || item === null) continue;\n\t\tconst candidate = item as Record<string, unknown>;\n\t\tif (\n\t\t\ttypeof candidate.id !== \"string\" ||\n\t\t\ttypeof candidate.subject !== \"string\" ||\n\t\t\ttypeof candidate.description !== \"string\" ||\n\t\t\t(candidate.status !== \"pending\" && candidate.status !== \"in_progress\" && candidate.status !== \"completed\")\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\ttasks.push({\n\t\t\tid: candidate.id,\n\t\t\tsubject: candidate.subject,\n\t\t\tdescription: candidate.description,\n\t\t\tstatus: candidate.status as TaskStatus,\n\t\t\tactiveForm: typeof candidate.activeForm === \"string\" ? candidate.activeForm : undefined,\n\t\t\tmetadata:\n\t\t\t\ttypeof candidate.metadata === \"object\" && candidate.metadata !== null\n\t\t\t\t\t? (candidate.metadata as Record<string, unknown>)\n\t\t\t\t\t: undefined,\n\t\t});\n\t}\n\treturn tasks;\n}\n\n/**\n * Format a task list for display.\n */\nexport function formatTaskList(tasks: Task[]): string {\n\tif (tasks.length === 0) {\n\t\treturn \"No tasks.\";\n\t}\n\n\tconst lines: string[] = [];\n\tconst pending = tasks.filter((t) => t.status === \"pending\");\n\tconst inProgress = tasks.filter((t) => t.status === \"in_progress\");\n\tconst completed = tasks.filter((t) => t.status === \"completed\");\n\n\tif (pending.length > 0) {\n\t\tlines.push(`Pending (${pending.length}):`);\n\t\tfor (const task of pending) {\n\t\t\tlines.push(`  [${task.id}] ${task.subject}`);\n\t\t}\n\t}\n\n\tif (inProgress.length > 0) {\n\t\tlines.push(`In Progress (${inProgress.length}):`);\n\t\tfor (const task of inProgress) {\n\t\t\tlines.push(`  [${task.id}] ${task.subject}${task.activeForm ? ` — ${task.activeForm}` : \"\"}`);\n\t\t}\n\t}\n\n\tif (completed.length > 0) {\n\t\tlines.push(`Completed (${completed.length}):`);\n\t\tfor (const task of completed) {\n\t\t\tlines.push(`  [${task.id}] ${task.subject}`);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Format a single task for display.\n */\nexport function formatTaskDetail(task: Task): string {\n\tconst lines = [`Task: ${task.subject}`, `ID: ${task.id}`, `Status: ${task.status}`];\n\tif (task.activeForm) {\n\t\tlines.push(`Active: ${task.activeForm}`);\n\t}\n\tif (task.description) {\n\t\tlines.push(`\\n${task.description}`);\n\t}\n\tif (task.metadata && Object.keys(task.metadata).length > 0) {\n\t\tlines.push(`\\nMetadata: ${JSON.stringify(task.metadata)}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}