@xopcai/xopc 0.0.30 → 0.0.31

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 (105) hide show
  1. package/dist/extensions/telegram/xopc.extension.json +1 -1
  2. package/dist/gateway/static/root/assets/agents-3u63Fw2Y.js +216 -0
  3. package/dist/gateway/static/root/assets/agents-3u63Fw2Y.js.map +1 -0
  4. package/dist/gateway/static/root/assets/{apps-page-CTChHQAu.js → apps-page-CWegY6Kp.js} +2 -2
  5. package/dist/gateway/static/root/assets/{apps-page-CTChHQAu.js.map → apps-page-CWegY6Kp.js.map} +1 -1
  6. package/dist/gateway/static/root/assets/channels-settings-CiyeXcTK.js +9 -0
  7. package/dist/gateway/static/root/assets/channels-settings-CiyeXcTK.js.map +1 -0
  8. package/dist/gateway/static/root/assets/cron-api-_j_79Zf5.js +3 -0
  9. package/dist/gateway/static/root/assets/cron-api-_j_79Zf5.js.map +1 -0
  10. package/dist/gateway/static/root/assets/cron-page-S86YNTtI.js +2 -0
  11. package/dist/gateway/static/root/assets/cron-page-S86YNTtI.js.map +1 -0
  12. package/dist/gateway/static/root/assets/dist-D0jxbvuz.js +2 -0
  13. package/dist/gateway/static/root/assets/{dist-UWGUW3x8.js.map → dist-D0jxbvuz.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{extension-debug-page-BwB4a4cK.js → extension-debug-page-DB630cW8.js} +2 -2
  15. package/dist/gateway/static/root/assets/{extension-debug-page-BwB4a4cK.js.map → extension-debug-page-DB630cW8.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-page-CSWu2PHZ.js → extension-page-CnoPUBul.js} +2 -2
  17. package/dist/gateway/static/root/assets/{extension-page-CSWu2PHZ.js.map → extension-page-CnoPUBul.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-settings-page-B12K2a13.js → extension-settings-page-BsiOkvBe.js} +2 -2
  19. package/dist/gateway/static/root/assets/{extension-settings-page-B12K2a13.js.map → extension-settings-page-BsiOkvBe.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/{index-D0pFZ0OE.js → index-DHLmAIQl.js} +81 -81
  21. package/dist/gateway/static/root/assets/{index-D0pFZ0OE.js.map → index-DHLmAIQl.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/index-DoPwy4aU.css +1 -0
  23. package/dist/gateway/static/root/assets/logs-page-Bndhenn2.js +2 -0
  24. package/dist/gateway/static/root/assets/logs-page-Bndhenn2.js.map +1 -0
  25. package/dist/gateway/static/root/assets/sessions-page-Q201-_lP.js +2 -0
  26. package/dist/gateway/static/root/assets/{sessions-page-DJkuWpOT.js.map → sessions-page-Q201-_lP.js.map} +1 -1
  27. package/dist/gateway/static/root/assets/settings-page-Cw75fpc6.js +2 -0
  28. package/dist/gateway/static/root/assets/settings-page-Cw75fpc6.js.map +1 -0
  29. package/dist/gateway/static/root/assets/skills-page-CVwEzD_J.js +3 -0
  30. package/dist/gateway/static/root/assets/skills-page-CVwEzD_J.js.map +1 -0
  31. package/dist/gateway/static/root/index.html +2 -2
  32. package/dist/package.js +1 -1
  33. package/dist/src/agent/orchestration/agent-orchestrator.js +1 -1
  34. package/dist/src/agent/service/process-direct-streaming.js +12 -1
  35. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  36. package/dist/src/agent/service.d.ts +4 -0
  37. package/dist/src/agent/service.js +7 -1
  38. package/dist/src/agent/service.js.map +1 -1
  39. package/dist/src/agent/skills/marketplace/resolve-adapter.js +1 -1
  40. package/dist/src/agent/skills/marketplace/resolve-adapter.js.map +1 -1
  41. package/dist/src/config/schema.js +1 -0
  42. package/dist/src/config/schema.js.map +1 -1
  43. package/dist/src/cron/validation.js +1 -1
  44. package/dist/src/cron/validation.js.map +1 -1
  45. package/dist/src/gateway/hono/routes/sessions.js +124 -2
  46. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  47. package/dist/src/gateway/hono/sse.js +9 -2
  48. package/dist/src/gateway/hono/sse.js.map +1 -1
  49. package/dist/src/gateway/service/run-gateway-agent.d.ts +1 -0
  50. package/dist/src/gateway/service/run-gateway-agent.js +18 -10
  51. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  52. package/dist/src/gateway/service.d.ts +23 -1
  53. package/dist/src/gateway/service.js +47 -3
  54. package/dist/src/gateway/service.js.map +1 -1
  55. package/dist/src/session/abort-cutoff.d.ts +6 -0
  56. package/dist/src/session/abort-cutoff.js +10 -0
  57. package/dist/src/session/abort-cutoff.js.map +1 -0
  58. package/dist/src/session/compaction-checkpoints.d.ts +8 -0
  59. package/dist/src/session/compaction-checkpoints.js +21 -0
  60. package/dist/src/session/compaction-checkpoints.js.map +1 -0
  61. package/dist/src/session/index.d.ts +8 -1
  62. package/dist/src/session/index.js +7 -1
  63. package/dist/src/session/manager.d.ts +26 -1
  64. package/dist/src/session/manager.js +39 -2
  65. package/dist/src/session/manager.js.map +1 -1
  66. package/dist/src/session/patch-metadata.d.ts +12 -0
  67. package/dist/src/session/patch-metadata.js +23 -0
  68. package/dist/src/session/patch-metadata.js.map +1 -0
  69. package/dist/src/session/search-index.d.ts +2 -0
  70. package/dist/src/session/search-index.js +30 -2
  71. package/dist/src/session/search-index.js.map +1 -1
  72. package/dist/src/session/session-context-for-llm.d.ts +32 -0
  73. package/dist/src/session/session-context-for-llm.js +60 -0
  74. package/dist/src/session/session-context-for-llm.js.map +1 -0
  75. package/dist/src/session/store.d.ts +36 -2
  76. package/dist/src/session/store.js +200 -28
  77. package/dist/src/session/store.js.map +1 -1
  78. package/dist/src/session/strip-webchat-early-save.d.ts +5 -0
  79. package/dist/src/session/strip-webchat-early-save.js +17 -0
  80. package/dist/src/session/strip-webchat-early-save.js.map +1 -0
  81. package/dist/src/session/transcript-format.d.ts +46 -0
  82. package/dist/src/session/transcript-format.js +88 -0
  83. package/dist/src/session/transcript-format.js.map +1 -0
  84. package/dist/src/session/types.d.ts +37 -0
  85. package/dist/src/session/types.js.map +1 -1
  86. package/dist/src/utils/logger/log-store.js +4 -3
  87. package/dist/src/utils/logger/log-store.js.map +1 -1
  88. package/package.json +1 -1
  89. package/dist/gateway/static/root/assets/agents-BfwtJOPK.js +0 -216
  90. package/dist/gateway/static/root/assets/agents-BfwtJOPK.js.map +0 -1
  91. package/dist/gateway/static/root/assets/channels-settings-BpwVOvvf.js +0 -9
  92. package/dist/gateway/static/root/assets/channels-settings-BpwVOvvf.js.map +0 -1
  93. package/dist/gateway/static/root/assets/cron-page-C_6AbVRf.js +0 -2
  94. package/dist/gateway/static/root/assets/cron-page-C_6AbVRf.js.map +0 -1
  95. package/dist/gateway/static/root/assets/cron-utils-DZ7pabh5.js +0 -3
  96. package/dist/gateway/static/root/assets/cron-utils-DZ7pabh5.js.map +0 -1
  97. package/dist/gateway/static/root/assets/dist-UWGUW3x8.js +0 -2
  98. package/dist/gateway/static/root/assets/index-C6itMrqR.css +0 -1
  99. package/dist/gateway/static/root/assets/logs-page-BXqha2gI.js +0 -2
  100. package/dist/gateway/static/root/assets/logs-page-BXqha2gI.js.map +0 -1
  101. package/dist/gateway/static/root/assets/sessions-page-DJkuWpOT.js +0 -2
  102. package/dist/gateway/static/root/assets/settings-page-CleZrGHy.js +0 -2
  103. package/dist/gateway/static/root/assets/settings-page-CleZrGHy.js.map +0 -1
  104. package/dist/gateway/static/root/assets/skills-page-D7NiIOzA.js +0 -3
  105. package/dist/gateway/static/root/assets/skills-page-D7NiIOzA.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","names":["EventEmitter"],"sources":["../../../src/session/manager.ts"],"sourcesContent":["// Session manager - high-level session management service\n\nimport EventEmitter from 'events';\nimport { createLogger } from '../utils/logger.js';\nimport { SessionStore } from './store.js';\nimport type {\n SessionMetadata,\n SessionDetail,\n SessionListQuery,\n PaginatedResult,\n GlobalSessionStats,\n ExportFormat,\n SessionStatus,\n} from './types.js';\nimport type { Message } from './types.js';\nimport type { CompactionConfig, CompactionResult } from '../agent/memory/compaction.js';\nimport type { WindowConfig } from '../agent/memory/window.js';\nimport type { Config } from '../config/schema.js';\n\nconst log = createLogger('SessionManager');\n\nexport interface SessionManagerConfig {\n config: Config;\n agentId?: string;\n sessionsDir?: string;\n windowConfig?: Partial<WindowConfig>;\n compactionConfig?: Partial<CompactionConfig>;\n}\n\nexport class SessionManager extends EventEmitter {\n private store: SessionStore;\n\n constructor(config: SessionManagerConfig) {\n super();\n this.store = new SessionStore(\n {\n config: config.config,\n agentId: config.agentId,\n sessionsDir: config.sessionsDir,\n },\n config.windowConfig,\n config.compactionConfig\n );\n }\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n this.emit('ready');\n }\n\n /** Low-level store (e.g. cron resolving weixin delivery from session index). */\n getStore(): SessionStore {\n return this.store;\n }\n\n // ========== CRUD Operations ==========\n\n async listSessions(query?: SessionListQuery): Promise<PaginatedResult<SessionMetadata>> {\n return this.store.list(query);\n }\n\n /**\n * List all subagent sessions.\n * Subagent sessions have keys starting with 'subagent:'.\n */\n async listSubagents(query: SessionListQuery = {}): Promise<PaginatedResult<SessionMetadata>> {\n // Filter for subagent sessions only\n const subagentQuery: SessionListQuery = {\n ...query,\n search: query.search ? `subagent:${query.search}` : 'subagent:',\n };\n \n const result = await this.store.list(subagentQuery);\n \n // Additional filtering to ensure only subagent sessions\n const subagentSessions = result.items.filter((s) => s.key.startsWith('subagent:'));\n \n return {\n ...result,\n items: subagentSessions,\n total: subagentSessions.length,\n hasMore: false, // Simplified for now\n };\n }\n\n async getSession(key: string): Promise<SessionDetail | null> {\n const session = await this.store.get(key);\n if (session) {\n this.emit('sessionAccessed', { key });\n }\n return session;\n }\n\n async getSessionMetadata(key: string): Promise<SessionMetadata | null> {\n return this.store.getMetadata(key);\n }\n\n async deleteSession(key: string): Promise<boolean> {\n const result = await this.store.delete(key);\n if (result) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n async deleteSessions(keys: string[]): Promise<{ success: string[]; failed: string[] }> {\n const result = await this.store.deleteMany(keys);\n for (const key of result.success) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n // ========== Metadata Updates ==========\n\n async renameSession(key: string, name: string): Promise<void> {\n await this.store.updateMetadata(key, { name });\n this.emit('sessionUpdated', { key, name });\n }\n\n /** Partial metadata update (caller merges nested fields like `customData` when needed). */\n async updateSessionMetadata(key: string, updates: Partial<SessionMetadata>): Promise<void> {\n await this.store.updateMetadata(key, updates);\n this.emit('sessionUpdated', { key });\n }\n\n async tagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n // Merge tags, remove duplicates\n const mergedTags = [...new Set([...existing.tags, ...tags])];\n await this.store.updateMetadata(key, { tags: mergedTags });\n this.emit('sessionUpdated', { key, tags: mergedTags });\n }\n\n async untagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n const filteredTags = existing.tags.filter((t) => !tags.includes(t));\n await this.store.updateMetadata(key, { tags: filteredTags });\n this.emit('sessionUpdated', { key, tags: filteredTags });\n }\n\n async setSessionTags(key: string, tags: string[]): Promise<void> {\n await this.store.updateMetadata(key, { tags: [...new Set(tags)] });\n this.emit('sessionUpdated', { key, tags });\n }\n\n // ========== Status Management ==========\n\n async archiveSession(key: string): Promise<void> {\n await this.store.archive(key);\n this.emit('sessionArchived', { key });\n }\n\n async unarchiveSession(key: string): Promise<void> {\n await this.store.unarchive(key);\n this.emit('sessionRestored', { key });\n }\n\n async pinSession(key: string): Promise<void> {\n await this.store.pin(key);\n this.emit('sessionPinned', { key });\n }\n\n async unpinSession(key: string): Promise<void> {\n await this.store.unpin(key);\n this.emit('sessionUnpinned', { key });\n }\n\n async setSessionStatus(key: string, status: SessionStatus): Promise<void> {\n await this.store.setStatus(key, status);\n this.emit('sessionStatusChanged', { key, status });\n }\n\n // ========== Search ==========\n\n async searchSessions(query: string): Promise<SessionMetadata[]> {\n const result = await this.store.list({ search: query, limit: 100 });\n return result.items;\n }\n\n async searchInSession(key: string, keyword: string): Promise<Message[]> {\n return this.store.searchInSession(key, keyword);\n }\n\n // ========== Export/Import ==========\n\n async exportSession(key: string, format: ExportFormat): Promise<string> {\n return this.store.exportSession(key, format);\n }\n\n // ========== Statistics ==========\n\n async getStats(): Promise<GlobalSessionStats> {\n return this.store.getStats();\n }\n\n // ========== Maintenance ==========\n\n async archiveOldSessions(olderThanDays: number): Promise<number> {\n const count = await this.store.archiveOld(olderThanDays);\n log.info({ count, olderThanDays }, 'Archived old sessions');\n return count;\n }\n\n // ========== Event Helpers ==========\n\n onSessionCreated(callback: (metadata: SessionMetadata) => void): void {\n this.on('sessionCreated', callback);\n }\n\n onSessionUpdated(callback: (data: { key: string; name?: string; tags?: string[] }) => void): void {\n this.on('sessionUpdated', callback);\n }\n\n onSessionDeleted(callback: (data: { key: string }) => void): void {\n this.on('sessionDeleted', callback);\n }\n\n onSessionArchived(callback: (data: { key: string }) => void): void {\n this.on('sessionArchived', callback);\n }\n\n onSessionRestored(callback: (data: { key: string }) => void): void {\n this.on('sessionRestored', callback);\n }\n\n onSessionPinned(callback: (data: { key: string }) => void): void {\n this.on('sessionPinned', callback);\n }\n\n onSessionUnpinned(callback: (data: { key: string }) => void): void {\n this.on('sessionUnpinned', callback);\n }\n\n onSessionStatusChanged(callback: (data: { key: string; status: SessionStatus }) => void): void {\n this.on('sessionStatusChanged', callback);\n }\n\n onSessionAccessed(callback: (data: { key: string }) => void): void {\n this.on('sessionAccessed', callback);\n }\n\n // ========== Store delegation (messages, compaction) ==========\n\n /** Load messages for a session key */\n async loadMessages(key: string) {\n return this.store.loadMessages(key);\n }\n\n /** Save messages for a session key */\n async saveMessages(key: string, messages: any[]) {\n return this.store.saveMessages(key, messages);\n }\n\n /** Delete session data */\n async delete(key: string): Promise<void> {\n await this.store.delete(key);\n }\n\n /** Token/window stats for a message list */\n getWindowStats(messages: any[]) {\n return this.store.getWindowStats(messages);\n }\n\n /** Prepare compaction run */\n prepareCompaction(key: string, messages: any[], contextWindow: number) {\n return this.store.prepareCompaction(key, messages, contextWindow);\n }\n\n /** Compact session messages */\n compact(\n key: string,\n messages: any[],\n contextWindow: number,\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n return this.store.compact(key, messages, contextWindow, instructions, force);\n }\n\n /** Compaction stats for a session */\n async getCompactionStats(key: string) {\n return this.store.getCompactionStats(key);\n }\n\n /** Estimate token usage for messages */\n async estimateTokenUsage(key: string, messages: any[]): Promise<number> {\n return this.store.estimateTokens(messages);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"manager.js","names":["EventEmitter"],"sources":["../../../src/session/manager.ts"],"sourcesContent":["// Session manager - high-level session management service\n\nimport EventEmitter from 'events';\nimport { createLogger } from '../utils/logger.js';\nimport { SessionStore } from './store.js';\nimport type {\n SessionMetadata,\n SessionDetail,\n SessionListQuery,\n PaginatedResult,\n GlobalSessionStats,\n ExportFormat,\n SessionStatus,\n} from './types.js';\nimport type { Message } from './types.js';\nimport type { CompactionConfig, CompactionResult } from '../agent/memory/compaction.js';\nimport type { XopcSessionTranscriptV1 } from './transcript-format.js';\nimport type { XopcTranscriptContextEntry } from './session-context-for-llm.js';\nimport { applySessionPatchToMetadata, type SessionPatchBody } from './patch-metadata.js';\nimport type { WindowConfig } from '../agent/memory/window.js';\nimport type { Config } from '../config/schema.js';\n\nconst log = createLogger('SessionManager');\n\nexport interface SessionManagerConfig {\n config: Config;\n agentId?: string;\n sessionsDir?: string;\n windowConfig?: Partial<WindowConfig>;\n compactionConfig?: Partial<CompactionConfig>;\n}\n\nexport class SessionManager extends EventEmitter {\n private store: SessionStore;\n\n constructor(config: SessionManagerConfig) {\n super();\n this.store = new SessionStore(\n {\n config: config.config,\n agentId: config.agentId,\n sessionsDir: config.sessionsDir,\n },\n config.windowConfig,\n config.compactionConfig\n );\n }\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n this.emit('ready');\n }\n\n /** Low-level store (e.g. cron resolving weixin delivery from session index). */\n getStore(): SessionStore {\n return this.store;\n }\n\n // ========== CRUD Operations ==========\n\n async listSessions(query?: SessionListQuery): Promise<PaginatedResult<SessionMetadata>> {\n return this.store.list(query);\n }\n\n /**\n * List all subagent sessions.\n * Subagent sessions have keys starting with 'subagent:'.\n */\n async listSubagents(query: SessionListQuery = {}): Promise<PaginatedResult<SessionMetadata>> {\n // Filter for subagent sessions only\n const subagentQuery: SessionListQuery = {\n ...query,\n search: query.search ? `subagent:${query.search}` : 'subagent:',\n };\n \n const result = await this.store.list(subagentQuery);\n \n // Additional filtering to ensure only subagent sessions\n const subagentSessions = result.items.filter((s) => s.key.startsWith('subagent:'));\n \n return {\n ...result,\n items: subagentSessions,\n total: subagentSessions.length,\n hasMore: false, // Simplified for now\n };\n }\n\n async getSession(\n key: string,\n options?: { includeTranscriptSummary?: boolean; includeTranscriptRows?: boolean },\n ): Promise<SessionDetail | null> {\n const session = await this.store.get(key, options);\n if (session) {\n this.emit('sessionAccessed', { key });\n }\n return session;\n }\n\n /**\n * OpenClaw-style `sessions.patch`: partial metadata (name, tags, customData shallow merge).\n */\n async patchSession(\n key: string,\n patch: SessionPatchBody,\n ): Promise<{ ok: true } | { ok: false; error: string }> {\n const meta = await this.store.getMetadata(key);\n if (!meta) {\n return { ok: false, error: 'Session not found' };\n }\n const updates = applySessionPatchToMetadata(meta, patch);\n if (Object.keys(updates).length === 0) {\n return { ok: true };\n }\n await this.store.updateMetadata(key, updates);\n this.emit('sessionUpdated', { key });\n return { ok: true };\n }\n\n async getSessionMetadata(key: string): Promise<SessionMetadata | null> {\n return this.store.getMetadata(key);\n }\n\n async deleteSession(key: string): Promise<boolean> {\n const result = await this.store.delete(key);\n if (result) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n async deleteSessions(keys: string[]): Promise<{ success: string[]; failed: string[] }> {\n const result = await this.store.deleteMany(keys);\n for (const key of result.success) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n // ========== Metadata Updates ==========\n\n async renameSession(key: string, name: string): Promise<void> {\n await this.store.updateMetadata(key, { name });\n this.emit('sessionUpdated', { key, name });\n }\n\n /** Partial metadata update (caller merges nested fields like `customData` when needed). */\n async updateSessionMetadata(key: string, updates: Partial<SessionMetadata>): Promise<void> {\n await this.store.updateMetadata(key, updates);\n this.emit('sessionUpdated', { key });\n }\n\n async tagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n // Merge tags, remove duplicates\n const mergedTags = [...new Set([...existing.tags, ...tags])];\n await this.store.updateMetadata(key, { tags: mergedTags });\n this.emit('sessionUpdated', { key, tags: mergedTags });\n }\n\n async untagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n const filteredTags = existing.tags.filter((t) => !tags.includes(t));\n await this.store.updateMetadata(key, { tags: filteredTags });\n this.emit('sessionUpdated', { key, tags: filteredTags });\n }\n\n async setSessionTags(key: string, tags: string[]): Promise<void> {\n await this.store.updateMetadata(key, { tags: [...new Set(tags)] });\n this.emit('sessionUpdated', { key, tags });\n }\n\n // ========== Status Management ==========\n\n async archiveSession(key: string): Promise<void> {\n await this.store.archive(key);\n this.emit('sessionArchived', { key });\n }\n\n async unarchiveSession(key: string): Promise<void> {\n await this.store.unarchive(key);\n this.emit('sessionRestored', { key });\n }\n\n async pinSession(key: string): Promise<void> {\n await this.store.pin(key);\n this.emit('sessionPinned', { key });\n }\n\n async unpinSession(key: string): Promise<void> {\n await this.store.unpin(key);\n this.emit('sessionUnpinned', { key });\n }\n\n async setSessionStatus(key: string, status: SessionStatus): Promise<void> {\n await this.store.setStatus(key, status);\n this.emit('sessionStatusChanged', { key, status });\n }\n\n // ========== Search ==========\n\n async searchSessions(query: string): Promise<SessionMetadata[]> {\n const result = await this.store.list({ search: query, limit: 100 });\n return result.items;\n }\n\n async searchInSession(key: string, keyword: string): Promise<Message[]> {\n return this.store.searchInSession(key, keyword);\n }\n\n // ========== Export/Import ==========\n\n async exportSession(key: string, format: ExportFormat): Promise<string> {\n return this.store.exportSession(key, format);\n }\n\n // ========== Statistics ==========\n\n async getStats(): Promise<GlobalSessionStats> {\n return this.store.getStats();\n }\n\n // ========== Maintenance ==========\n\n async archiveOldSessions(olderThanDays: number): Promise<number> {\n const count = await this.store.archiveOld(olderThanDays);\n log.info({ count, olderThanDays }, 'Archived old sessions');\n return count;\n }\n\n // ========== Event Helpers ==========\n\n onSessionCreated(callback: (metadata: SessionMetadata) => void): void {\n this.on('sessionCreated', callback);\n }\n\n onSessionUpdated(callback: (data: { key: string; name?: string; tags?: string[] }) => void): void {\n this.on('sessionUpdated', callback);\n }\n\n onSessionDeleted(callback: (data: { key: string }) => void): void {\n this.on('sessionDeleted', callback);\n }\n\n onSessionArchived(callback: (data: { key: string }) => void): void {\n this.on('sessionArchived', callback);\n }\n\n onSessionRestored(callback: (data: { key: string }) => void): void {\n this.on('sessionRestored', callback);\n }\n\n onSessionPinned(callback: (data: { key: string }) => void): void {\n this.on('sessionPinned', callback);\n }\n\n onSessionUnpinned(callback: (data: { key: string }) => void): void {\n this.on('sessionUnpinned', callback);\n }\n\n onSessionStatusChanged(callback: (data: { key: string; status: SessionStatus }) => void): void {\n this.on('sessionStatusChanged', callback);\n }\n\n onSessionAccessed(callback: (data: { key: string }) => void): void {\n this.on('sessionAccessed', callback);\n }\n\n // ========== Store delegation (messages, compaction) ==========\n\n /** Load messages for a session key */\n async loadMessages(key: string) {\n return this.store.loadMessages(key);\n }\n\n /** Wrapped transcript document (stable id, compaction history); null if missing or not a valid envelope. */\n async loadTranscriptDocument(key: string): Promise<XopcSessionTranscriptV1 | null> {\n return this.store.loadTranscriptDocument(key);\n }\n\n /** Save messages for a session key */\n async saveMessages(key: string, messages: any[]) {\n return this.store.saveMessages(key, messages);\n }\n\n /**\n * Append `kind: 'context'` transcript row (persisted, excluded from {@link loadMessages} / LLM).\n */\n async appendTranscriptContextEntry(\n key: string,\n entry: Omit<XopcTranscriptContextEntry, 'kind'> & Partial<Pick<XopcTranscriptContextEntry, 'kind'>>,\n ): Promise<void> {\n await this.store.appendTranscriptContextEntry(key, entry);\n this.emit('sessionUpdated', { key });\n }\n\n /** Delete session data */\n async delete(key: string): Promise<void> {\n await this.store.delete(key);\n }\n\n /** Token/window stats for a message list */\n getWindowStats(messages: any[]) {\n return this.store.getWindowStats(messages);\n }\n\n /** Prepare compaction run */\n prepareCompaction(key: string, messages: any[], contextWindow: number) {\n return this.store.prepareCompaction(key, messages, contextWindow);\n }\n\n /** Compact session messages */\n compact(\n key: string,\n messages: any[],\n contextWindow: number,\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n return this.store.compact(key, messages, contextWindow, instructions, force);\n }\n\n /** Compaction stats for a session */\n async getCompactionStats(key: string) {\n return this.store.getCompactionStats(key);\n }\n\n /** List pre-compaction transcript snapshots (newest first). */\n listCompactionCheckpoints(key: string) {\n return this.store.listCompactionCheckpoints(key);\n }\n\n getCompactionCheckpointDetail(key: string, checkpointId: string) {\n return this.store.getCompactionCheckpointDetail(key, checkpointId);\n }\n\n restoreCompactionCheckpoint(key: string, checkpointId: string) {\n return this.store.restoreCompactionCheckpoint(key, checkpointId);\n }\n\n /** Estimate token usage for messages */\n async estimateTokenUsage(key: string, messages: any[]): Promise<number> {\n return this.store.estimateTokens(messages);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * OpenClaw-style partial session row updates: merge tags and shallow-merge customData.
3
+ */
4
+ import type { SessionMetadata } from './types.js';
5
+ export type SessionPatchBody = {
6
+ name?: string;
7
+ tags?: string[];
8
+ /** When true, `tags` replaces the tag list; otherwise tags are union-merged. */
9
+ replaceTags?: boolean;
10
+ customData?: Record<string, unknown>;
11
+ };
12
+ export declare function applySessionPatchToMetadata(existing: SessionMetadata, patch: SessionPatchBody): Partial<SessionMetadata>;
@@ -0,0 +1,23 @@
1
+ //#region src/session/patch-metadata.ts
2
+ function applySessionPatchToMetadata(existing, patch) {
3
+ const out = {};
4
+ if (typeof patch.name === "string") {
5
+ const t = patch.name.trim();
6
+ if (t.length > 0) out.name = t;
7
+ else out.name = void 0;
8
+ }
9
+ if (patch.tags !== void 0 && Array.isArray(patch.tags)) {
10
+ const normalized = [...new Set(patch.tags.map((t) => String(t).trim()).filter(Boolean))];
11
+ if (patch.replaceTags) out.tags = normalized;
12
+ else out.tags = [...new Set([...existing.tags, ...normalized])];
13
+ }
14
+ if (patch.customData !== void 0 && typeof patch.customData === "object" && patch.customData !== null) out.customData = {
15
+ ...existing.customData ?? {},
16
+ ...patch.customData
17
+ };
18
+ return out;
19
+ }
20
+ //#endregion
21
+ export { applySessionPatchToMetadata };
22
+
23
+ //# sourceMappingURL=patch-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-metadata.js","names":[],"sources":["../../../src/session/patch-metadata.ts"],"sourcesContent":["/**\n * OpenClaw-style partial session row updates: merge tags and shallow-merge customData.\n */\n\nimport type { SessionMetadata } from './types.js';\n\nexport type SessionPatchBody = {\n name?: string;\n tags?: string[];\n /** When true, `tags` replaces the tag list; otherwise tags are union-merged. */\n replaceTags?: boolean;\n customData?: Record<string, unknown>;\n};\n\nexport function applySessionPatchToMetadata(\n existing: SessionMetadata,\n patch: SessionPatchBody,\n): Partial<SessionMetadata> {\n const out: Partial<SessionMetadata> = {};\n\n if (typeof patch.name === 'string') {\n const t = patch.name.trim();\n if (t.length > 0) {\n out.name = t;\n } else {\n out.name = undefined;\n }\n }\n\n if (patch.tags !== undefined && Array.isArray(patch.tags)) {\n const normalized = [...new Set(patch.tags.map((t) => String(t).trim()).filter(Boolean))];\n if (patch.replaceTags) {\n out.tags = normalized;\n } else {\n out.tags = [...new Set([...existing.tags, ...normalized])];\n }\n }\n\n if (patch.customData !== undefined && typeof patch.customData === 'object' && patch.customData !== null) {\n out.customData = { ...(existing.customData ?? {}), ...patch.customData };\n }\n\n return out;\n}\n"],"mappings":";AAcA,SAAgB,4BACd,UACA,OAC0B;CAC1B,MAAM,MAAgC,EAAE;AAExC,KAAI,OAAO,MAAM,SAAS,UAAU;EAClC,MAAM,IAAI,MAAM,KAAK,MAAM;AAC3B,MAAI,EAAE,SAAS,EACb,KAAI,OAAO;MAEX,KAAI,OAAO,KAAA;;AAIf,KAAI,MAAM,SAAS,KAAA,KAAa,MAAM,QAAQ,MAAM,KAAK,EAAE;EACzD,MAAM,aAAa,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,KAAK,MAAM,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC;AACxF,MAAI,MAAM,YACR,KAAI,OAAO;MAEX,KAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,SAAS,MAAM,GAAG,WAAW,CAAC,CAAC;;AAI9D,KAAI,MAAM,eAAe,KAAA,KAAa,OAAO,MAAM,eAAe,YAAY,MAAM,eAAe,KACjG,KAAI,aAAa;EAAE,GAAI,SAAS,cAAc,EAAE;EAAG,GAAG,MAAM;EAAY;AAG1E,QAAO"}
@@ -17,5 +17,7 @@ export declare class SessionSearchIndex {
17
17
  private extractSessionKeyFromPath;
18
18
  private findSessionJsonFiles;
19
19
  private buildWordIndex;
20
+ /** Index `kind: 'context'` `text` / `id` so session search matches audit rows (synthetic slot indices). */
21
+ private mergeContextTextIntoWordIndex;
20
22
  private mergeIntoGlobalIndex;
21
23
  }
@@ -1,5 +1,7 @@
1
1
  import { FILENAMES, init_paths } from "../config/paths.js";
2
2
  import { fileStemToSessionKey } from "./session-file-key.js";
3
+ import { isTranscriptContextEntry } from "./session-context-for-llm.js";
4
+ import { parseStoredTranscriptJson } from "./transcript-format.js";
3
5
  import { basename, join } from "node:path";
4
6
  import { readFile, readdir } from "node:fs/promises";
5
7
  //#region src/session/search-index.ts
@@ -19,10 +21,17 @@ var SessionSearchIndex = class {
19
21
  const files = await this.findSessionJsonFiles(sessionsRoot);
20
22
  for (const file of files) try {
21
23
  const raw = await readFile(file, "utf-8");
22
- const messages = JSON.parse(raw);
23
- if (!Array.isArray(messages)) continue;
24
+ if (!raw.trim()) continue;
25
+ try {
26
+ JSON.parse(raw);
27
+ } catch {
28
+ continue;
29
+ }
30
+ const { messages, rows, envelope } = parseStoredTranscriptJson(raw);
31
+ if (!envelope) continue;
24
32
  const key = this.extractSessionKeyFromPath(file);
25
33
  const wordIndex = this.buildWordIndex(messages);
34
+ this.mergeContextTextIntoWordIndex(wordIndex, rows, messages.length);
26
35
  const indexed = {
27
36
  key,
28
37
  messages,
@@ -84,6 +93,25 @@ var SessionSearchIndex = class {
84
93
  }
85
94
  return index;
86
95
  }
96
+ /** Index `kind: 'context'` `text` / `id` so session search matches audit rows (synthetic slot indices). */
97
+ mergeContextTextIntoWordIndex(wordIndex, rows, llmMessageCount) {
98
+ let slot = 0;
99
+ for (const r of rows) {
100
+ if (!isTranscriptContextEntry(r)) continue;
101
+ const parts = [];
102
+ if (typeof r.text === "string" && r.text.trim()) parts.push(r.text);
103
+ if (typeof r.id === "string" && r.id.trim()) parts.push(r.id);
104
+ const blob = parts.join(" ");
105
+ if (!blob.trim()) continue;
106
+ const words = tokenizeWords(blob);
107
+ const idx = llmMessageCount + slot;
108
+ slot += 1;
109
+ for (const word of words) {
110
+ if (!wordIndex.has(word)) wordIndex.set(word, /* @__PURE__ */ new Set());
111
+ wordIndex.get(word).add(idx);
112
+ }
113
+ }
114
+ }
87
115
  mergeIntoGlobalIndex(sessionKey, wordIndex) {
88
116
  for (const word of wordIndex.keys()) {
89
117
  if (!this.globalWordIndex.has(word)) this.globalWordIndex.set(word, /* @__PURE__ */ new Set());
@@ -1 +1 @@
1
- {"version":3,"file":"search-index.js","names":[],"sources":["../../../src/session/search-index.ts"],"sourcesContent":["/**\n * In-memory inverted index over session transcript JSON files on disk.\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\n\nimport type { AgentMessage } from '@mariozechner/pi-agent-core';\n\nimport { FILENAMES } from '../config/paths.js';\nimport { fileStemToSessionKey } from './session-file-key.js';\n\ninterface IndexedSession {\n key: string;\n messages: AgentMessage[];\n wordIndex: Map<string, Set<number>>;\n}\n\nexport class SessionSearchIndex {\n private sessions = new Map<string, IndexedSession>();\n private globalWordIndex = new Map<string, Set<string>>();\n\n /**\n * Scan `sessionsRoot` (same root as {@link SessionStore}: sharded `*.json`, excludes index/archive).\n */\n async buildIndex(sessionsRoot: string): Promise<void> {\n this.sessions.clear();\n this.globalWordIndex.clear();\n\n const files = await this.findSessionJsonFiles(sessionsRoot);\n\n for (const file of files) {\n try {\n const raw = await readFile(file, 'utf-8');\n const messages = JSON.parse(raw) as AgentMessage[];\n if (!Array.isArray(messages)) {\n continue;\n }\n\n const key = this.extractSessionKeyFromPath(file);\n const wordIndex = this.buildWordIndex(messages);\n\n const indexed: IndexedSession = { key, messages, wordIndex };\n this.sessions.set(key, indexed);\n this.mergeIntoGlobalIndex(key, wordIndex);\n } catch {\n /* skip bad files */\n }\n }\n }\n\n search(query: string, limit = 10): Array<{ key: string; score: number }> {\n const queryWords = tokenizeWords(query);\n\n if (queryWords.length === 0) {\n return [];\n }\n\n const scores = new Map<string, number>();\n\n for (const word of queryWords) {\n const matchingSessions = this.globalWordIndex.get(word);\n if (!matchingSessions) {\n continue;\n }\n for (const sessionKey of matchingSessions) {\n scores.set(sessionKey, (scores.get(sessionKey) || 0) + 1);\n }\n }\n\n return Array.from(scores.entries())\n .map(([key, score]) => ({ key, score: score / queryWords.length }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n }\n\n getSessionMessages(key: string): AgentMessage[] {\n return this.sessions.get(key)?.messages ?? [];\n }\n\n private extractSessionKeyFromPath(filePath: string): string {\n const base = basename(filePath, '.json');\n return fileStemToSessionKey(base);\n }\n\n private async findSessionJsonFiles(dir: string): Promise<string[]> {\n const files: string[] = [];\n\n const walk = async (rel: string): Promise<void> => {\n const abs = join(dir, rel);\n let entries;\n try {\n entries = await readdir(abs, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const ent of entries) {\n const childRel = rel ? join(rel, ent.name) : ent.name;\n if (ent.isDirectory()) {\n if (ent.name === 'archive') {\n continue;\n }\n await walk(childRel);\n } else if (\n ent.name.endsWith('.json') &&\n ent.name !== FILENAMES.SESSIONS_INDEX &&\n !ent.name.endsWith('.meta.json')\n ) {\n files.push(join(dir, childRel));\n }\n }\n };\n\n await walk('');\n return files;\n }\n\n private buildWordIndex(messages: AgentMessage[]): Map<string, Set<number>> {\n const index = new Map<string, Set<number>>();\n\n for (let i = 0; i < messages.length; i++) {\n const text = extractIndexableText(messages[i]?.content);\n const words = tokenizeWords(text);\n\n for (const word of words) {\n if (!index.has(word)) {\n index.set(word, new Set());\n }\n index.get(word)!.add(i);\n }\n }\n\n return index;\n }\n\n private mergeIntoGlobalIndex(sessionKey: string, wordIndex: Map<string, Set<number>>): void {\n for (const word of wordIndex.keys()) {\n if (!this.globalWordIndex.has(word)) {\n this.globalWordIndex.set(word, new Set());\n }\n this.globalWordIndex.get(word)!.add(sessionKey);\n }\n }\n}\n\nfunction tokenizeWords(text: string): string[] {\n return text\n .toLowerCase()\n .split(/\\W+/)\n .map((w) => w.trim())\n .filter(Boolean);\n}\n\nfunction extractIndexableText(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const item of content) {\n if (typeof item !== 'object' || item === null || !('type' in item)) {\n continue;\n }\n const c = item as { type?: string; text?: string };\n if (c.type === 'text' && typeof c.text === 'string') {\n parts.push(c.text);\n }\n }\n return parts.join(' ');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"search-index.js","names":[],"sources":["../../../src/session/search-index.ts"],"sourcesContent":["/**\n * In-memory inverted index over session transcript JSON files on disk.\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\n\nimport type { AgentMessage } from '@mariozechner/pi-agent-core';\n\nimport { FILENAMES } from '../config/paths.js';\nimport { fileStemToSessionKey } from './session-file-key.js';\nimport { parseStoredTranscriptJson } from './transcript-format.js';\nimport { isTranscriptContextEntry, type TranscriptStoredRow } from './session-context-for-llm.js';\n\ninterface IndexedSession {\n key: string;\n messages: AgentMessage[];\n wordIndex: Map<string, Set<number>>;\n}\n\nexport class SessionSearchIndex {\n private sessions = new Map<string, IndexedSession>();\n private globalWordIndex = new Map<string, Set<string>>();\n\n /**\n * Scan `sessionsRoot` (same root as {@link SessionStore}: sharded `*.json`, excludes index/archive).\n */\n async buildIndex(sessionsRoot: string): Promise<void> {\n this.sessions.clear();\n this.globalWordIndex.clear();\n\n const files = await this.findSessionJsonFiles(sessionsRoot);\n\n for (const file of files) {\n try {\n const raw = await readFile(file, 'utf-8');\n const trimmed = raw.trim();\n if (!trimmed) {\n continue;\n }\n try {\n JSON.parse(raw);\n } catch {\n continue;\n }\n const { messages, rows, envelope } = parseStoredTranscriptJson(raw);\n if (!envelope) {\n continue;\n }\n\n const key = this.extractSessionKeyFromPath(file);\n const wordIndex = this.buildWordIndex(messages);\n this.mergeContextTextIntoWordIndex(wordIndex, rows, messages.length);\n\n const indexed: IndexedSession = { key, messages, wordIndex };\n this.sessions.set(key, indexed);\n this.mergeIntoGlobalIndex(key, wordIndex);\n } catch {\n /* skip bad files */\n }\n }\n }\n\n search(query: string, limit = 10): Array<{ key: string; score: number }> {\n const queryWords = tokenizeWords(query);\n\n if (queryWords.length === 0) {\n return [];\n }\n\n const scores = new Map<string, number>();\n\n for (const word of queryWords) {\n const matchingSessions = this.globalWordIndex.get(word);\n if (!matchingSessions) {\n continue;\n }\n for (const sessionKey of matchingSessions) {\n scores.set(sessionKey, (scores.get(sessionKey) || 0) + 1);\n }\n }\n\n return Array.from(scores.entries())\n .map(([key, score]) => ({ key, score: score / queryWords.length }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n }\n\n getSessionMessages(key: string): AgentMessage[] {\n return this.sessions.get(key)?.messages ?? [];\n }\n\n private extractSessionKeyFromPath(filePath: string): string {\n const base = basename(filePath, '.json');\n return fileStemToSessionKey(base);\n }\n\n private async findSessionJsonFiles(dir: string): Promise<string[]> {\n const files: string[] = [];\n\n const walk = async (rel: string): Promise<void> => {\n const abs = join(dir, rel);\n let entries;\n try {\n entries = await readdir(abs, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const ent of entries) {\n const childRel = rel ? join(rel, ent.name) : ent.name;\n if (ent.isDirectory()) {\n if (ent.name === 'archive') {\n continue;\n }\n await walk(childRel);\n } else if (\n ent.name.endsWith('.json') &&\n ent.name !== FILENAMES.SESSIONS_INDEX &&\n !ent.name.endsWith('.meta.json')\n ) {\n files.push(join(dir, childRel));\n }\n }\n };\n\n await walk('');\n return files;\n }\n\n private buildWordIndex(messages: AgentMessage[]): Map<string, Set<number>> {\n const index = new Map<string, Set<number>>();\n\n for (let i = 0; i < messages.length; i++) {\n const text = extractIndexableText(messages[i]?.content);\n const words = tokenizeWords(text);\n\n for (const word of words) {\n if (!index.has(word)) {\n index.set(word, new Set());\n }\n index.get(word)!.add(i);\n }\n }\n\n return index;\n }\n\n /** Index `kind: 'context'` `text` / `id` so session search matches audit rows (synthetic slot indices). */\n private mergeContextTextIntoWordIndex(\n wordIndex: Map<string, Set<number>>,\n rows: TranscriptStoredRow[],\n llmMessageCount: number,\n ): void {\n let slot = 0;\n for (const r of rows) {\n if (!isTranscriptContextEntry(r)) continue;\n const parts: string[] = [];\n if (typeof r.text === 'string' && r.text.trim()) parts.push(r.text);\n if (typeof r.id === 'string' && r.id.trim()) parts.push(r.id);\n const blob = parts.join(' ');\n if (!blob.trim()) continue;\n const words = tokenizeWords(blob);\n const idx = llmMessageCount + slot;\n slot += 1;\n for (const word of words) {\n if (!wordIndex.has(word)) {\n wordIndex.set(word, new Set());\n }\n wordIndex.get(word)!.add(idx);\n }\n }\n }\n\n private mergeIntoGlobalIndex(sessionKey: string, wordIndex: Map<string, Set<number>>): void {\n for (const word of wordIndex.keys()) {\n if (!this.globalWordIndex.has(word)) {\n this.globalWordIndex.set(word, new Set());\n }\n this.globalWordIndex.get(word)!.add(sessionKey);\n }\n }\n}\n\nfunction tokenizeWords(text: string): string[] {\n return text\n .toLowerCase()\n .split(/\\W+/)\n .map((w) => w.trim())\n .filter(Boolean);\n}\n\nfunction extractIndexableText(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const item of content) {\n if (typeof item !== 'object' || item === null || !('type' in item)) {\n continue;\n }\n const c = item as { type?: string; text?: string };\n if (c.type === 'text' && typeof c.text === 'string') {\n parts.push(c.text);\n }\n }\n return parts.join(' ');\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Transcript rows persisted on disk may include non-LLM entries (e.g. `kind: 'context'`).
3
+ * {@link buildSessionContextForLlm} is the single choke point for provider-facing history.
4
+ *
5
+ * Do not pass raw `parseStoredTranscriptJson(...).rows` into pi-agent / providers — always run
6
+ * {@link buildSessionContextForLlm} first (or use {@link SessionStore.loadMessages}, which already does).
7
+ */
8
+ import type { AgentMessage } from '@mariozechner/pi-agent-core';
9
+ /** Persisted-only row: never sent to the model as a chat message. */
10
+ export interface XopcTranscriptContextEntry {
11
+ kind: 'context';
12
+ id?: string;
13
+ /** Short human-readable line for UIs / logs. */
14
+ text?: string;
15
+ /** Structured payload (tool summaries, delivery metadata, etc.). */
16
+ data?: Record<string, unknown>;
17
+ createdAt?: string;
18
+ }
19
+ export type TranscriptStoredRow = AgentMessage | XopcTranscriptContextEntry;
20
+ export declare function isTranscriptContextEntry(x: unknown): x is XopcTranscriptContextEntry;
21
+ /**
22
+ * Normalize a JSON array from on-disk transcript into stored rows (drops unrecognized objects).
23
+ */
24
+ export declare function transcriptRowsFromJsonArray(arr: unknown[]): TranscriptStoredRow[];
25
+ /** Messages only — what providers and pi-agent should see. */
26
+ export declare function buildSessionContextForLlm(rows: TranscriptStoredRow[]): AgentMessage[];
27
+ /**
28
+ * When persisting LLM messages, keep prior `kind: 'context'` rows in their relative positions:
29
+ * each non-context slot in the previous file is replaced by the next incoming LLM message;
30
+ * trailing new LLM rows are appended. Extra old LLM rows are dropped if the new list is shorter.
31
+ */
32
+ export declare function mergeLlmMessagesPreservingContextRows(prevRows: TranscriptStoredRow[], llmMessages: AgentMessage[]): TranscriptStoredRow[];
@@ -0,0 +1,60 @@
1
+ //#region src/session/session-context-for-llm.ts
2
+ function isTranscriptContextEntry(x) {
3
+ if (!x || typeof x !== "object") return false;
4
+ return x.kind === "context";
5
+ }
6
+ const LLM_ROLES = new Set([
7
+ "user",
8
+ "assistant",
9
+ "system",
10
+ "tool",
11
+ "toolResult"
12
+ ]);
13
+ function isLikelyAgentMessage(x) {
14
+ if (!x || typeof x !== "object") return false;
15
+ const role = x.role;
16
+ return typeof role === "string" && LLM_ROLES.has(role);
17
+ }
18
+ /**
19
+ * Normalize a JSON array from on-disk transcript into stored rows (drops unrecognized objects).
20
+ */
21
+ function transcriptRowsFromJsonArray(arr) {
22
+ const out = [];
23
+ for (const x of arr) {
24
+ if (isTranscriptContextEntry(x)) {
25
+ out.push(x);
26
+ continue;
27
+ }
28
+ if (isLikelyAgentMessage(x)) out.push(x);
29
+ }
30
+ return out;
31
+ }
32
+ /** Messages only — what providers and pi-agent should see. */
33
+ function buildSessionContextForLlm(rows) {
34
+ const out = [];
35
+ for (const r of rows) if (!isTranscriptContextEntry(r)) out.push(r);
36
+ return out;
37
+ }
38
+ /**
39
+ * When persisting LLM messages, keep prior `kind: 'context'` rows in their relative positions:
40
+ * each non-context slot in the previous file is replaced by the next incoming LLM message;
41
+ * trailing new LLM rows are appended. Extra old LLM rows are dropped if the new list is shorter.
42
+ */
43
+ function mergeLlmMessagesPreservingContextRows(prevRows, llmMessages) {
44
+ let i = 0;
45
+ const out = [];
46
+ for (const r of prevRows) if (isTranscriptContextEntry(r)) out.push(r);
47
+ else if (i < llmMessages.length) {
48
+ out.push(llmMessages[i]);
49
+ i += 1;
50
+ }
51
+ while (i < llmMessages.length) {
52
+ out.push(llmMessages[i]);
53
+ i += 1;
54
+ }
55
+ return out;
56
+ }
57
+ //#endregion
58
+ export { buildSessionContextForLlm, isTranscriptContextEntry, mergeLlmMessagesPreservingContextRows, transcriptRowsFromJsonArray };
59
+
60
+ //# sourceMappingURL=session-context-for-llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-context-for-llm.js","names":[],"sources":["../../../src/session/session-context-for-llm.ts"],"sourcesContent":["/**\n * Transcript rows persisted on disk may include non-LLM entries (e.g. `kind: 'context'`).\n * {@link buildSessionContextForLlm} is the single choke point for provider-facing history.\n *\n * Do not pass raw `parseStoredTranscriptJson(...).rows` into pi-agent / providers — always run\n * {@link buildSessionContextForLlm} first (or use {@link SessionStore.loadMessages}, which already does).\n */\n\nimport type { AgentMessage } from '@mariozechner/pi-agent-core';\n\n/** Persisted-only row: never sent to the model as a chat message. */\nexport interface XopcTranscriptContextEntry {\n kind: 'context';\n id?: string;\n /** Short human-readable line for UIs / logs. */\n text?: string;\n /** Structured payload (tool summaries, delivery metadata, etc.). */\n data?: Record<string, unknown>;\n createdAt?: string;\n}\n\nexport type TranscriptStoredRow = AgentMessage | XopcTranscriptContextEntry;\n\nexport function isTranscriptContextEntry(x: unknown): x is XopcTranscriptContextEntry {\n if (!x || typeof x !== 'object') return false;\n return (x as Record<string, unknown>).kind === 'context';\n}\n\nconst LLM_ROLES = new Set(['user', 'assistant', 'system', 'tool', 'toolResult']);\n\nfunction isLikelyAgentMessage(x: unknown): x is AgentMessage {\n if (!x || typeof x !== 'object') return false;\n const role = (x as Record<string, unknown>).role;\n return typeof role === 'string' && LLM_ROLES.has(role);\n}\n\n/**\n * Normalize a JSON array from on-disk transcript into stored rows (drops unrecognized objects).\n */\nexport function transcriptRowsFromJsonArray(arr: unknown[]): TranscriptStoredRow[] {\n const out: TranscriptStoredRow[] = [];\n for (const x of arr) {\n if (isTranscriptContextEntry(x)) {\n out.push(x);\n continue;\n }\n if (isLikelyAgentMessage(x)) {\n out.push(x);\n }\n }\n return out;\n}\n\n/** Messages only — what providers and pi-agent should see. */\nexport function buildSessionContextForLlm(rows: TranscriptStoredRow[]): AgentMessage[] {\n const out: AgentMessage[] = [];\n for (const r of rows) {\n if (!isTranscriptContextEntry(r)) {\n out.push(r);\n }\n }\n return out;\n}\n\n/**\n * When persisting LLM messages, keep prior `kind: 'context'` rows in their relative positions:\n * each non-context slot in the previous file is replaced by the next incoming LLM message;\n * trailing new LLM rows are appended. Extra old LLM rows are dropped if the new list is shorter.\n */\nexport function mergeLlmMessagesPreservingContextRows(\n prevRows: TranscriptStoredRow[],\n llmMessages: AgentMessage[],\n): TranscriptStoredRow[] {\n let i = 0;\n const out: TranscriptStoredRow[] = [];\n for (const r of prevRows) {\n if (isTranscriptContextEntry(r)) {\n out.push(r);\n } else {\n if (i < llmMessages.length) {\n out.push(llmMessages[i]);\n i += 1;\n }\n }\n }\n while (i < llmMessages.length) {\n out.push(llmMessages[i]);\n i += 1;\n }\n return out;\n}\n"],"mappings":";AAuBA,SAAgB,yBAAyB,GAA6C;AACpF,KAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,QAAQ,EAA8B,SAAS;;AAGjD,MAAM,YAAY,IAAI,IAAI;CAAC;CAAQ;CAAa;CAAU;CAAQ;CAAa,CAAC;AAEhF,SAAS,qBAAqB,GAA+B;AAC3D,KAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;CACxC,MAAM,OAAQ,EAA8B;AAC5C,QAAO,OAAO,SAAS,YAAY,UAAU,IAAI,KAAK;;;;;AAMxD,SAAgB,4BAA4B,KAAuC;CACjF,MAAM,MAA6B,EAAE;AACrC,MAAK,MAAM,KAAK,KAAK;AACnB,MAAI,yBAAyB,EAAE,EAAE;AAC/B,OAAI,KAAK,EAAE;AACX;;AAEF,MAAI,qBAAqB,EAAE,CACzB,KAAI,KAAK,EAAE;;AAGf,QAAO;;;AAIT,SAAgB,0BAA0B,MAA6C;CACrF,MAAM,MAAsB,EAAE;AAC9B,MAAK,MAAM,KAAK,KACd,KAAI,CAAC,yBAAyB,EAAE,CAC9B,KAAI,KAAK,EAAE;AAGf,QAAO;;;;;;;AAQT,SAAgB,sCACd,UACA,aACuB;CACvB,IAAI,IAAI;CACR,MAAM,MAA6B,EAAE;AACrC,MAAK,MAAM,KAAK,SACd,KAAI,yBAAyB,EAAE,CAC7B,KAAI,KAAK,EAAE;UAEP,IAAI,YAAY,QAAQ;AAC1B,MAAI,KAAK,YAAY,GAAG;AACxB,OAAK;;AAIX,QAAO,IAAI,YAAY,QAAQ;AAC7B,MAAI,KAAK,YAAY,GAAG;AACxB,OAAK;;AAEP,QAAO"}
@@ -1,10 +1,12 @@
1
1
  import type { Config } from '../config/schema.js';
2
2
  import type { AgentMessage } from '@mariozechner/pi-agent-core';
3
- import type { SessionMetadata, SessionDetail, SessionListQuery, PaginatedResult, GlobalSessionStats, ExportFormat } from './types.js';
3
+ import type { SessionMetadata, SessionDetail, SessionListQuery, PaginatedResult, GlobalSessionStats, ExportFormat, CompactionCheckpointSummary, CompactionCheckpointDetail } from './types.js';
4
4
  import { SessionStatus } from './types.js';
5
5
  import type { Message } from './types.js';
6
6
  import { type CompactionConfig, type CompactionResult } from '../agent/memory/compaction.js';
7
7
  import { type WindowConfig } from '../agent/memory/window.js';
8
+ import { type XopcSessionTranscriptV1 } from './transcript-format.js';
9
+ import { type TranscriptStoredRow, type XopcTranscriptContextEntry } from './session-context-for-llm.js';
8
10
  /**
9
11
  * Session files live under `resolveSessionsDir(config, agentId)` (ADR-003), sharded by
10
12
  * `resolveSessionShardRelativePath(sessionKey)` (users/… vs system/heartbeat; web UI uses
@@ -75,7 +77,12 @@ export declare class SessionStore {
75
77
  */
76
78
  private extractRoutingFromKey;
77
79
  list(query?: SessionListQuery): Promise<PaginatedResult<SessionMetadata>>;
78
- get(key: string): Promise<SessionDetail | null>;
80
+ get(key: string, options?: {
81
+ includeTranscriptSummary?: boolean;
82
+ includeTranscriptRows?: boolean;
83
+ }): Promise<SessionDetail | null>;
84
+ /** Full on-disk transcript rows (LLM messages + optional `kind: 'context'`). */
85
+ loadTranscriptRows(key: string): Promise<TranscriptStoredRow[]>;
79
86
  getMetadata(key: string): Promise<SessionMetadata | null>;
80
87
  updateMetadata(key: string, updates: Partial<SessionMetadata>): Promise<void>;
81
88
  delete(key: string): Promise<boolean>;
@@ -91,6 +98,10 @@ export declare class SessionStore {
91
98
  loadMessages(key: string, options?: {
92
99
  fromArchive?: boolean;
93
100
  }): Promise<AgentMessage[]>;
101
+ /**
102
+ * Load the versioned transcript document (stable id, compaction history), or null if missing/invalid.
103
+ */
104
+ loadTranscriptDocument(key: string): Promise<XopcSessionTranscriptV1 | null>;
94
105
  /**
95
106
  * Find the most recent archived session file for a given key.
96
107
  * Archived files have format: {safeKey}.{timestamp}.json
@@ -98,8 +109,15 @@ export declare class SessionStore {
98
109
  private findMostRecentArchive;
99
110
  /**
100
111
  * Persist transcript JSON + merge session row into the index. Caller must hold {@link runStoreMutation} (or be nested under it).
112
+ * Transcript is stored as a versioned document (pi-style header) with stable {@link XopcSessionTranscriptV1.id}.
101
113
  */
114
+ private writeSessionTranscriptFromStoredRows;
102
115
  private writeSessionTranscriptAndUpdateIndex;
116
+ /**
117
+ * Append a persisted-only transcript row (`kind: 'context'`), visible on disk and in session search
118
+ * after stripping, but never returned from {@link loadMessages}.
119
+ */
120
+ appendTranscriptContextEntry(key: string, entry: Omit<XopcTranscriptContextEntry, 'kind'> & Partial<Pick<XopcTranscriptContextEntry, 'kind'>>): Promise<void>;
103
121
  saveMessages(key: string, messages: AgentMessage[]): Promise<void>;
104
122
  /**
105
123
  * Get window stats for messages
@@ -145,6 +163,19 @@ export declare class SessionStore {
145
163
  totalTokensAfter: number;
146
164
  lastCompactionAt: any;
147
165
  }>;
166
+ /**
167
+ * List pre-compaction transcript snapshots for a session (newest first).
168
+ */
169
+ listCompactionCheckpoints(key: string): Promise<CompactionCheckpointSummary[]>;
170
+ /**
171
+ * Metadata for a single compaction checkpoint file.
172
+ */
173
+ getCompactionCheckpointDetail(key: string, checkpointId: string): Promise<CompactionCheckpointDetail | null>;
174
+ /**
175
+ * Restore main transcript from a pre-compaction snapshot (then re-wrap + index sync).
176
+ * Does not delete the checkpoint file.
177
+ */
178
+ restoreCompactionCheckpoint(key: string, checkpointId: string): Promise<void>;
148
179
  /** Alias for delete */
149
180
  deleteSession(key: string): Promise<boolean>;
150
181
  /** Alias for loadMessages */
@@ -156,6 +187,9 @@ export declare class SessionStore {
156
187
  /** Alias for estimateTokens */
157
188
  estimateTokenUsage(key: string, messages: AgentMessage[]): Promise<number>;
158
189
  searchInSession(key: string, keyword: string): Promise<Message[]>;
190
+ /**
191
+ * JSON export includes API-shaped `messages` (LLM-only) plus `transcriptRows` (full on-disk order).
192
+ */
159
193
  exportSession(key: string, format: ExportFormat): Promise<string>;
160
194
  getStats(): Promise<GlobalSessionStats>;
161
195
  archiveOld(olderThanDays: number): Promise<number>;