@opengoat/core 2026.2.9

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 (288) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/apps/runtime/create-opengoat-runtime.d.ts +14 -0
  4. package/dist/apps/runtime/create-opengoat-runtime.js +23 -0
  5. package/dist/apps/runtime/create-opengoat-runtime.js.map +1 -0
  6. package/dist/core/acp/application/acp-agent.d.ts +60 -0
  7. package/dist/core/acp/application/acp-agent.js +424 -0
  8. package/dist/core/acp/application/acp-agent.js.map +1 -0
  9. package/dist/core/acp/application/session-store.d.ts +13 -0
  10. package/dist/core/acp/application/session-store.js +40 -0
  11. package/dist/core/acp/application/session-store.js.map +1 -0
  12. package/dist/core/acp/domain/meta.d.ts +7 -0
  13. package/dist/core/acp/domain/meta.js +31 -0
  14. package/dist/core/acp/domain/meta.js.map +1 -0
  15. package/dist/core/acp/domain/session.d.ts +23 -0
  16. package/dist/core/acp/domain/session.js +2 -0
  17. package/dist/core/acp/domain/session.js.map +1 -0
  18. package/dist/core/acp/index.d.ts +5 -0
  19. package/dist/core/acp/index.js +4 -0
  20. package/dist/core/acp/index.js.map +1 -0
  21. package/dist/core/agents/application/agent-manifest.service.d.ts +20 -0
  22. package/dist/core/agents/application/agent-manifest.service.js +89 -0
  23. package/dist/core/agents/application/agent-manifest.service.js.map +1 -0
  24. package/dist/core/agents/application/agent.service.d.ts +25 -0
  25. package/dist/core/agents/application/agent.service.js +191 -0
  26. package/dist/core/agents/application/agent.service.js.map +1 -0
  27. package/dist/core/agents/application/workspace-context.service.d.ts +28 -0
  28. package/dist/core/agents/application/workspace-context.service.js +157 -0
  29. package/dist/core/agents/application/workspace-context.service.js.map +1 -0
  30. package/dist/core/agents/domain/agent-manifest.d.ts +37 -0
  31. package/dist/core/agents/domain/agent-manifest.js +228 -0
  32. package/dist/core/agents/domain/agent-manifest.js.map +1 -0
  33. package/dist/core/agents/domain/workspace-context.d.ts +13 -0
  34. package/dist/core/agents/domain/workspace-context.js +14 -0
  35. package/dist/core/agents/domain/workspace-context.js.map +1 -0
  36. package/dist/core/agents/index.d.ts +6 -0
  37. package/dist/core/agents/index.js +6 -0
  38. package/dist/core/agents/index.js.map +1 -0
  39. package/dist/core/bootstrap/application/bootstrap.service.d.ts +24 -0
  40. package/dist/core/bootstrap/application/bootstrap.service.js +108 -0
  41. package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -0
  42. package/dist/core/bootstrap/index.d.ts +1 -0
  43. package/dist/core/bootstrap/index.js +2 -0
  44. package/dist/core/bootstrap/index.js.map +1 -0
  45. package/dist/core/domain/agent-id.d.ts +3 -0
  46. package/dist/core/domain/agent-id.js +12 -0
  47. package/dist/core/domain/agent-id.js.map +1 -0
  48. package/dist/core/domain/agent.d.ts +39 -0
  49. package/dist/core/domain/agent.js +2 -0
  50. package/dist/core/domain/agent.js.map +1 -0
  51. package/dist/core/domain/opengoat-paths.d.ts +29 -0
  52. package/dist/core/domain/opengoat-paths.js +2 -0
  53. package/dist/core/domain/opengoat-paths.js.map +1 -0
  54. package/dist/core/gateway/domain/protocol.d.ts +113 -0
  55. package/dist/core/gateway/domain/protocol.js +394 -0
  56. package/dist/core/gateway/domain/protocol.js.map +1 -0
  57. package/dist/core/gateway/index.d.ts +2 -0
  58. package/dist/core/gateway/index.js +2 -0
  59. package/dist/core/gateway/index.js.map +1 -0
  60. package/dist/core/llm/application/vercel-ai-text-runtime.d.ts +26 -0
  61. package/dist/core/llm/application/vercel-ai-text-runtime.js +223 -0
  62. package/dist/core/llm/application/vercel-ai-text-runtime.js.map +1 -0
  63. package/dist/core/llm/domain/text-runtime.d.ts +22 -0
  64. package/dist/core/llm/domain/text-runtime.js +2 -0
  65. package/dist/core/llm/domain/text-runtime.js.map +1 -0
  66. package/dist/core/llm/index.d.ts +2 -0
  67. package/dist/core/llm/index.js +2 -0
  68. package/dist/core/llm/index.js.map +1 -0
  69. package/dist/core/logging/application/structured-logger.d.ts +29 -0
  70. package/dist/core/logging/application/structured-logger.js +86 -0
  71. package/dist/core/logging/application/structured-logger.js.map +1 -0
  72. package/dist/core/logging/domain/logger.d.ts +16 -0
  73. package/dist/core/logging/domain/logger.js +18 -0
  74. package/dist/core/logging/domain/logger.js.map +1 -0
  75. package/dist/core/logging/index.d.ts +3 -0
  76. package/dist/core/logging/index.js +3 -0
  77. package/dist/core/logging/index.js.map +1 -0
  78. package/dist/core/opengoat/application/opengoat.service.d.ts +84 -0
  79. package/dist/core/opengoat/application/opengoat.service.js +308 -0
  80. package/dist/core/opengoat/application/opengoat.service.js.map +1 -0
  81. package/dist/core/opengoat/index.d.ts +1 -0
  82. package/dist/core/opengoat/index.js +2 -0
  83. package/dist/core/opengoat/index.js.map +1 -0
  84. package/dist/core/orchestration/application/orchestration-planner.service.d.ts +28 -0
  85. package/dist/core/orchestration/application/orchestration-planner.service.js +279 -0
  86. package/dist/core/orchestration/application/orchestration-planner.service.js.map +1 -0
  87. package/dist/core/orchestration/application/orchestration.service.d.ts +52 -0
  88. package/dist/core/orchestration/application/orchestration.service.js +1044 -0
  89. package/dist/core/orchestration/application/orchestration.service.js.map +1 -0
  90. package/dist/core/orchestration/application/routing.service.d.ts +11 -0
  91. package/dist/core/orchestration/application/routing.service.js +108 -0
  92. package/dist/core/orchestration/application/routing.service.js.map +1 -0
  93. package/dist/core/orchestration/domain/loop.d.ts +119 -0
  94. package/dist/core/orchestration/domain/loop.js +2 -0
  95. package/dist/core/orchestration/domain/loop.js.map +1 -0
  96. package/dist/core/orchestration/domain/routing.d.ts +58 -0
  97. package/dist/core/orchestration/domain/routing.js +2 -0
  98. package/dist/core/orchestration/domain/routing.js.map +1 -0
  99. package/dist/core/orchestration/domain/run-events.d.ts +21 -0
  100. package/dist/core/orchestration/domain/run-events.js +2 -0
  101. package/dist/core/orchestration/domain/run-events.js.map +1 -0
  102. package/dist/core/orchestration/index.d.ts +6 -0
  103. package/dist/core/orchestration/index.js +4 -0
  104. package/dist/core/orchestration/index.js.map +1 -0
  105. package/dist/core/plugins/application/plugin.service.d.ts +32 -0
  106. package/dist/core/plugins/application/plugin.service.js +236 -0
  107. package/dist/core/plugins/application/plugin.service.js.map +1 -0
  108. package/dist/core/plugins/domain/openclaw-compat.d.ts +60 -0
  109. package/dist/core/plugins/domain/openclaw-compat.js +9 -0
  110. package/dist/core/plugins/domain/openclaw-compat.js.map +1 -0
  111. package/dist/core/plugins/index.d.ts +3 -0
  112. package/dist/core/plugins/index.js +3 -0
  113. package/dist/core/plugins/index.js.map +1 -0
  114. package/dist/core/ports/command-runner.port.d.ts +14 -0
  115. package/dist/core/ports/command-runner.port.js +2 -0
  116. package/dist/core/ports/command-runner.port.js.map +1 -0
  117. package/dist/core/ports/file-system.port.d.ts +9 -0
  118. package/dist/core/ports/file-system.port.js +2 -0
  119. package/dist/core/ports/file-system.port.js.map +1 -0
  120. package/dist/core/ports/path.port.d.ts +3 -0
  121. package/dist/core/ports/path.port.js +2 -0
  122. package/dist/core/ports/path.port.js.map +1 -0
  123. package/dist/core/ports/paths-provider.port.d.ts +4 -0
  124. package/dist/core/ports/paths-provider.port.js +2 -0
  125. package/dist/core/ports/paths-provider.port.js.map +1 -0
  126. package/dist/core/providers/application/provider.service.d.ts +80 -0
  127. package/dist/core/providers/application/provider.service.js +391 -0
  128. package/dist/core/providers/application/provider.service.js.map +1 -0
  129. package/dist/core/providers/base-provider.d.ts +20 -0
  130. package/dist/core/providers/base-provider.js +44 -0
  131. package/dist/core/providers/base-provider.js.map +1 -0
  132. package/dist/core/providers/cli-provider.d.ts +26 -0
  133. package/dist/core/providers/cli-provider.js +151 -0
  134. package/dist/core/providers/cli-provider.js.map +1 -0
  135. package/dist/core/providers/command-executor.d.ts +12 -0
  136. package/dist/core/providers/command-executor.js +76 -0
  137. package/dist/core/providers/command-executor.js.map +1 -0
  138. package/dist/core/providers/errors.d.ts +30 -0
  139. package/dist/core/providers/errors.js +52 -0
  140. package/dist/core/providers/errors.js.map +1 -0
  141. package/dist/core/providers/index.d.ts +18 -0
  142. package/dist/core/providers/index.js +27 -0
  143. package/dist/core/providers/index.js.map +1 -0
  144. package/dist/core/providers/loader.d.ts +2 -0
  145. package/dist/core/providers/loader.js +99 -0
  146. package/dist/core/providers/loader.js.map +1 -0
  147. package/dist/core/providers/onboarding.d.ts +13 -0
  148. package/dist/core/providers/onboarding.js +149 -0
  149. package/dist/core/providers/onboarding.js.map +1 -0
  150. package/dist/core/providers/provider-module.d.ts +19 -0
  151. package/dist/core/providers/provider-module.js +2 -0
  152. package/dist/core/providers/provider-module.js.map +1 -0
  153. package/dist/core/providers/provider-session.d.ts +3 -0
  154. package/dist/core/providers/provider-session.js +44 -0
  155. package/dist/core/providers/provider-session.js.map +1 -0
  156. package/dist/core/providers/providers/claude/index.d.ts +4 -0
  157. package/dist/core/providers/providers/claude/index.js +19 -0
  158. package/dist/core/providers/providers/claude/index.js.map +1 -0
  159. package/dist/core/providers/providers/claude/provider.d.ts +8 -0
  160. package/dist/core/providers/providers/claude/provider.js +106 -0
  161. package/dist/core/providers/providers/claude/provider.js.map +1 -0
  162. package/dist/core/providers/providers/codex/index.d.ts +4 -0
  163. package/dist/core/providers/providers/codex/index.js +19 -0
  164. package/dist/core/providers/providers/codex/index.js.map +1 -0
  165. package/dist/core/providers/providers/codex/provider.d.ts +7 -0
  166. package/dist/core/providers/providers/codex/provider.js +31 -0
  167. package/dist/core/providers/providers/codex/provider.js.map +1 -0
  168. package/dist/core/providers/providers/cursor/index.d.ts +4 -0
  169. package/dist/core/providers/providers/cursor/index.js +19 -0
  170. package/dist/core/providers/providers/cursor/index.js.map +1 -0
  171. package/dist/core/providers/providers/cursor/provider.d.ts +11 -0
  172. package/dist/core/providers/providers/cursor/provider.js +90 -0
  173. package/dist/core/providers/providers/cursor/provider.js.map +1 -0
  174. package/dist/core/providers/providers/extended-http/catalog.d.ts +40 -0
  175. package/dist/core/providers/providers/extended-http/catalog.js +728 -0
  176. package/dist/core/providers/providers/extended-http/catalog.js.map +1 -0
  177. package/dist/core/providers/providers/extended-http/index.d.ts +5 -0
  178. package/dist/core/providers/providers/extended-http/index.js +13 -0
  179. package/dist/core/providers/providers/extended-http/index.js.map +1 -0
  180. package/dist/core/providers/providers/extended-http/provider.d.ts +31 -0
  181. package/dist/core/providers/providers/extended-http/provider.js +580 -0
  182. package/dist/core/providers/providers/extended-http/provider.js.map +1 -0
  183. package/dist/core/providers/providers/gemini/index.d.ts +4 -0
  184. package/dist/core/providers/providers/gemini/index.js +37 -0
  185. package/dist/core/providers/providers/gemini/index.js.map +1 -0
  186. package/dist/core/providers/providers/gemini/provider.d.ts +7 -0
  187. package/dist/core/providers/providers/gemini/provider.js +59 -0
  188. package/dist/core/providers/providers/gemini/provider.js.map +1 -0
  189. package/dist/core/providers/providers/grok/index.d.ts +4 -0
  190. package/dist/core/providers/providers/grok/index.js +41 -0
  191. package/dist/core/providers/providers/grok/index.js.map +1 -0
  192. package/dist/core/providers/providers/grok/provider.d.ts +13 -0
  193. package/dist/core/providers/providers/grok/provider.js +95 -0
  194. package/dist/core/providers/providers/grok/provider.js.map +1 -0
  195. package/dist/core/providers/providers/openai/index.d.ts +4 -0
  196. package/dist/core/providers/providers/openai/index.js +45 -0
  197. package/dist/core/providers/providers/openai/index.js.map +1 -0
  198. package/dist/core/providers/providers/openai/provider.d.ts +14 -0
  199. package/dist/core/providers/providers/openai/provider.js +203 -0
  200. package/dist/core/providers/providers/openai/provider.js.map +1 -0
  201. package/dist/core/providers/providers/openclaw/index.d.ts +4 -0
  202. package/dist/core/providers/providers/openclaw/index.js +31 -0
  203. package/dist/core/providers/providers/openclaw/index.js.map +1 -0
  204. package/dist/core/providers/providers/openclaw/provider.d.ts +11 -0
  205. package/dist/core/providers/providers/openclaw/provider.js +108 -0
  206. package/dist/core/providers/providers/openclaw/provider.js.map +1 -0
  207. package/dist/core/providers/providers/opencode/index.d.ts +4 -0
  208. package/dist/core/providers/providers/opencode/index.js +23 -0
  209. package/dist/core/providers/providers/opencode/index.js.map +1 -0
  210. package/dist/core/providers/providers/opencode/provider.d.ts +11 -0
  211. package/dist/core/providers/providers/opencode/provider.js +215 -0
  212. package/dist/core/providers/providers/opencode/provider.js.map +1 -0
  213. package/dist/core/providers/providers/openrouter/index.d.ts +4 -0
  214. package/dist/core/providers/providers/openrouter/index.js +37 -0
  215. package/dist/core/providers/providers/openrouter/index.js.map +1 -0
  216. package/dist/core/providers/providers/openrouter/provider.d.ts +13 -0
  217. package/dist/core/providers/providers/openrouter/provider.js +80 -0
  218. package/dist/core/providers/providers/openrouter/provider.js.map +1 -0
  219. package/dist/core/providers/registry.d.ts +13 -0
  220. package/dist/core/providers/registry.js +36 -0
  221. package/dist/core/providers/registry.js.map +1 -0
  222. package/dist/core/providers/types.d.ts +101 -0
  223. package/dist/core/providers/types.js +2 -0
  224. package/dist/core/providers/types.js.map +1 -0
  225. package/dist/core/scenarios/application/scenario-runner.service.d.ts +23 -0
  226. package/dist/core/scenarios/application/scenario-runner.service.js +172 -0
  227. package/dist/core/scenarios/application/scenario-runner.service.js.map +1 -0
  228. package/dist/core/scenarios/domain/scenario.d.ts +43 -0
  229. package/dist/core/scenarios/domain/scenario.js +2 -0
  230. package/dist/core/scenarios/domain/scenario.js.map +1 -0
  231. package/dist/core/scenarios/index.d.ts +2 -0
  232. package/dist/core/scenarios/index.js +2 -0
  233. package/dist/core/scenarios/index.js.map +1 -0
  234. package/dist/core/sessions/application/session.service.d.ts +74 -0
  235. package/dist/core/sessions/application/session.service.js +895 -0
  236. package/dist/core/sessions/application/session.service.js.map +1 -0
  237. package/dist/core/sessions/domain/session.d.ts +80 -0
  238. package/dist/core/sessions/domain/session.js +24 -0
  239. package/dist/core/sessions/domain/session.js.map +1 -0
  240. package/dist/core/sessions/domain/transcript.d.ts +28 -0
  241. package/dist/core/sessions/domain/transcript.js +10 -0
  242. package/dist/core/sessions/domain/transcript.js.map +1 -0
  243. package/dist/core/sessions/errors.d.ts +12 -0
  244. package/dist/core/sessions/errors.js +22 -0
  245. package/dist/core/sessions/errors.js.map +1 -0
  246. package/dist/core/sessions/index.d.ts +6 -0
  247. package/dist/core/sessions/index.js +4 -0
  248. package/dist/core/sessions/index.js.map +1 -0
  249. package/dist/core/skills/application/skill.service.d.ts +24 -0
  250. package/dist/core/skills/application/skill.service.js +375 -0
  251. package/dist/core/skills/application/skill.service.js.map +1 -0
  252. package/dist/core/skills/domain/skill.d.ts +62 -0
  253. package/dist/core/skills/domain/skill.js +46 -0
  254. package/dist/core/skills/domain/skill.js.map +1 -0
  255. package/dist/core/skills/index.d.ts +3 -0
  256. package/dist/core/skills/index.js +3 -0
  257. package/dist/core/skills/index.js.map +1 -0
  258. package/dist/core/templates/default-templates.d.ts +19 -0
  259. package/dist/core/templates/default-templates.js +259 -0
  260. package/dist/core/templates/default-templates.js.map +1 -0
  261. package/dist/index.d.ts +27 -0
  262. package/dist/index.js +28 -0
  263. package/dist/index.js.map +1 -0
  264. package/dist/platform/node/acp-server.d.ts +12 -0
  265. package/dist/platform/node/acp-server.js +18 -0
  266. package/dist/platform/node/acp-server.js.map +1 -0
  267. package/dist/platform/node/dotenv.d.ts +6 -0
  268. package/dist/platform/node/dotenv.js +67 -0
  269. package/dist/platform/node/dotenv.js.map +1 -0
  270. package/dist/platform/node/node-command-runner.d.ts +4 -0
  271. package/dist/platform/node/node-command-runner.js +29 -0
  272. package/dist/platform/node/node-command-runner.js.map +1 -0
  273. package/dist/platform/node/node-file-system.d.ts +10 -0
  274. package/dist/platform/node/node-file-system.js +47 -0
  275. package/dist/platform/node/node-file-system.js.map +1 -0
  276. package/dist/platform/node/node-logger.d.ts +9 -0
  277. package/dist/platform/node/node-logger.js +124 -0
  278. package/dist/platform/node/node-logger.js.map +1 -0
  279. package/dist/platform/node/node-path.port.d.ts +9 -0
  280. package/dist/platform/node/node-path.port.js +41 -0
  281. package/dist/platform/node/node-path.port.js.map +1 -0
  282. package/dist/platform/node/opengoat-gateway-client.d.ts +19 -0
  283. package/dist/platform/node/opengoat-gateway-client.js +194 -0
  284. package/dist/platform/node/opengoat-gateway-client.js.map +1 -0
  285. package/dist/platform/node/opengoat-gateway-server.d.ts +53 -0
  286. package/dist/platform/node/opengoat-gateway-server.js +906 -0
  287. package/dist/platform/node/opengoat-gateway-server.js.map +1 -0
  288. package/package.json +37 -0
@@ -0,0 +1,895 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../domain/agent-id.js";
4
+ import { DEFAULT_SESSION_CONFIG, SESSION_STORE_SCHEMA_VERSION, SESSION_TRANSCRIPT_SCHEMA_VERSION } from "../domain/session.js";
5
+ import { isSessionTranscriptCompaction, isSessionTranscriptHeader, isSessionTranscriptMessage } from "../domain/transcript.js";
6
+ import { SessionConfigParseError, SessionStoreParseError, SessionTranscriptParseError } from "../errors.js";
7
+ export class SessionService {
8
+ fileSystem;
9
+ pathPort;
10
+ commandRunner;
11
+ nowIso;
12
+ nowMs;
13
+ constructor(deps) {
14
+ this.fileSystem = deps.fileSystem;
15
+ this.pathPort = deps.pathPort;
16
+ this.commandRunner = deps.commandRunner;
17
+ this.nowIso = deps.nowIso ?? (() => new Date().toISOString());
18
+ this.nowMs = deps.nowMs ?? (() => Date.now());
19
+ }
20
+ async prepareRunSession(paths, agentId, request) {
21
+ if (request.disableSession) {
22
+ return { enabled: false };
23
+ }
24
+ const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
25
+ const workspacePath = this.pathPort.join(paths.workspacesDir, normalizedAgentId);
26
+ const workingPath = resolveWorkingPath(request.workingPath);
27
+ const config = await this.readSessionConfig(paths, normalizedAgentId);
28
+ const store = await this.readStore(paths, normalizedAgentId);
29
+ const sessionKey = resolveSessionKey({
30
+ agentId: normalizedAgentId,
31
+ mainKey: config.mainKey,
32
+ sessions: store.sessions,
33
+ reference: request.sessionRef
34
+ });
35
+ const existingEntry = store.sessions[sessionKey];
36
+ if (!existingEntry || existingEntry.workingPath !== workingPath) {
37
+ await this.ensureWorkingPathGitRepository(workingPath);
38
+ }
39
+ const fresh = existingEntry ? isSessionFresh(existingEntry.updatedAt, config.reset, this.nowMs()) : false;
40
+ const workingPathChanged = Boolean(existingEntry?.workingPath && existingEntry.workingPath !== workingPath);
41
+ const isNewSession = request.forceNew || !existingEntry || !fresh || workingPathChanged;
42
+ const sessionId = isNewSession ? newSessionId() : existingEntry.sessionId;
43
+ const transcriptPath = isNewSession
44
+ ? this.pathPort.join(resolveSessionsDir(paths, normalizedAgentId), `${sessionId}.jsonl`)
45
+ : existingEntry?.transcriptFile?.trim() ||
46
+ this.pathPort.join(resolveSessionsDir(paths, normalizedAgentId), `${sessionId}.jsonl`);
47
+ const baseEntry = isNewSession ? {} : existingEntry ?? {};
48
+ const nextEntry = {
49
+ ...baseEntry,
50
+ sessionId,
51
+ updatedAt: this.nowMs(),
52
+ transcriptFile: transcriptPath,
53
+ workspacePath,
54
+ workingPath
55
+ };
56
+ store.sessions[sessionKey] = nextEntry;
57
+ await this.persistStore(paths, normalizedAgentId, store);
58
+ await this.ensureTranscriptHeader({
59
+ transcriptPath,
60
+ agentId: normalizedAgentId,
61
+ sessionId,
62
+ sessionKey,
63
+ workspacePath,
64
+ workingPath
65
+ });
66
+ const compaction = await this.compactSessionInternal({
67
+ paths,
68
+ agentId: normalizedAgentId,
69
+ sessionKey,
70
+ force: false,
71
+ config,
72
+ store
73
+ });
74
+ const contextPrompt = await this.buildSessionContext({
75
+ transcriptPath,
76
+ config,
77
+ sessionKey,
78
+ sessionId,
79
+ workspacePath,
80
+ workingPath
81
+ });
82
+ await this.appendMessage({
83
+ paths,
84
+ agentId: normalizedAgentId,
85
+ sessionKey,
86
+ role: "user",
87
+ content: request.userMessage
88
+ });
89
+ return {
90
+ enabled: true,
91
+ info: {
92
+ agentId: normalizedAgentId,
93
+ sessionId,
94
+ sessionKey,
95
+ transcriptPath,
96
+ workspacePath,
97
+ workingPath,
98
+ isNewSession
99
+ },
100
+ contextPrompt,
101
+ compactionApplied: compaction.applied
102
+ };
103
+ }
104
+ async recordAssistantReply(paths, info, content) {
105
+ await this.appendMessage({
106
+ paths,
107
+ agentId: info.agentId,
108
+ sessionKey: info.sessionKey,
109
+ role: "assistant",
110
+ content
111
+ });
112
+ const config = await this.readSessionConfig(paths, info.agentId);
113
+ const store = await this.readStore(paths, info.agentId);
114
+ const compaction = await this.compactSessionInternal({
115
+ paths,
116
+ agentId: info.agentId,
117
+ sessionKey: info.sessionKey,
118
+ force: false,
119
+ config,
120
+ store
121
+ });
122
+ return compaction;
123
+ }
124
+ async listSessions(paths, agentId, options = {}) {
125
+ const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
126
+ const store = await this.readStore(paths, normalizedAgentId);
127
+ const now = this.nowMs();
128
+ const activeWindowMs = typeof options.activeMinutes === "number" && Number.isFinite(options.activeMinutes) && options.activeMinutes > 0
129
+ ? Math.floor(options.activeMinutes) * 60_000
130
+ : undefined;
131
+ const summaries = Object.entries(store.sessions)
132
+ .map(([sessionKey, entry]) => toSessionSummary({
133
+ paths,
134
+ pathPort: this.pathPort,
135
+ agentId: normalizedAgentId,
136
+ sessionKey,
137
+ entry
138
+ }))
139
+ .filter((entry) => {
140
+ if (!activeWindowMs) {
141
+ return true;
142
+ }
143
+ return now - entry.updatedAt <= activeWindowMs;
144
+ })
145
+ .sort((left, right) => right.updatedAt - left.updatedAt);
146
+ return summaries;
147
+ }
148
+ async renameSession(paths, agentId, title, sessionRef) {
149
+ const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
150
+ const config = await this.readSessionConfig(paths, normalizedAgentId);
151
+ const store = await this.readStore(paths, normalizedAgentId);
152
+ const sessionKey = resolveSessionKey({
153
+ agentId: normalizedAgentId,
154
+ mainKey: config.mainKey,
155
+ sessions: store.sessions,
156
+ reference: sessionRef
157
+ });
158
+ const entry = store.sessions[sessionKey];
159
+ if (!entry) {
160
+ throw new Error(`Session not found: ${sessionRef ?? sessionKey}`);
161
+ }
162
+ const updatedAt = this.nowMs();
163
+ const nextEntry = {
164
+ ...entry,
165
+ title: normalizeSessionTitle(title),
166
+ updatedAt
167
+ };
168
+ store.sessions[sessionKey] = nextEntry;
169
+ await this.persistStore(paths, normalizedAgentId, store);
170
+ return toSessionSummary({
171
+ paths,
172
+ pathPort: this.pathPort,
173
+ agentId: normalizedAgentId,
174
+ sessionKey,
175
+ entry: nextEntry
176
+ });
177
+ }
178
+ async removeSession(paths, agentId, sessionRef) {
179
+ const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
180
+ const config = await this.readSessionConfig(paths, normalizedAgentId);
181
+ const store = await this.readStore(paths, normalizedAgentId);
182
+ const sessionKey = resolveSessionKey({
183
+ agentId: normalizedAgentId,
184
+ mainKey: config.mainKey,
185
+ sessions: store.sessions,
186
+ reference: sessionRef
187
+ });
188
+ const entry = store.sessions[sessionKey];
189
+ if (!entry) {
190
+ throw new Error(`Session not found: ${sessionRef ?? sessionKey}`);
191
+ }
192
+ delete store.sessions[sessionKey];
193
+ await this.persistStore(paths, normalizedAgentId, store);
194
+ return {
195
+ sessionKey,
196
+ sessionId: entry.sessionId,
197
+ title: resolveSessionTitle(sessionKey, entry.title),
198
+ transcriptPath: entry.transcriptFile?.trim() ||
199
+ this.pathPort.join(resolveSessionsDir(paths, normalizedAgentId), `${entry.sessionId}.jsonl`)
200
+ };
201
+ }
202
+ async getSessionHistory(paths, agentId, options = {}) {
203
+ const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
204
+ const config = await this.readSessionConfig(paths, normalizedAgentId);
205
+ const store = await this.readStore(paths, normalizedAgentId);
206
+ const sessionKey = resolveSessionKey({
207
+ agentId: normalizedAgentId,
208
+ mainKey: config.mainKey,
209
+ sessions: store.sessions,
210
+ reference: options.sessionRef
211
+ });
212
+ const entry = store.sessions[sessionKey];
213
+ if (!entry) {
214
+ return { sessionKey, messages: [] };
215
+ }
216
+ const transcriptPath = entry.transcriptFile?.trim() || this.pathPort.join(resolveSessionsDir(paths, normalizedAgentId), `${entry.sessionId}.jsonl`);
217
+ const records = await this.readTranscriptRecords(transcriptPath);
218
+ let messages = records
219
+ .filter((record) => isSessionTranscriptMessage(record) || isSessionTranscriptCompaction(record))
220
+ .map((record) => {
221
+ if (isSessionTranscriptCompaction(record)) {
222
+ return {
223
+ type: "compaction",
224
+ content: record.summary,
225
+ timestamp: record.timestamp
226
+ };
227
+ }
228
+ return {
229
+ type: "message",
230
+ role: record.role,
231
+ content: record.content,
232
+ timestamp: record.timestamp
233
+ };
234
+ });
235
+ if (!options.includeCompaction) {
236
+ messages = messages.filter((message) => message.type !== "compaction");
237
+ }
238
+ if (typeof options.limit === "number" && Number.isFinite(options.limit) && options.limit > 0) {
239
+ messages = messages.slice(-Math.floor(options.limit));
240
+ }
241
+ return {
242
+ sessionKey,
243
+ sessionId: entry.sessionId,
244
+ transcriptPath,
245
+ messages
246
+ };
247
+ }
248
+ async resetSession(paths, agentId, sessionRef) {
249
+ const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
250
+ const workspacePath = this.pathPort.join(paths.workspacesDir, normalizedAgentId);
251
+ const config = await this.readSessionConfig(paths, normalizedAgentId);
252
+ const store = await this.readStore(paths, normalizedAgentId);
253
+ const sessionKey = resolveSessionKey({
254
+ agentId: normalizedAgentId,
255
+ mainKey: config.mainKey,
256
+ sessions: store.sessions,
257
+ reference: sessionRef
258
+ });
259
+ const existingEntry = store.sessions[sessionKey];
260
+ const sessionId = newSessionId();
261
+ const workingPath = existingEntry?.workingPath?.trim() || process.cwd();
262
+ const transcriptPath = this.pathPort.join(resolveSessionsDir(paths, normalizedAgentId), `${sessionId}.jsonl`);
263
+ store.sessions[sessionKey] = {
264
+ sessionId,
265
+ updatedAt: this.nowMs(),
266
+ transcriptFile: transcriptPath,
267
+ workspacePath,
268
+ workingPath
269
+ };
270
+ await this.persistStore(paths, normalizedAgentId, store);
271
+ await this.ensureTranscriptHeader({
272
+ transcriptPath,
273
+ agentId: normalizedAgentId,
274
+ sessionId,
275
+ sessionKey,
276
+ workspacePath,
277
+ workingPath
278
+ });
279
+ return {
280
+ agentId: normalizedAgentId,
281
+ sessionId,
282
+ sessionKey,
283
+ transcriptPath,
284
+ workspacePath,
285
+ workingPath,
286
+ isNewSession: true
287
+ };
288
+ }
289
+ async compactSession(paths, agentId, sessionRef) {
290
+ const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
291
+ const config = await this.readSessionConfig(paths, normalizedAgentId);
292
+ const store = await this.readStore(paths, normalizedAgentId);
293
+ const sessionKey = resolveSessionKey({
294
+ agentId: normalizedAgentId,
295
+ mainKey: config.mainKey,
296
+ sessions: store.sessions,
297
+ reference: sessionRef
298
+ });
299
+ return this.compactSessionInternal({
300
+ paths,
301
+ agentId: normalizedAgentId,
302
+ sessionKey,
303
+ force: true,
304
+ config,
305
+ store
306
+ });
307
+ }
308
+ async compactSessionInternal(params) {
309
+ const entry = params.store.sessions[params.sessionKey];
310
+ if (!entry) {
311
+ const sessionId = newSessionId();
312
+ const transcriptPath = this.pathPort.join(resolveSessionsDir(params.paths, params.agentId), `${sessionId}.jsonl`);
313
+ return {
314
+ sessionKey: params.sessionKey,
315
+ sessionId,
316
+ transcriptPath,
317
+ applied: false,
318
+ compactedMessages: 0
319
+ };
320
+ }
321
+ const transcriptPath = entry.transcriptFile?.trim() || this.pathPort.join(resolveSessionsDir(params.paths, params.agentId), `${entry.sessionId}.jsonl`);
322
+ const records = await this.readTranscriptRecords(transcriptPath);
323
+ const header = ensureHeaderRecord(records, {
324
+ sessionId: entry.sessionId,
325
+ sessionKey: params.sessionKey,
326
+ agentId: params.agentId,
327
+ nowIso: this.nowIso(),
328
+ workspacePath: entry.workspacePath?.trim() || this.pathPort.join(params.paths.workspacesDir, params.agentId),
329
+ workingPath: entry.workingPath?.trim() || process.cwd()
330
+ });
331
+ const messages = records.filter(isSessionTranscriptMessage);
332
+ const messageChars = messages.reduce((total, message) => total + message.content.length, 0);
333
+ const trigger = params.config.compaction;
334
+ if (!params.force) {
335
+ if (!trigger.enabled) {
336
+ return {
337
+ sessionKey: params.sessionKey,
338
+ sessionId: entry.sessionId,
339
+ transcriptPath,
340
+ applied: false,
341
+ compactedMessages: 0
342
+ };
343
+ }
344
+ if (messages.length < trigger.triggerMessageCount && messageChars < trigger.triggerChars) {
345
+ return {
346
+ sessionKey: params.sessionKey,
347
+ sessionId: entry.sessionId,
348
+ transcriptPath,
349
+ applied: false,
350
+ compactedMessages: 0
351
+ };
352
+ }
353
+ }
354
+ const keepRecentMessages = clampPositive(trigger.keepRecentMessages, 1);
355
+ if (messages.length <= keepRecentMessages) {
356
+ return {
357
+ sessionKey: params.sessionKey,
358
+ sessionId: entry.sessionId,
359
+ transcriptPath,
360
+ applied: false,
361
+ compactedMessages: 0
362
+ };
363
+ }
364
+ const compactedMessages = messages.slice(0, messages.length - keepRecentMessages);
365
+ const keptMessages = messages.slice(-keepRecentMessages);
366
+ const summary = summarizeCompactedMessages(compactedMessages, trigger.summaryMaxChars);
367
+ const existingCompactions = records.filter(isSessionTranscriptCompaction).slice(-3);
368
+ const nextCompaction = {
369
+ type: "compaction",
370
+ summary,
371
+ compactedMessages: compactedMessages.length,
372
+ keptMessages: keptMessages.length,
373
+ timestamp: this.nowMs()
374
+ };
375
+ const nextRecords = [header, ...existingCompactions, nextCompaction, ...keptMessages];
376
+ await this.writeTranscriptRecords(transcriptPath, nextRecords);
377
+ params.store.sessions[params.sessionKey] = {
378
+ ...entry,
379
+ transcriptFile: transcriptPath,
380
+ updatedAt: this.nowMs(),
381
+ compactionCount: (entry.compactionCount ?? 0) + 1
382
+ };
383
+ await this.persistStore(params.paths, params.agentId, params.store);
384
+ return {
385
+ sessionKey: params.sessionKey,
386
+ sessionId: entry.sessionId,
387
+ transcriptPath,
388
+ applied: true,
389
+ summary,
390
+ compactedMessages: compactedMessages.length
391
+ };
392
+ }
393
+ async appendMessage(params) {
394
+ const normalizedContent = params.content.trim();
395
+ if (!normalizedContent) {
396
+ return;
397
+ }
398
+ const store = await this.readStore(params.paths, params.agentId);
399
+ const entry = store.sessions[params.sessionKey];
400
+ if (!entry) {
401
+ return;
402
+ }
403
+ const transcriptPath = entry.transcriptFile?.trim() ||
404
+ this.pathPort.join(resolveSessionsDir(params.paths, params.agentId), `${entry.sessionId}.jsonl`);
405
+ const records = await this.readTranscriptRecords(transcriptPath);
406
+ const header = ensureHeaderRecord(records, {
407
+ sessionId: entry.sessionId,
408
+ sessionKey: params.sessionKey,
409
+ agentId: params.agentId,
410
+ nowIso: this.nowIso(),
411
+ workspacePath: entry.workspacePath?.trim() || this.pathPort.join(params.paths.workspacesDir, params.agentId),
412
+ workingPath: entry.workingPath?.trim() || process.cwd()
413
+ });
414
+ const message = {
415
+ type: "message",
416
+ role: params.role,
417
+ content: normalizedContent,
418
+ timestamp: this.nowMs()
419
+ };
420
+ const nextRecords = [header, ...records.filter((record) => !isSessionTranscriptHeader(record)), message];
421
+ await this.writeTranscriptRecords(transcriptPath, nextRecords);
422
+ const inputChars = params.role === "user" ? (entry.inputChars ?? 0) + normalizedContent.length : entry.inputChars ?? 0;
423
+ const outputChars = params.role === "assistant" ? (entry.outputChars ?? 0) + normalizedContent.length : entry.outputChars ?? 0;
424
+ store.sessions[params.sessionKey] = {
425
+ ...entry,
426
+ transcriptFile: transcriptPath,
427
+ updatedAt: this.nowMs(),
428
+ inputChars,
429
+ outputChars,
430
+ totalChars: inputChars + outputChars
431
+ };
432
+ await this.persistStore(params.paths, params.agentId, store);
433
+ }
434
+ async buildSessionContext(params) {
435
+ const records = await this.readTranscriptRecords(params.transcriptPath);
436
+ const history = records
437
+ .filter((record) => isSessionTranscriptMessage(record) || isSessionTranscriptCompaction(record))
438
+ .map((record) => {
439
+ if (isSessionTranscriptCompaction(record)) {
440
+ return {
441
+ label: "COMPACTION",
442
+ timestamp: record.timestamp,
443
+ content: record.summary
444
+ };
445
+ }
446
+ return {
447
+ label: record.role.toUpperCase(),
448
+ timestamp: record.timestamp,
449
+ content: record.content
450
+ };
451
+ });
452
+ const pruned = pruneHistory(history, params.config);
453
+ if (pruned.length === 0) {
454
+ return "";
455
+ }
456
+ const lines = [
457
+ `Session key: ${params.sessionKey}`,
458
+ `Session id: ${params.sessionId}`,
459
+ `Workspace path: ${params.workspacePath}`,
460
+ `Working path: ${params.workingPath}`,
461
+ "",
462
+ "Recent session history:",
463
+ ...pruned.map((entry) => `[${entry.label} ${new Date(entry.timestamp).toISOString()}] ${entry.content}`)
464
+ ];
465
+ const rendered = lines.join("\n");
466
+ return clampText(rendered, params.config.contextMaxChars);
467
+ }
468
+ async ensureTranscriptHeader(params) {
469
+ const records = await this.readTranscriptRecords(params.transcriptPath);
470
+ const header = ensureHeaderRecord(records, {
471
+ sessionId: params.sessionId,
472
+ sessionKey: params.sessionKey,
473
+ agentId: params.agentId,
474
+ nowIso: this.nowIso(),
475
+ workspacePath: params.workspacePath,
476
+ workingPath: params.workingPath
477
+ });
478
+ const first = records[0];
479
+ if (!first || !isSessionTranscriptHeader(first)) {
480
+ const next = [header, ...records.filter((record) => !isSessionTranscriptHeader(record))];
481
+ await this.writeTranscriptRecords(params.transcriptPath, next);
482
+ }
483
+ }
484
+ async readStore(paths, agentId) {
485
+ const sessionsDir = resolveSessionsDir(paths, agentId);
486
+ const storePath = this.pathPort.join(sessionsDir, "sessions.json");
487
+ const exists = await this.fileSystem.exists(storePath);
488
+ if (!exists) {
489
+ return {
490
+ schemaVersion: SESSION_STORE_SCHEMA_VERSION,
491
+ sessions: {}
492
+ };
493
+ }
494
+ let parsed;
495
+ try {
496
+ parsed = JSON.parse(await this.fileSystem.readFile(storePath));
497
+ }
498
+ catch {
499
+ throw new SessionStoreParseError(storePath);
500
+ }
501
+ if (!parsed || typeof parsed !== "object") {
502
+ throw new SessionStoreParseError(storePath);
503
+ }
504
+ const record = parsed;
505
+ if (record.schemaVersion === SESSION_STORE_SCHEMA_VERSION && isSessionEntryMap(record.sessions)) {
506
+ return {
507
+ schemaVersion: SESSION_STORE_SCHEMA_VERSION,
508
+ sessions: record.sessions
509
+ };
510
+ }
511
+ if (isSessionEntryMap(parsed)) {
512
+ return {
513
+ schemaVersion: SESSION_STORE_SCHEMA_VERSION,
514
+ sessions: parsed
515
+ };
516
+ }
517
+ throw new SessionStoreParseError(storePath);
518
+ }
519
+ async persistStore(paths, agentId, store) {
520
+ const sessionsDir = resolveSessionsDir(paths, agentId);
521
+ const storePath = this.pathPort.join(sessionsDir, "sessions.json");
522
+ await this.fileSystem.ensureDir(sessionsDir);
523
+ await this.fileSystem.writeFile(storePath, `${JSON.stringify(store, null, 2)}\n`);
524
+ }
525
+ async readTranscriptRecords(transcriptPath) {
526
+ const exists = await this.fileSystem.exists(transcriptPath);
527
+ if (!exists) {
528
+ return [];
529
+ }
530
+ const raw = await this.fileSystem.readFile(transcriptPath);
531
+ const lines = raw
532
+ .split("\n")
533
+ .map((line) => line.trim())
534
+ .filter(Boolean);
535
+ if (lines.length === 0) {
536
+ return [];
537
+ }
538
+ const records = [];
539
+ for (const line of lines) {
540
+ let parsed;
541
+ try {
542
+ parsed = JSON.parse(line);
543
+ }
544
+ catch {
545
+ throw new SessionTranscriptParseError(transcriptPath);
546
+ }
547
+ const normalized = normalizeTranscriptRecord(parsed);
548
+ if (normalized) {
549
+ records.push(normalized);
550
+ }
551
+ }
552
+ return records;
553
+ }
554
+ async writeTranscriptRecords(transcriptPath, records) {
555
+ await this.fileSystem.ensureDir(path.dirname(transcriptPath));
556
+ const payload = records.map((record) => JSON.stringify(record)).join("\n");
557
+ await this.fileSystem.writeFile(transcriptPath, payload ? `${payload}\n` : "");
558
+ }
559
+ async readSessionConfig(paths, agentId) {
560
+ const configPath = this.pathPort.join(paths.agentsDir, agentId, "config.json");
561
+ const exists = await this.fileSystem.exists(configPath);
562
+ if (!exists) {
563
+ return DEFAULT_SESSION_CONFIG;
564
+ }
565
+ let parsed;
566
+ try {
567
+ parsed = JSON.parse(await this.fileSystem.readFile(configPath));
568
+ }
569
+ catch {
570
+ throw new SessionConfigParseError(configPath);
571
+ }
572
+ const sessionConfigRaw = parsed?.runtime?.sessions;
573
+ return mergeSessionConfig(DEFAULT_SESSION_CONFIG, sessionConfigRaw);
574
+ }
575
+ async ensureWorkingPathGitRepository(workingPath) {
576
+ if (!this.commandRunner) {
577
+ return;
578
+ }
579
+ if (!(await this.fileSystem.exists(workingPath))) {
580
+ return;
581
+ }
582
+ try {
583
+ const probe = await this.commandRunner.run({
584
+ command: "git",
585
+ args: ["rev-parse", "--is-inside-work-tree"],
586
+ cwd: workingPath
587
+ });
588
+ if (probe.code === 0) {
589
+ return;
590
+ }
591
+ await this.commandRunner.run({
592
+ command: "git",
593
+ args: ["init", "--quiet"],
594
+ cwd: workingPath
595
+ });
596
+ }
597
+ catch {
598
+ // Git tooling might not be installed; session setup remains functional without VCS bootstrap.
599
+ }
600
+ }
601
+ }
602
+ function normalizeTranscriptRecord(value) {
603
+ if (!value || typeof value !== "object") {
604
+ return null;
605
+ }
606
+ const record = value;
607
+ if (record.type === "session") {
608
+ if (record.schemaVersion === SESSION_TRANSCRIPT_SCHEMA_VERSION &&
609
+ typeof record.sessionId === "string" &&
610
+ typeof record.sessionKey === "string" &&
611
+ typeof record.agentId === "string" &&
612
+ typeof record.createdAt === "string") {
613
+ return {
614
+ type: "session",
615
+ schemaVersion: SESSION_TRANSCRIPT_SCHEMA_VERSION,
616
+ sessionId: record.sessionId,
617
+ sessionKey: record.sessionKey,
618
+ agentId: record.agentId,
619
+ createdAt: record.createdAt,
620
+ workspacePath: typeof record.workspacePath === "string" ? record.workspacePath : undefined,
621
+ workingPath: typeof record.workingPath === "string" ? record.workingPath : undefined
622
+ };
623
+ }
624
+ return null;
625
+ }
626
+ if (record.type === "message") {
627
+ if ((record.role === "user" || record.role === "assistant" || record.role === "system") &&
628
+ typeof record.content === "string" &&
629
+ typeof record.timestamp === "number" &&
630
+ Number.isFinite(record.timestamp)) {
631
+ return {
632
+ type: "message",
633
+ role: record.role,
634
+ content: record.content,
635
+ timestamp: record.timestamp
636
+ };
637
+ }
638
+ return null;
639
+ }
640
+ if (record.type === "compaction") {
641
+ if (typeof record.summary === "string" &&
642
+ typeof record.timestamp === "number" &&
643
+ Number.isFinite(record.timestamp) &&
644
+ typeof record.compactedMessages === "number" &&
645
+ Number.isFinite(record.compactedMessages) &&
646
+ typeof record.keptMessages === "number" &&
647
+ Number.isFinite(record.keptMessages)) {
648
+ return {
649
+ type: "compaction",
650
+ summary: record.summary,
651
+ compactedMessages: record.compactedMessages,
652
+ keptMessages: record.keptMessages,
653
+ timestamp: record.timestamp
654
+ };
655
+ }
656
+ return null;
657
+ }
658
+ return null;
659
+ }
660
+ function ensureHeaderRecord(records, params) {
661
+ const existing = records.find(isSessionTranscriptHeader);
662
+ if (existing) {
663
+ return existing;
664
+ }
665
+ return {
666
+ type: "session",
667
+ schemaVersion: SESSION_TRANSCRIPT_SCHEMA_VERSION,
668
+ sessionId: params.sessionId,
669
+ sessionKey: params.sessionKey,
670
+ agentId: params.agentId,
671
+ createdAt: params.nowIso,
672
+ workspacePath: params.workspacePath,
673
+ workingPath: params.workingPath
674
+ };
675
+ }
676
+ function resolveSessionsDir(paths, agentId) {
677
+ return path.join(paths.agentsDir, agentId, "sessions");
678
+ }
679
+ function resolveSessionKey(params) {
680
+ const mainKey = buildMainSessionKey(params.agentId, params.mainKey);
681
+ const reference = params.reference?.trim().toLowerCase();
682
+ if (!reference) {
683
+ return mainKey;
684
+ }
685
+ if (reference === "main") {
686
+ return mainKey;
687
+ }
688
+ const byId = Object.entries(params.sessions).find(([, entry]) => entry.sessionId === reference);
689
+ if (byId) {
690
+ return byId[0];
691
+ }
692
+ if (params.sessions[reference]) {
693
+ return reference;
694
+ }
695
+ if (reference.includes(":")) {
696
+ return reference;
697
+ }
698
+ return `agent:${params.agentId}:${normalizeSessionSegment(reference) || "main"}`;
699
+ }
700
+ function buildMainSessionKey(agentId, mainKey) {
701
+ const normalizedMainKey = normalizeSessionSegment(mainKey) || "main";
702
+ return `agent:${normalizeAgentId(agentId) || DEFAULT_AGENT_ID}:${normalizedMainKey}`;
703
+ }
704
+ function normalizeSessionSegment(value) {
705
+ return value
706
+ .trim()
707
+ .toLowerCase()
708
+ .replace(/[^a-z0-9-]+/g, "-")
709
+ .replace(/^-+|-+$/g, "");
710
+ }
711
+ function isSessionFresh(updatedAt, reset, nowMs) {
712
+ const staleDaily = reset.mode === "daily" ? updatedAt < resolveMostRecentDailyReset(nowMs, normalizeHour(reset.atHour)) : false;
713
+ const idleMinutes = resolveIdleMinutes(reset);
714
+ const staleIdle = idleMinutes !== undefined ? nowMs > updatedAt + idleMinutes * 60_000 : false;
715
+ return !(staleDaily || staleIdle);
716
+ }
717
+ function resolveMostRecentDailyReset(nowMs, atHour) {
718
+ const resetAt = new Date(nowMs);
719
+ resetAt.setHours(atHour, 0, 0, 0);
720
+ if (nowMs < resetAt.getTime()) {
721
+ resetAt.setDate(resetAt.getDate() - 1);
722
+ }
723
+ return resetAt.getTime();
724
+ }
725
+ function normalizeHour(value) {
726
+ if (typeof value !== "number" || !Number.isFinite(value)) {
727
+ return DEFAULT_SESSION_CONFIG.reset.atHour;
728
+ }
729
+ return Math.min(23, Math.max(0, Math.floor(value)));
730
+ }
731
+ function resolveIdleMinutes(reset) {
732
+ if (typeof reset.idleMinutes === "number" && Number.isFinite(reset.idleMinutes) && reset.idleMinutes > 0) {
733
+ return Math.floor(reset.idleMinutes);
734
+ }
735
+ if (reset.mode === "idle") {
736
+ return 60;
737
+ }
738
+ return undefined;
739
+ }
740
+ function mergeSessionConfig(base, override) {
741
+ if (!override) {
742
+ return base;
743
+ }
744
+ const reset = override.reset ?? {};
745
+ const pruning = override.pruning ?? {};
746
+ const compaction = override.compaction ?? {};
747
+ return {
748
+ mainKey: normalizeSessionSegment(override.mainKey ?? base.mainKey) || base.mainKey,
749
+ contextMaxChars: clampPositive(override.contextMaxChars ?? base.contextMaxChars, base.contextMaxChars),
750
+ reset: {
751
+ mode: reset.mode === "idle" ? "idle" : reset.mode === "daily" ? "daily" : base.reset.mode,
752
+ atHour: normalizeHour(reset.atHour ?? base.reset.atHour),
753
+ idleMinutes: typeof reset.idleMinutes === "number" && Number.isFinite(reset.idleMinutes) && reset.idleMinutes > 0
754
+ ? Math.floor(reset.idleMinutes)
755
+ : base.reset.idleMinutes
756
+ },
757
+ pruning: {
758
+ enabled: pruning.enabled ?? base.pruning.enabled,
759
+ maxMessages: clampPositive(pruning.maxMessages ?? base.pruning.maxMessages, base.pruning.maxMessages),
760
+ maxChars: clampPositive(pruning.maxChars ?? base.pruning.maxChars, base.pruning.maxChars),
761
+ keepRecentMessages: clampPositive(pruning.keepRecentMessages ?? base.pruning.keepRecentMessages, base.pruning.keepRecentMessages)
762
+ },
763
+ compaction: {
764
+ enabled: compaction.enabled ?? base.compaction.enabled,
765
+ triggerMessageCount: clampPositive(compaction.triggerMessageCount ?? base.compaction.triggerMessageCount, base.compaction.triggerMessageCount),
766
+ triggerChars: clampPositive(compaction.triggerChars ?? base.compaction.triggerChars, base.compaction.triggerChars),
767
+ keepRecentMessages: clampPositive(compaction.keepRecentMessages ?? base.compaction.keepRecentMessages, base.compaction.keepRecentMessages),
768
+ summaryMaxChars: clampPositive(compaction.summaryMaxChars ?? base.compaction.summaryMaxChars, base.compaction.summaryMaxChars)
769
+ }
770
+ };
771
+ }
772
+ function clampPositive(value, fallback) {
773
+ if (!Number.isFinite(value) || value <= 0) {
774
+ return fallback;
775
+ }
776
+ return Math.floor(value);
777
+ }
778
+ function pruneHistory(history, config) {
779
+ if (history.length === 0) {
780
+ return [];
781
+ }
782
+ if (!config.pruning.enabled) {
783
+ return history;
784
+ }
785
+ const maxMessages = Math.max(config.pruning.maxMessages, config.pruning.keepRecentMessages);
786
+ const keepRecent = Math.max(1, config.pruning.keepRecentMessages);
787
+ let selected = history.slice(-maxMessages);
788
+ while (selected.length > keepRecent && estimateHistoryChars(selected) > config.pruning.maxChars) {
789
+ selected = selected.slice(1);
790
+ }
791
+ return selected.map((entry) => ({
792
+ ...entry,
793
+ content: clampText(entry.content, 1500)
794
+ }));
795
+ }
796
+ function estimateHistoryChars(history) {
797
+ return history.reduce((total, entry) => total + entry.label.length + entry.content.length + 32, 0);
798
+ }
799
+ function summarizeCompactedMessages(messages, maxChars) {
800
+ const lines = ["Compaction summary of earlier messages:"];
801
+ for (const message of messages) {
802
+ const flattened = message.content.replace(/\s+/g, " ").trim();
803
+ if (!flattened) {
804
+ continue;
805
+ }
806
+ lines.push(`- ${message.role}: ${flattened}`);
807
+ const rendered = lines.join("\n");
808
+ if (rendered.length >= maxChars) {
809
+ break;
810
+ }
811
+ }
812
+ return clampText(lines.join("\n"), maxChars);
813
+ }
814
+ function clampText(value, maxChars) {
815
+ if (value.length <= maxChars) {
816
+ return value;
817
+ }
818
+ const marker = "\n...[truncated]...\n";
819
+ const tailChars = Math.max(64, maxChars - marker.length);
820
+ return `${value.slice(-tailChars).trimStart()}${marker}`;
821
+ }
822
+ function newSessionId() {
823
+ return randomUUID().toLowerCase();
824
+ }
825
+ function resolveWorkingPath(input) {
826
+ const normalized = input?.trim();
827
+ if (normalized) {
828
+ return path.resolve(normalized);
829
+ }
830
+ return process.cwd();
831
+ }
832
+ function normalizeSessionTitle(input) {
833
+ const value = input.trim();
834
+ if (!value) {
835
+ throw new Error("Session title cannot be empty.");
836
+ }
837
+ if (value.length <= 120) {
838
+ return value;
839
+ }
840
+ return `${value.slice(0, 117)}...`;
841
+ }
842
+ function resolveSessionTitle(sessionKey, title) {
843
+ const explicit = title?.trim();
844
+ if (explicit) {
845
+ return explicit;
846
+ }
847
+ const segment = sessionKey.split(":").at(-1)?.trim() || "session";
848
+ const normalized = segment.replace(/[-_]+/g, " ").trim();
849
+ if (!normalized) {
850
+ return "Session";
851
+ }
852
+ return normalized.charAt(0).toUpperCase() + normalized.slice(1);
853
+ }
854
+ function toSessionSummary(params) {
855
+ return {
856
+ sessionKey: params.sessionKey,
857
+ sessionId: params.entry.sessionId,
858
+ title: resolveSessionTitle(params.sessionKey, params.entry.title),
859
+ updatedAt: params.entry.updatedAt,
860
+ transcriptPath: params.entry.transcriptFile?.trim() ||
861
+ params.pathPort.join(resolveSessionsDir(params.paths, params.agentId), `${params.entry.sessionId}.jsonl`),
862
+ workspacePath: params.entry.workspacePath?.trim() ||
863
+ params.pathPort.join(params.paths.workspacesDir, params.agentId),
864
+ workingPath: params.entry.workingPath?.trim() || undefined,
865
+ inputChars: params.entry.inputChars ?? 0,
866
+ outputChars: params.entry.outputChars ?? 0,
867
+ totalChars: params.entry.totalChars ?? 0,
868
+ compactionCount: params.entry.compactionCount ?? 0
869
+ };
870
+ }
871
+ function isSessionEntryMap(value) {
872
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
873
+ return false;
874
+ }
875
+ return Object.values(value).every((entry) => {
876
+ if (!entry || typeof entry !== "object") {
877
+ return false;
878
+ }
879
+ const record = entry;
880
+ if (typeof record.sessionId !== "string" || typeof record.updatedAt !== "number") {
881
+ return false;
882
+ }
883
+ if (record.workspacePath !== undefined && typeof record.workspacePath !== "string") {
884
+ return false;
885
+ }
886
+ if (record.workingPath !== undefined && typeof record.workingPath !== "string") {
887
+ return false;
888
+ }
889
+ if (record.title !== undefined && typeof record.title !== "string") {
890
+ return false;
891
+ }
892
+ return true;
893
+ });
894
+ }
895
+ //# sourceMappingURL=session.service.js.map