@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.
- package/LICENSE +21 -0
- package/README.md +20 -0
- package/dist/apps/runtime/create-opengoat-runtime.d.ts +14 -0
- package/dist/apps/runtime/create-opengoat-runtime.js +23 -0
- package/dist/apps/runtime/create-opengoat-runtime.js.map +1 -0
- package/dist/core/acp/application/acp-agent.d.ts +60 -0
- package/dist/core/acp/application/acp-agent.js +424 -0
- package/dist/core/acp/application/acp-agent.js.map +1 -0
- package/dist/core/acp/application/session-store.d.ts +13 -0
- package/dist/core/acp/application/session-store.js +40 -0
- package/dist/core/acp/application/session-store.js.map +1 -0
- package/dist/core/acp/domain/meta.d.ts +7 -0
- package/dist/core/acp/domain/meta.js +31 -0
- package/dist/core/acp/domain/meta.js.map +1 -0
- package/dist/core/acp/domain/session.d.ts +23 -0
- package/dist/core/acp/domain/session.js +2 -0
- package/dist/core/acp/domain/session.js.map +1 -0
- package/dist/core/acp/index.d.ts +5 -0
- package/dist/core/acp/index.js +4 -0
- package/dist/core/acp/index.js.map +1 -0
- package/dist/core/agents/application/agent-manifest.service.d.ts +20 -0
- package/dist/core/agents/application/agent-manifest.service.js +89 -0
- package/dist/core/agents/application/agent-manifest.service.js.map +1 -0
- package/dist/core/agents/application/agent.service.d.ts +25 -0
- package/dist/core/agents/application/agent.service.js +191 -0
- package/dist/core/agents/application/agent.service.js.map +1 -0
- package/dist/core/agents/application/workspace-context.service.d.ts +28 -0
- package/dist/core/agents/application/workspace-context.service.js +157 -0
- package/dist/core/agents/application/workspace-context.service.js.map +1 -0
- package/dist/core/agents/domain/agent-manifest.d.ts +37 -0
- package/dist/core/agents/domain/agent-manifest.js +228 -0
- package/dist/core/agents/domain/agent-manifest.js.map +1 -0
- package/dist/core/agents/domain/workspace-context.d.ts +13 -0
- package/dist/core/agents/domain/workspace-context.js +14 -0
- package/dist/core/agents/domain/workspace-context.js.map +1 -0
- package/dist/core/agents/index.d.ts +6 -0
- package/dist/core/agents/index.js +6 -0
- package/dist/core/agents/index.js.map +1 -0
- package/dist/core/bootstrap/application/bootstrap.service.d.ts +24 -0
- package/dist/core/bootstrap/application/bootstrap.service.js +108 -0
- package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -0
- package/dist/core/bootstrap/index.d.ts +1 -0
- package/dist/core/bootstrap/index.js +2 -0
- package/dist/core/bootstrap/index.js.map +1 -0
- package/dist/core/domain/agent-id.d.ts +3 -0
- package/dist/core/domain/agent-id.js +12 -0
- package/dist/core/domain/agent-id.js.map +1 -0
- package/dist/core/domain/agent.d.ts +39 -0
- package/dist/core/domain/agent.js +2 -0
- package/dist/core/domain/agent.js.map +1 -0
- package/dist/core/domain/opengoat-paths.d.ts +29 -0
- package/dist/core/domain/opengoat-paths.js +2 -0
- package/dist/core/domain/opengoat-paths.js.map +1 -0
- package/dist/core/gateway/domain/protocol.d.ts +113 -0
- package/dist/core/gateway/domain/protocol.js +394 -0
- package/dist/core/gateway/domain/protocol.js.map +1 -0
- package/dist/core/gateway/index.d.ts +2 -0
- package/dist/core/gateway/index.js +2 -0
- package/dist/core/gateway/index.js.map +1 -0
- package/dist/core/llm/application/vercel-ai-text-runtime.d.ts +26 -0
- package/dist/core/llm/application/vercel-ai-text-runtime.js +223 -0
- package/dist/core/llm/application/vercel-ai-text-runtime.js.map +1 -0
- package/dist/core/llm/domain/text-runtime.d.ts +22 -0
- package/dist/core/llm/domain/text-runtime.js +2 -0
- package/dist/core/llm/domain/text-runtime.js.map +1 -0
- package/dist/core/llm/index.d.ts +2 -0
- package/dist/core/llm/index.js +2 -0
- package/dist/core/llm/index.js.map +1 -0
- package/dist/core/logging/application/structured-logger.d.ts +29 -0
- package/dist/core/logging/application/structured-logger.js +86 -0
- package/dist/core/logging/application/structured-logger.js.map +1 -0
- package/dist/core/logging/domain/logger.d.ts +16 -0
- package/dist/core/logging/domain/logger.js +18 -0
- package/dist/core/logging/domain/logger.js.map +1 -0
- package/dist/core/logging/index.d.ts +3 -0
- package/dist/core/logging/index.js +3 -0
- package/dist/core/logging/index.js.map +1 -0
- package/dist/core/opengoat/application/opengoat.service.d.ts +84 -0
- package/dist/core/opengoat/application/opengoat.service.js +308 -0
- package/dist/core/opengoat/application/opengoat.service.js.map +1 -0
- package/dist/core/opengoat/index.d.ts +1 -0
- package/dist/core/opengoat/index.js +2 -0
- package/dist/core/opengoat/index.js.map +1 -0
- package/dist/core/orchestration/application/orchestration-planner.service.d.ts +28 -0
- package/dist/core/orchestration/application/orchestration-planner.service.js +279 -0
- package/dist/core/orchestration/application/orchestration-planner.service.js.map +1 -0
- package/dist/core/orchestration/application/orchestration.service.d.ts +52 -0
- package/dist/core/orchestration/application/orchestration.service.js +1044 -0
- package/dist/core/orchestration/application/orchestration.service.js.map +1 -0
- package/dist/core/orchestration/application/routing.service.d.ts +11 -0
- package/dist/core/orchestration/application/routing.service.js +108 -0
- package/dist/core/orchestration/application/routing.service.js.map +1 -0
- package/dist/core/orchestration/domain/loop.d.ts +119 -0
- package/dist/core/orchestration/domain/loop.js +2 -0
- package/dist/core/orchestration/domain/loop.js.map +1 -0
- package/dist/core/orchestration/domain/routing.d.ts +58 -0
- package/dist/core/orchestration/domain/routing.js +2 -0
- package/dist/core/orchestration/domain/routing.js.map +1 -0
- package/dist/core/orchestration/domain/run-events.d.ts +21 -0
- package/dist/core/orchestration/domain/run-events.js +2 -0
- package/dist/core/orchestration/domain/run-events.js.map +1 -0
- package/dist/core/orchestration/index.d.ts +6 -0
- package/dist/core/orchestration/index.js +4 -0
- package/dist/core/orchestration/index.js.map +1 -0
- package/dist/core/plugins/application/plugin.service.d.ts +32 -0
- package/dist/core/plugins/application/plugin.service.js +236 -0
- package/dist/core/plugins/application/plugin.service.js.map +1 -0
- package/dist/core/plugins/domain/openclaw-compat.d.ts +60 -0
- package/dist/core/plugins/domain/openclaw-compat.js +9 -0
- package/dist/core/plugins/domain/openclaw-compat.js.map +1 -0
- package/dist/core/plugins/index.d.ts +3 -0
- package/dist/core/plugins/index.js +3 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/ports/command-runner.port.d.ts +14 -0
- package/dist/core/ports/command-runner.port.js +2 -0
- package/dist/core/ports/command-runner.port.js.map +1 -0
- package/dist/core/ports/file-system.port.d.ts +9 -0
- package/dist/core/ports/file-system.port.js +2 -0
- package/dist/core/ports/file-system.port.js.map +1 -0
- package/dist/core/ports/path.port.d.ts +3 -0
- package/dist/core/ports/path.port.js +2 -0
- package/dist/core/ports/path.port.js.map +1 -0
- package/dist/core/ports/paths-provider.port.d.ts +4 -0
- package/dist/core/ports/paths-provider.port.js +2 -0
- package/dist/core/ports/paths-provider.port.js.map +1 -0
- package/dist/core/providers/application/provider.service.d.ts +80 -0
- package/dist/core/providers/application/provider.service.js +391 -0
- package/dist/core/providers/application/provider.service.js.map +1 -0
- package/dist/core/providers/base-provider.d.ts +20 -0
- package/dist/core/providers/base-provider.js +44 -0
- package/dist/core/providers/base-provider.js.map +1 -0
- package/dist/core/providers/cli-provider.d.ts +26 -0
- package/dist/core/providers/cli-provider.js +151 -0
- package/dist/core/providers/cli-provider.js.map +1 -0
- package/dist/core/providers/command-executor.d.ts +12 -0
- package/dist/core/providers/command-executor.js +76 -0
- package/dist/core/providers/command-executor.js.map +1 -0
- package/dist/core/providers/errors.d.ts +30 -0
- package/dist/core/providers/errors.js +52 -0
- package/dist/core/providers/errors.js.map +1 -0
- package/dist/core/providers/index.d.ts +18 -0
- package/dist/core/providers/index.js +27 -0
- package/dist/core/providers/index.js.map +1 -0
- package/dist/core/providers/loader.d.ts +2 -0
- package/dist/core/providers/loader.js +99 -0
- package/dist/core/providers/loader.js.map +1 -0
- package/dist/core/providers/onboarding.d.ts +13 -0
- package/dist/core/providers/onboarding.js +149 -0
- package/dist/core/providers/onboarding.js.map +1 -0
- package/dist/core/providers/provider-module.d.ts +19 -0
- package/dist/core/providers/provider-module.js +2 -0
- package/dist/core/providers/provider-module.js.map +1 -0
- package/dist/core/providers/provider-session.d.ts +3 -0
- package/dist/core/providers/provider-session.js +44 -0
- package/dist/core/providers/provider-session.js.map +1 -0
- package/dist/core/providers/providers/claude/index.d.ts +4 -0
- package/dist/core/providers/providers/claude/index.js +19 -0
- package/dist/core/providers/providers/claude/index.js.map +1 -0
- package/dist/core/providers/providers/claude/provider.d.ts +8 -0
- package/dist/core/providers/providers/claude/provider.js +106 -0
- package/dist/core/providers/providers/claude/provider.js.map +1 -0
- package/dist/core/providers/providers/codex/index.d.ts +4 -0
- package/dist/core/providers/providers/codex/index.js +19 -0
- package/dist/core/providers/providers/codex/index.js.map +1 -0
- package/dist/core/providers/providers/codex/provider.d.ts +7 -0
- package/dist/core/providers/providers/codex/provider.js +31 -0
- package/dist/core/providers/providers/codex/provider.js.map +1 -0
- package/dist/core/providers/providers/cursor/index.d.ts +4 -0
- package/dist/core/providers/providers/cursor/index.js +19 -0
- package/dist/core/providers/providers/cursor/index.js.map +1 -0
- package/dist/core/providers/providers/cursor/provider.d.ts +11 -0
- package/dist/core/providers/providers/cursor/provider.js +90 -0
- package/dist/core/providers/providers/cursor/provider.js.map +1 -0
- package/dist/core/providers/providers/extended-http/catalog.d.ts +40 -0
- package/dist/core/providers/providers/extended-http/catalog.js +728 -0
- package/dist/core/providers/providers/extended-http/catalog.js.map +1 -0
- package/dist/core/providers/providers/extended-http/index.d.ts +5 -0
- package/dist/core/providers/providers/extended-http/index.js +13 -0
- package/dist/core/providers/providers/extended-http/index.js.map +1 -0
- package/dist/core/providers/providers/extended-http/provider.d.ts +31 -0
- package/dist/core/providers/providers/extended-http/provider.js +580 -0
- package/dist/core/providers/providers/extended-http/provider.js.map +1 -0
- package/dist/core/providers/providers/gemini/index.d.ts +4 -0
- package/dist/core/providers/providers/gemini/index.js +37 -0
- package/dist/core/providers/providers/gemini/index.js.map +1 -0
- package/dist/core/providers/providers/gemini/provider.d.ts +7 -0
- package/dist/core/providers/providers/gemini/provider.js +59 -0
- package/dist/core/providers/providers/gemini/provider.js.map +1 -0
- package/dist/core/providers/providers/grok/index.d.ts +4 -0
- package/dist/core/providers/providers/grok/index.js +41 -0
- package/dist/core/providers/providers/grok/index.js.map +1 -0
- package/dist/core/providers/providers/grok/provider.d.ts +13 -0
- package/dist/core/providers/providers/grok/provider.js +95 -0
- package/dist/core/providers/providers/grok/provider.js.map +1 -0
- package/dist/core/providers/providers/openai/index.d.ts +4 -0
- package/dist/core/providers/providers/openai/index.js +45 -0
- package/dist/core/providers/providers/openai/index.js.map +1 -0
- package/dist/core/providers/providers/openai/provider.d.ts +14 -0
- package/dist/core/providers/providers/openai/provider.js +203 -0
- package/dist/core/providers/providers/openai/provider.js.map +1 -0
- package/dist/core/providers/providers/openclaw/index.d.ts +4 -0
- package/dist/core/providers/providers/openclaw/index.js +31 -0
- package/dist/core/providers/providers/openclaw/index.js.map +1 -0
- package/dist/core/providers/providers/openclaw/provider.d.ts +11 -0
- package/dist/core/providers/providers/openclaw/provider.js +108 -0
- package/dist/core/providers/providers/openclaw/provider.js.map +1 -0
- package/dist/core/providers/providers/opencode/index.d.ts +4 -0
- package/dist/core/providers/providers/opencode/index.js +23 -0
- package/dist/core/providers/providers/opencode/index.js.map +1 -0
- package/dist/core/providers/providers/opencode/provider.d.ts +11 -0
- package/dist/core/providers/providers/opencode/provider.js +215 -0
- package/dist/core/providers/providers/opencode/provider.js.map +1 -0
- package/dist/core/providers/providers/openrouter/index.d.ts +4 -0
- package/dist/core/providers/providers/openrouter/index.js +37 -0
- package/dist/core/providers/providers/openrouter/index.js.map +1 -0
- package/dist/core/providers/providers/openrouter/provider.d.ts +13 -0
- package/dist/core/providers/providers/openrouter/provider.js +80 -0
- package/dist/core/providers/providers/openrouter/provider.js.map +1 -0
- package/dist/core/providers/registry.d.ts +13 -0
- package/dist/core/providers/registry.js +36 -0
- package/dist/core/providers/registry.js.map +1 -0
- package/dist/core/providers/types.d.ts +101 -0
- package/dist/core/providers/types.js +2 -0
- package/dist/core/providers/types.js.map +1 -0
- package/dist/core/scenarios/application/scenario-runner.service.d.ts +23 -0
- package/dist/core/scenarios/application/scenario-runner.service.js +172 -0
- package/dist/core/scenarios/application/scenario-runner.service.js.map +1 -0
- package/dist/core/scenarios/domain/scenario.d.ts +43 -0
- package/dist/core/scenarios/domain/scenario.js +2 -0
- package/dist/core/scenarios/domain/scenario.js.map +1 -0
- package/dist/core/scenarios/index.d.ts +2 -0
- package/dist/core/scenarios/index.js +2 -0
- package/dist/core/scenarios/index.js.map +1 -0
- package/dist/core/sessions/application/session.service.d.ts +74 -0
- package/dist/core/sessions/application/session.service.js +895 -0
- package/dist/core/sessions/application/session.service.js.map +1 -0
- package/dist/core/sessions/domain/session.d.ts +80 -0
- package/dist/core/sessions/domain/session.js +24 -0
- package/dist/core/sessions/domain/session.js.map +1 -0
- package/dist/core/sessions/domain/transcript.d.ts +28 -0
- package/dist/core/sessions/domain/transcript.js +10 -0
- package/dist/core/sessions/domain/transcript.js.map +1 -0
- package/dist/core/sessions/errors.d.ts +12 -0
- package/dist/core/sessions/errors.js +22 -0
- package/dist/core/sessions/errors.js.map +1 -0
- package/dist/core/sessions/index.d.ts +6 -0
- package/dist/core/sessions/index.js +4 -0
- package/dist/core/sessions/index.js.map +1 -0
- package/dist/core/skills/application/skill.service.d.ts +24 -0
- package/dist/core/skills/application/skill.service.js +375 -0
- package/dist/core/skills/application/skill.service.js.map +1 -0
- package/dist/core/skills/domain/skill.d.ts +62 -0
- package/dist/core/skills/domain/skill.js +46 -0
- package/dist/core/skills/domain/skill.js.map +1 -0
- package/dist/core/skills/index.d.ts +3 -0
- package/dist/core/skills/index.js +3 -0
- package/dist/core/skills/index.js.map +1 -0
- package/dist/core/templates/default-templates.d.ts +19 -0
- package/dist/core/templates/default-templates.js +259 -0
- package/dist/core/templates/default-templates.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/node/acp-server.d.ts +12 -0
- package/dist/platform/node/acp-server.js +18 -0
- package/dist/platform/node/acp-server.js.map +1 -0
- package/dist/platform/node/dotenv.d.ts +6 -0
- package/dist/platform/node/dotenv.js +67 -0
- package/dist/platform/node/dotenv.js.map +1 -0
- package/dist/platform/node/node-command-runner.d.ts +4 -0
- package/dist/platform/node/node-command-runner.js +29 -0
- package/dist/platform/node/node-command-runner.js.map +1 -0
- package/dist/platform/node/node-file-system.d.ts +10 -0
- package/dist/platform/node/node-file-system.js +47 -0
- package/dist/platform/node/node-file-system.js.map +1 -0
- package/dist/platform/node/node-logger.d.ts +9 -0
- package/dist/platform/node/node-logger.js +124 -0
- package/dist/platform/node/node-logger.js.map +1 -0
- package/dist/platform/node/node-path.port.d.ts +9 -0
- package/dist/platform/node/node-path.port.js +41 -0
- package/dist/platform/node/node-path.port.js.map +1 -0
- package/dist/platform/node/opengoat-gateway-client.d.ts +19 -0
- package/dist/platform/node/opengoat-gateway-client.js +194 -0
- package/dist/platform/node/opengoat-gateway-client.js.map +1 -0
- package/dist/platform/node/opengoat-gateway-server.d.ts +53 -0
- package/dist/platform/node/opengoat-gateway-server.js +906 -0
- package/dist/platform/node/opengoat-gateway-server.js.map +1 -0
- 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
|