@clinebot/core 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/account/cline-account-service.d.ts +34 -0
- package/dist/account/index.d.ts +3 -0
- package/dist/account/rpc.d.ts +38 -0
- package/dist/account/types.d.ts +74 -0
- package/dist/agents/agent-config-loader.d.ts +18 -0
- package/dist/agents/agent-config-parser.d.ts +25 -0
- package/dist/agents/hooks-config-loader.d.ts +23 -0
- package/dist/agents/index.d.ts +11 -0
- package/dist/agents/plugin-config-loader.d.ts +22 -0
- package/dist/agents/plugin-loader.d.ts +9 -0
- package/dist/agents/plugin-sandbox.d.ts +12 -0
- package/dist/agents/unified-config-file-watcher.d.ts +77 -0
- package/dist/agents/user-instruction-config-loader.d.ts +63 -0
- package/dist/auth/client.d.ts +11 -0
- package/dist/auth/cline.d.ts +41 -0
- package/dist/auth/codex.d.ts +39 -0
- package/dist/auth/oca.d.ts +22 -0
- package/dist/auth/server.d.ts +22 -0
- package/dist/auth/types.d.ts +72 -0
- package/dist/auth/utils.d.ts +32 -0
- package/dist/chat/chat-schema.d.ts +145 -0
- package/dist/default-tools/constants.d.ts +23 -0
- package/dist/default-tools/definitions.d.ts +96 -0
- package/dist/default-tools/executors/apply-patch-parser.d.ts +68 -0
- package/dist/default-tools/executors/apply-patch.d.ts +26 -0
- package/dist/default-tools/executors/bash.d.ts +49 -0
- package/dist/default-tools/executors/editor.d.ts +31 -0
- package/dist/default-tools/executors/file-read.d.ts +40 -0
- package/dist/default-tools/executors/index.d.ts +44 -0
- package/dist/default-tools/executors/search.d.ts +50 -0
- package/dist/default-tools/executors/web-fetch.d.ts +58 -0
- package/dist/default-tools/index.d.ts +57 -0
- package/dist/default-tools/presets.d.ts +124 -0
- package/dist/default-tools/schemas.d.ts +121 -0
- package/dist/default-tools/types.d.ts +237 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +220 -0
- package/dist/input/file-indexer.d.ts +5 -0
- package/dist/input/index.d.ts +4 -0
- package/dist/input/mention-enricher.d.ts +12 -0
- package/dist/mcp/config-loader.d.ts +15 -0
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/manager.d.ts +24 -0
- package/dist/mcp/types.d.ts +66 -0
- package/dist/runtime/hook-file-hooks.d.ts +18 -0
- package/dist/runtime/rules.d.ts +5 -0
- package/dist/runtime/runtime-builder.d.ts +5 -0
- package/dist/runtime/sandbox/subprocess-sandbox.d.ts +19 -0
- package/dist/runtime/session-runtime.d.ts +36 -0
- package/dist/runtime/tool-approval.d.ts +9 -0
- package/dist/runtime/workflows.d.ts +13 -0
- package/dist/server/index.d.ts +47 -0
- package/dist/server/index.js +641 -0
- package/dist/session/default-session-manager.d.ts +77 -0
- package/dist/session/rpc-session-service.d.ts +12 -0
- package/dist/session/runtime-oauth-token-manager.d.ts +28 -0
- package/dist/session/session-artifacts.d.ts +19 -0
- package/dist/session/session-graph.d.ts +15 -0
- package/dist/session/session-host.d.ts +21 -0
- package/dist/session/session-manager.d.ts +50 -0
- package/dist/session/session-manifest.d.ts +30 -0
- package/dist/session/session-service.d.ts +113 -0
- package/dist/session/sqlite-rpc-session-backend.d.ts +30 -0
- package/dist/session/unified-session-persistence-service.d.ts +93 -0
- package/dist/session/workspace-manager.d.ts +28 -0
- package/dist/session/workspace-manifest.d.ts +25 -0
- package/dist/storage/provider-settings-legacy-migration.d.ts +13 -0
- package/dist/storage/provider-settings-manager.d.ts +20 -0
- package/dist/storage/sqlite-session-store.d.ts +29 -0
- package/dist/storage/sqlite-team-store.d.ts +31 -0
- package/dist/storage/team-store.d.ts +2 -0
- package/dist/team/index.d.ts +1 -0
- package/dist/team/projections.d.ts +8 -0
- package/dist/types/common.d.ts +10 -0
- package/dist/types/config.d.ts +37 -0
- package/dist/types/events.d.ts +54 -0
- package/dist/types/provider-settings.d.ts +20 -0
- package/dist/types/sessions.d.ts +9 -0
- package/dist/types/storage.d.ts +37 -0
- package/dist/types/workspace.d.ts +7 -0
- package/dist/types.d.ts +26 -0
- package/package.json +63 -0
- package/src/account/cline-account-service.test.ts +101 -0
- package/src/account/cline-account-service.ts +267 -0
- package/src/account/index.ts +20 -0
- package/src/account/rpc.test.ts +62 -0
- package/src/account/rpc.ts +172 -0
- package/src/account/types.ts +80 -0
- package/src/agents/agent-config-loader.test.ts +234 -0
- package/src/agents/agent-config-loader.ts +107 -0
- package/src/agents/agent-config-parser.ts +191 -0
- package/src/agents/hooks-config-loader.ts +97 -0
- package/src/agents/index.ts +84 -0
- package/src/agents/plugin-config-loader.test.ts +91 -0
- package/src/agents/plugin-config-loader.ts +160 -0
- package/src/agents/plugin-loader.test.ts +102 -0
- package/src/agents/plugin-loader.ts +105 -0
- package/src/agents/plugin-sandbox.test.ts +120 -0
- package/src/agents/plugin-sandbox.ts +471 -0
- package/src/agents/unified-config-file-watcher.test.ts +196 -0
- package/src/agents/unified-config-file-watcher.ts +483 -0
- package/src/agents/user-instruction-config-loader.test.ts +158 -0
- package/src/agents/user-instruction-config-loader.ts +438 -0
- package/src/auth/client.test.ts +40 -0
- package/src/auth/client.ts +25 -0
- package/src/auth/cline.test.ts +130 -0
- package/src/auth/cline.ts +414 -0
- package/src/auth/codex.test.ts +170 -0
- package/src/auth/codex.ts +466 -0
- package/src/auth/oca.test.ts +215 -0
- package/src/auth/oca.ts +546 -0
- package/src/auth/server.ts +216 -0
- package/src/auth/types.ts +78 -0
- package/src/auth/utils.test.ts +128 -0
- package/src/auth/utils.ts +247 -0
- package/src/chat/chat-schema.ts +82 -0
- package/src/default-tools/constants.ts +35 -0
- package/src/default-tools/definitions.test.ts +233 -0
- package/src/default-tools/definitions.ts +632 -0
- package/src/default-tools/executors/apply-patch-parser.ts +520 -0
- package/src/default-tools/executors/apply-patch.ts +359 -0
- package/src/default-tools/executors/bash.ts +205 -0
- package/src/default-tools/executors/editor.ts +231 -0
- package/src/default-tools/executors/file-read.test.ts +25 -0
- package/src/default-tools/executors/file-read.ts +94 -0
- package/src/default-tools/executors/index.ts +75 -0
- package/src/default-tools/executors/search.ts +278 -0
- package/src/default-tools/executors/web-fetch.ts +259 -0
- package/src/default-tools/index.ts +161 -0
- package/src/default-tools/presets.test.ts +63 -0
- package/src/default-tools/presets.ts +168 -0
- package/src/default-tools/schemas.ts +228 -0
- package/src/default-tools/types.ts +324 -0
- package/src/index.ts +119 -0
- package/src/input/file-indexer.d.ts +11 -0
- package/src/input/file-indexer.test.ts +87 -0
- package/src/input/file-indexer.ts +280 -0
- package/src/input/index.ts +7 -0
- package/src/input/mention-enricher.test.ts +82 -0
- package/src/input/mention-enricher.ts +119 -0
- package/src/mcp/config-loader.test.ts +238 -0
- package/src/mcp/config-loader.ts +219 -0
- package/src/mcp/index.ts +26 -0
- package/src/mcp/manager.test.ts +106 -0
- package/src/mcp/manager.ts +262 -0
- package/src/mcp/types.ts +88 -0
- package/src/runtime/hook-file-hooks.test.ts +106 -0
- package/src/runtime/hook-file-hooks.ts +736 -0
- package/src/runtime/index.ts +27 -0
- package/src/runtime/rules.ts +34 -0
- package/src/runtime/runtime-builder.team-persistence.test.ts +203 -0
- package/src/runtime/runtime-builder.test.ts +215 -0
- package/src/runtime/runtime-builder.ts +515 -0
- package/src/runtime/runtime-parity.test.ts +132 -0
- package/src/runtime/sandbox/subprocess-sandbox.ts +207 -0
- package/src/runtime/session-runtime.ts +44 -0
- package/src/runtime/tool-approval.ts +104 -0
- package/src/runtime/workflows.test.ts +119 -0
- package/src/runtime/workflows.ts +54 -0
- package/src/server/index.ts +282 -0
- package/src/session/default-session-manager.e2e.test.ts +354 -0
- package/src/session/default-session-manager.test.ts +816 -0
- package/src/session/default-session-manager.ts +1286 -0
- package/src/session/index.ts +37 -0
- package/src/session/rpc-session-service.ts +189 -0
- package/src/session/runtime-oauth-token-manager.test.ts +137 -0
- package/src/session/runtime-oauth-token-manager.ts +265 -0
- package/src/session/session-artifacts.ts +106 -0
- package/src/session/session-graph.ts +90 -0
- package/src/session/session-host.ts +190 -0
- package/src/session/session-manager.ts +56 -0
- package/src/session/session-manifest.ts +29 -0
- package/src/session/session-service.team-persistence.test.ts +48 -0
- package/src/session/session-service.ts +610 -0
- package/src/session/sqlite-rpc-session-backend.ts +303 -0
- package/src/session/unified-session-persistence-service.ts +781 -0
- package/src/session/workspace-manager.ts +98 -0
- package/src/session/workspace-manifest.ts +100 -0
- package/src/storage/artifact-store.ts +1 -0
- package/src/storage/index.ts +11 -0
- package/src/storage/provider-settings-legacy-migration.test.ts +175 -0
- package/src/storage/provider-settings-legacy-migration.ts +637 -0
- package/src/storage/provider-settings-manager.test.ts +111 -0
- package/src/storage/provider-settings-manager.ts +129 -0
- package/src/storage/session-store.ts +1 -0
- package/src/storage/sqlite-session-store.ts +270 -0
- package/src/storage/sqlite-team-store.ts +443 -0
- package/src/storage/team-store.ts +5 -0
- package/src/team/index.ts +4 -0
- package/src/team/projections.ts +285 -0
- package/src/types/common.ts +14 -0
- package/src/types/config.ts +64 -0
- package/src/types/events.ts +46 -0
- package/src/types/index.ts +24 -0
- package/src/types/provider-settings.ts +43 -0
- package/src/types/sessions.ts +16 -0
- package/src/types/storage.ts +64 -0
- package/src/types/workspace.ts +7 -0
- package/src/types.ts +127 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
rmdirSync,
|
|
6
|
+
unlinkSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
export function nowIso(): string {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function unlinkIfExists(path: string | null | undefined): void {
|
|
15
|
+
if (!path || !existsSync(path)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
unlinkSync(path);
|
|
20
|
+
} catch {
|
|
21
|
+
// Best effort cleanup.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SessionArtifactPaths {
|
|
26
|
+
transcriptPath: string;
|
|
27
|
+
hookPath: string;
|
|
28
|
+
messagesPath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class SessionArtifacts {
|
|
32
|
+
constructor(private readonly ensureSessionsDir: () => string) {}
|
|
33
|
+
|
|
34
|
+
public sessionArtifactsDir(sessionId: string): string {
|
|
35
|
+
return join(this.ensureSessionsDir(), sessionId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public ensureSessionArtifactsDir(sessionId: string): string {
|
|
39
|
+
const dir = this.sessionArtifactsDir(sessionId);
|
|
40
|
+
if (!existsSync(dir)) {
|
|
41
|
+
mkdirSync(dir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
return dir;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public sessionTranscriptPath(sessionId: string): string {
|
|
47
|
+
return join(this.ensureSessionArtifactsDir(sessionId), `${sessionId}.log`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public sessionHookPath(sessionId: string): string {
|
|
51
|
+
return join(
|
|
52
|
+
this.ensureSessionArtifactsDir(sessionId),
|
|
53
|
+
`${sessionId}.hooks.jsonl`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public sessionMessagesPath(sessionId: string): string {
|
|
58
|
+
return join(
|
|
59
|
+
this.ensureSessionArtifactsDir(sessionId),
|
|
60
|
+
`${sessionId}.messages.json`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public sessionManifestPath(sessionId: string, ensureDir = true): string {
|
|
65
|
+
const base = ensureDir
|
|
66
|
+
? this.ensureSessionArtifactsDir(sessionId)
|
|
67
|
+
: this.sessionArtifactsDir(sessionId);
|
|
68
|
+
return join(base, `${sessionId}.json`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public removeSessionDirIfEmpty(sessionId: string): void {
|
|
72
|
+
let dir = this.sessionArtifactsDir(sessionId);
|
|
73
|
+
const sessionsDir = this.ensureSessionsDir();
|
|
74
|
+
while (dir.startsWith(sessionsDir) && dir !== sessionsDir) {
|
|
75
|
+
if (!existsSync(dir)) {
|
|
76
|
+
dir = dirname(dir);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
if (readdirSync(dir).length > 0) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
rmdirSync(dir);
|
|
84
|
+
} catch {
|
|
85
|
+
// Best-effort cleanup.
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
dir = dirname(dir);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public subagentArtifactPaths(
|
|
93
|
+
sessionId: string,
|
|
94
|
+
subAgentId: string,
|
|
95
|
+
activeTeamTaskSessionId?: string,
|
|
96
|
+
): SessionArtifactPaths {
|
|
97
|
+
void subAgentId;
|
|
98
|
+
void activeTeamTaskSessionId;
|
|
99
|
+
const dir = this.ensureSessionArtifactsDir(sessionId);
|
|
100
|
+
return {
|
|
101
|
+
transcriptPath: join(dir, `${sessionId}.log`),
|
|
102
|
+
hookPath: join(dir, `${sessionId}.hooks.jsonl`),
|
|
103
|
+
messagesPath: join(dir, `${sessionId}.messages.json`),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { HookEventPayload } from "@clinebot/agents";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import type { SessionStatus } from "../types/common";
|
|
4
|
+
|
|
5
|
+
export function sanitizeSessionToken(value: string): string {
|
|
6
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function makeSubSessionId(
|
|
10
|
+
rootSessionId: string,
|
|
11
|
+
agentId: string,
|
|
12
|
+
): string {
|
|
13
|
+
const root = sanitizeSessionToken(rootSessionId);
|
|
14
|
+
const agent = sanitizeSessionToken(agentId);
|
|
15
|
+
const joined = `${root}__${agent}`;
|
|
16
|
+
return joined.length > 180 ? joined.slice(0, 180) : joined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function makeTeamTaskSubSessionId(
|
|
20
|
+
rootSessionId: string,
|
|
21
|
+
agentId: string,
|
|
22
|
+
): string {
|
|
23
|
+
const root = sanitizeSessionToken(rootSessionId);
|
|
24
|
+
const agent = sanitizeSessionToken(agentId);
|
|
25
|
+
return `${root}__teamtask__${agent}__${nanoid(6)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseTeamTaskSubSessionId(
|
|
29
|
+
sessionId: string,
|
|
30
|
+
): { rootSessionId: string; agentId: string; teamTaskId: string } | null {
|
|
31
|
+
const marker = "__teamtask__";
|
|
32
|
+
const markerIndex = sessionId.indexOf(marker);
|
|
33
|
+
if (markerIndex <= 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const rootSessionId = sessionId.slice(0, markerIndex);
|
|
37
|
+
const remainder = sessionId.slice(markerIndex + marker.length);
|
|
38
|
+
const lastSeparator = remainder.lastIndexOf("__");
|
|
39
|
+
if (lastSeparator <= 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const agentId = remainder.slice(0, lastSeparator);
|
|
43
|
+
const teamTaskId = remainder.slice(lastSeparator + 2);
|
|
44
|
+
if (!rootSessionId || !agentId || !teamTaskId) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return { rootSessionId, agentId, teamTaskId };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseSubSessionId(
|
|
51
|
+
sessionId: string,
|
|
52
|
+
): { rootSessionId: string; agentId: string } | null {
|
|
53
|
+
if (parseTeamTaskSubSessionId(sessionId)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const separator = "__";
|
|
57
|
+
const separatorIndex = sessionId.indexOf(separator);
|
|
58
|
+
if (separatorIndex <= 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const rootSessionId = sessionId.slice(0, separatorIndex);
|
|
62
|
+
const agentId = sessionId.slice(separatorIndex + separator.length);
|
|
63
|
+
if (!rootSessionId || !agentId) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return { rootSessionId, agentId };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function deriveSubsessionStatus(event: HookEventPayload): SessionStatus {
|
|
70
|
+
switch (event.hookName) {
|
|
71
|
+
case "agent_end":
|
|
72
|
+
return "completed";
|
|
73
|
+
case "session_shutdown": {
|
|
74
|
+
const reason = String(event.reason ?? "").toLowerCase();
|
|
75
|
+
if (
|
|
76
|
+
reason.includes("cancel") ||
|
|
77
|
+
reason.includes("abort") ||
|
|
78
|
+
reason.includes("interrupt")
|
|
79
|
+
) {
|
|
80
|
+
return "cancelled";
|
|
81
|
+
}
|
|
82
|
+
if (reason.includes("fail") || reason.includes("error")) {
|
|
83
|
+
return "failed";
|
|
84
|
+
}
|
|
85
|
+
return "completed";
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
return "running";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import type {
|
|
5
|
+
AgentConfig,
|
|
6
|
+
ToolApprovalRequest,
|
|
7
|
+
ToolApprovalResult,
|
|
8
|
+
} from "@clinebot/agents";
|
|
9
|
+
import { getRpcServerHealth } from "@clinebot/rpc";
|
|
10
|
+
import { resolveSessionDataDir } from "@clinebot/shared/storage";
|
|
11
|
+
import { nanoid } from "nanoid";
|
|
12
|
+
import type { ToolExecutors } from "../default-tools";
|
|
13
|
+
import { SqliteSessionStore } from "../storage/sqlite-session-store";
|
|
14
|
+
import { DefaultSessionManager } from "./default-session-manager";
|
|
15
|
+
import { RpcCoreSessionService } from "./rpc-session-service";
|
|
16
|
+
import type { SessionManager } from "./session-manager";
|
|
17
|
+
import { CoreSessionService } from "./session-service";
|
|
18
|
+
|
|
19
|
+
const DEFAULT_RPC_ADDRESS =
|
|
20
|
+
process.env.CLINE_RPC_ADDRESS?.trim() || "127.0.0.1:4317";
|
|
21
|
+
|
|
22
|
+
type SessionBackend = RpcCoreSessionService | CoreSessionService;
|
|
23
|
+
|
|
24
|
+
let cachedBackend: SessionBackend | undefined;
|
|
25
|
+
let backendInitPromise: Promise<SessionBackend> | undefined;
|
|
26
|
+
|
|
27
|
+
export interface CreateSessionHostOptions {
|
|
28
|
+
distinctId?: string;
|
|
29
|
+
sessionService?: SessionBackend;
|
|
30
|
+
backendMode?: "auto" | "rpc" | "local";
|
|
31
|
+
rpcAddress?: string;
|
|
32
|
+
autoStartRpcServer?: boolean;
|
|
33
|
+
rpcConnectAttempts?: number;
|
|
34
|
+
rpcConnectDelayMs?: number;
|
|
35
|
+
defaultToolExecutors?: Partial<ToolExecutors>;
|
|
36
|
+
toolPolicies?: AgentConfig["toolPolicies"];
|
|
37
|
+
requestToolApproval?: (
|
|
38
|
+
request: ToolApprovalRequest,
|
|
39
|
+
) => Promise<ToolApprovalResult>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SessionHost = SessionManager;
|
|
43
|
+
|
|
44
|
+
function isLikelyScriptEntryPath(pathValue: string | undefined): boolean {
|
|
45
|
+
if (!pathValue) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return /\.(?:[cm]?[jt]s|tsx?)$/i.test(pathValue);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function startRpcServerInBackground(address: string): void {
|
|
52
|
+
const launcher = process.argv[0];
|
|
53
|
+
const entry = process.argv[1];
|
|
54
|
+
const startArgs = ["rpc", "start", "--address", address];
|
|
55
|
+
const args =
|
|
56
|
+
entry && isLikelyScriptEntryPath(entry) ? [entry, ...startArgs] : startArgs;
|
|
57
|
+
|
|
58
|
+
const child = spawn(launcher, args, {
|
|
59
|
+
detached: true,
|
|
60
|
+
stdio: "ignore",
|
|
61
|
+
env: process.env,
|
|
62
|
+
cwd: process.cwd(),
|
|
63
|
+
});
|
|
64
|
+
child.unref();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function tryConnectRpcBackend(
|
|
68
|
+
address: string,
|
|
69
|
+
): Promise<RpcCoreSessionService | undefined> {
|
|
70
|
+
try {
|
|
71
|
+
const health = await getRpcServerHealth(address);
|
|
72
|
+
if (!health) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return new RpcCoreSessionService({
|
|
76
|
+
address,
|
|
77
|
+
sessionsDir: resolveSessionDataDir(),
|
|
78
|
+
});
|
|
79
|
+
} catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createLocalBackend(): CoreSessionService {
|
|
85
|
+
return new CoreSessionService(new SqliteSessionStore());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function resolveHostDistinctId(explicitDistinctId: string | undefined): string {
|
|
89
|
+
if (
|
|
90
|
+
typeof explicitDistinctId === "string" &&
|
|
91
|
+
explicitDistinctId.trim().length > 0
|
|
92
|
+
) {
|
|
93
|
+
return explicitDistinctId.trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const sessionDataDir = resolveSessionDataDir();
|
|
97
|
+
const distinctIdPath = resolve(sessionDataDir, "machine-id");
|
|
98
|
+
try {
|
|
99
|
+
if (existsSync(distinctIdPath)) {
|
|
100
|
+
const savedDistinctId = readFileSync(distinctIdPath, "utf8").trim();
|
|
101
|
+
if (savedDistinctId.length > 0) {
|
|
102
|
+
return savedDistinctId;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Ignore read errors and generate a fresh fallback ID.
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const generatedDistinctId = nanoid();
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(sessionDataDir, { recursive: true });
|
|
112
|
+
writeFileSync(distinctIdPath, generatedDistinctId, "utf8");
|
|
113
|
+
} catch {
|
|
114
|
+
// Ignore write errors and continue with in-memory fallback.
|
|
115
|
+
}
|
|
116
|
+
return generatedDistinctId;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function resolveBackend(
|
|
120
|
+
options: CreateSessionHostOptions,
|
|
121
|
+
): Promise<SessionBackend> {
|
|
122
|
+
if (cachedBackend) {
|
|
123
|
+
return cachedBackend;
|
|
124
|
+
}
|
|
125
|
+
if (backendInitPromise) {
|
|
126
|
+
return await backendInitPromise;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const mode = options.backendMode ?? "auto";
|
|
130
|
+
const address = options.rpcAddress?.trim() || DEFAULT_RPC_ADDRESS;
|
|
131
|
+
const attempts = Math.max(1, options.rpcConnectAttempts ?? 5);
|
|
132
|
+
const delayMs = Math.max(0, options.rpcConnectDelayMs ?? 100);
|
|
133
|
+
const autoStartRpc = options.autoStartRpcServer !== false;
|
|
134
|
+
|
|
135
|
+
backendInitPromise = (async () => {
|
|
136
|
+
if (mode === "local") {
|
|
137
|
+
cachedBackend = createLocalBackend();
|
|
138
|
+
return cachedBackend;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const existingRpcBackend = await tryConnectRpcBackend(address);
|
|
142
|
+
if (existingRpcBackend) {
|
|
143
|
+
cachedBackend = existingRpcBackend;
|
|
144
|
+
return cachedBackend;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (mode === "rpc") {
|
|
148
|
+
throw new Error(`RPC backend unavailable at ${address}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (autoStartRpc) {
|
|
152
|
+
try {
|
|
153
|
+
startRpcServerInBackground(address);
|
|
154
|
+
} catch {
|
|
155
|
+
// Ignore launch failures and fall back to local backend.
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
159
|
+
const rpcBackend = await tryConnectRpcBackend(address);
|
|
160
|
+
if (rpcBackend) {
|
|
161
|
+
cachedBackend = rpcBackend;
|
|
162
|
+
return cachedBackend;
|
|
163
|
+
}
|
|
164
|
+
if (delayMs > 0) {
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
cachedBackend = createLocalBackend();
|
|
171
|
+
return cachedBackend;
|
|
172
|
+
})().finally(() => {
|
|
173
|
+
backendInitPromise = undefined;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return await backendInitPromise;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function createSessionHost(
|
|
180
|
+
options: CreateSessionHostOptions,
|
|
181
|
+
): Promise<SessionHost> {
|
|
182
|
+
const backend = options.sessionService ?? (await resolveBackend(options));
|
|
183
|
+
return new DefaultSessionManager({
|
|
184
|
+
sessionService: backend,
|
|
185
|
+
defaultToolExecutors: options.defaultToolExecutors,
|
|
186
|
+
toolPolicies: options.toolPolicies,
|
|
187
|
+
requestToolApproval: options.requestToolApproval,
|
|
188
|
+
distinctId: resolveHostDistinctId(options.distinctId),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AgentResult } from "@clinebot/agents";
|
|
2
|
+
import type { providers as LlmsProviders } from "@clinebot/llms";
|
|
3
|
+
import type { SessionSource } from "../types/common";
|
|
4
|
+
import type { CoreSessionConfig } from "../types/config";
|
|
5
|
+
import type { CoreSessionEvent } from "../types/events";
|
|
6
|
+
import type { SessionRecord } from "../types/sessions";
|
|
7
|
+
import type { SessionManifest } from "./session-manifest";
|
|
8
|
+
|
|
9
|
+
export interface StartSessionInput {
|
|
10
|
+
config: CoreSessionConfig;
|
|
11
|
+
source?: SessionSource;
|
|
12
|
+
prompt?: string;
|
|
13
|
+
interactive?: boolean;
|
|
14
|
+
initialMessages?: LlmsProviders.Message[];
|
|
15
|
+
userImages?: string[];
|
|
16
|
+
userFiles?: string[];
|
|
17
|
+
userInstructionWatcher?: import("../agents").UserInstructionConfigWatcher;
|
|
18
|
+
onTeamRestored?: () => void;
|
|
19
|
+
defaultToolExecutors?: Partial<import("../default-tools").ToolExecutors>;
|
|
20
|
+
toolPolicies?: import("@clinebot/agents").AgentConfig["toolPolicies"];
|
|
21
|
+
requestToolApproval?: (
|
|
22
|
+
request: import("@clinebot/agents").ToolApprovalRequest,
|
|
23
|
+
) => Promise<import("@clinebot/agents").ToolApprovalResult>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StartSessionResult {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
manifest: SessionManifest;
|
|
29
|
+
manifestPath: string;
|
|
30
|
+
transcriptPath: string;
|
|
31
|
+
hookPath: string;
|
|
32
|
+
messagesPath: string;
|
|
33
|
+
result?: AgentResult;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SendSessionInput {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
prompt: string;
|
|
39
|
+
userImages?: string[];
|
|
40
|
+
userFiles?: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SessionManager {
|
|
44
|
+
start(input: StartSessionInput): Promise<StartSessionResult>;
|
|
45
|
+
send(input: SendSessionInput): Promise<AgentResult | undefined>;
|
|
46
|
+
abort(sessionId: string): Promise<void>;
|
|
47
|
+
stop(sessionId: string): Promise<void>;
|
|
48
|
+
dispose(reason?: string): Promise<void>;
|
|
49
|
+
get(sessionId: string): Promise<SessionRecord | undefined>;
|
|
50
|
+
list(limit?: number): Promise<SessionRecord[]>;
|
|
51
|
+
delete(sessionId: string): Promise<boolean>;
|
|
52
|
+
readMessages(sessionId: string): Promise<LlmsProviders.Message[]>;
|
|
53
|
+
readTranscript(sessionId: string, maxChars?: number): Promise<string>;
|
|
54
|
+
readHooks(sessionId: string, limit?: number): Promise<unknown[]>;
|
|
55
|
+
subscribe(listener: (event: CoreSessionEvent) => void): () => void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SESSION_STATUSES, SessionSource } from "../types/common";
|
|
3
|
+
|
|
4
|
+
const SessionStatusSchema = z.enum(SESSION_STATUSES);
|
|
5
|
+
|
|
6
|
+
export const SessionManifestSchema = z.object({
|
|
7
|
+
version: z.literal(1),
|
|
8
|
+
session_id: z.string().min(1),
|
|
9
|
+
source: z.enum(SessionSource),
|
|
10
|
+
pid: z.number().int(),
|
|
11
|
+
started_at: z.string().min(1),
|
|
12
|
+
ended_at: z.string().min(1).optional(),
|
|
13
|
+
exit_code: z.number().int().nullable().optional(),
|
|
14
|
+
status: SessionStatusSchema,
|
|
15
|
+
interactive: z.boolean(),
|
|
16
|
+
provider: z.string().min(1),
|
|
17
|
+
model: z.string().min(1),
|
|
18
|
+
cwd: z.string().min(1),
|
|
19
|
+
workspace_root: z.string().min(1),
|
|
20
|
+
team_name: z.string().min(1).optional(),
|
|
21
|
+
enable_tools: z.boolean(),
|
|
22
|
+
enable_spawn: z.boolean(),
|
|
23
|
+
enable_teams: z.boolean(),
|
|
24
|
+
prompt: z.string().optional(),
|
|
25
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
26
|
+
messages_path: z.string().min(1).optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type SessionManifest = z.infer<typeof SessionManifestSchema>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { AgentTeamsRuntime } from "@clinebot/agents";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { FileTeamPersistenceStore } from "./session-service";
|
|
7
|
+
|
|
8
|
+
describe("FileTeamPersistenceStore", () => {
|
|
9
|
+
it("does not create state.json for an empty runtime", () => {
|
|
10
|
+
const baseDir = mkdtempSync(join(tmpdir(), "team-store-"));
|
|
11
|
+
const store = new FileTeamPersistenceStore({
|
|
12
|
+
teamName: "agent-team-nmnn9",
|
|
13
|
+
baseDir,
|
|
14
|
+
});
|
|
15
|
+
const runtime = new AgentTeamsRuntime({ teamName: "agent-team-nmnn9" });
|
|
16
|
+
|
|
17
|
+
store.persist(runtime);
|
|
18
|
+
|
|
19
|
+
expect(
|
|
20
|
+
existsSync(join(baseDir, "agent-team-nmnn9", "state.json")),
|
|
21
|
+
).toBeFalsy();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("removes persisted state when runtime becomes empty again", () => {
|
|
25
|
+
const baseDir = mkdtempSync(join(tmpdir(), "team-store-"));
|
|
26
|
+
const store = new FileTeamPersistenceStore({
|
|
27
|
+
teamName: "agent-team-cleanup",
|
|
28
|
+
baseDir,
|
|
29
|
+
});
|
|
30
|
+
const runtime = new AgentTeamsRuntime({ teamName: "agent-team-cleanup" });
|
|
31
|
+
|
|
32
|
+
store.upsertTeammateSpec({
|
|
33
|
+
agentId: "worker",
|
|
34
|
+
rolePrompt: "Implement tasks",
|
|
35
|
+
});
|
|
36
|
+
store.persist(runtime);
|
|
37
|
+
expect(
|
|
38
|
+
existsSync(join(baseDir, "agent-team-cleanup", "state.json")),
|
|
39
|
+
).toBeTruthy();
|
|
40
|
+
|
|
41
|
+
store.removeTeammateSpec("worker");
|
|
42
|
+
runtime.cleanup();
|
|
43
|
+
store.persist(runtime);
|
|
44
|
+
expect(
|
|
45
|
+
existsSync(join(baseDir, "agent-team-cleanup", "state.json")),
|
|
46
|
+
).toBeFalsy();
|
|
47
|
+
});
|
|
48
|
+
});
|