@clinebot/core 0.0.6 → 0.0.10
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/dist/agents/hooks-config-loader.d.ts +1 -0
- package/dist/auth/cline.d.ts +2 -0
- package/dist/auth/codex.d.ts +5 -1
- package/dist/auth/oca.d.ts +7 -1
- package/dist/auth/types.d.ts +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.node.d.ts +2 -0
- package/dist/index.node.js +164 -162
- package/dist/input/mention-enricher.d.ts +1 -0
- package/dist/providers/local-provider-service.d.ts +1 -1
- package/dist/runtime/session-runtime.d.ts +1 -1
- package/dist/session/default-session-manager.d.ts +13 -17
- package/dist/session/rpc-spawn-lease.d.ts +7 -0
- package/dist/session/runtime-oauth-token-manager.d.ts +4 -2
- package/dist/session/session-agent-events.d.ts +15 -0
- package/dist/session/session-config-builder.d.ts +13 -0
- package/dist/session/session-manager.d.ts +2 -2
- package/dist/session/session-team-coordination.d.ts +12 -0
- package/dist/session/session-telemetry.d.ts +9 -0
- package/dist/session/unified-session-persistence-service.d.ts +12 -16
- package/dist/session/utils/helpers.d.ts +1 -1
- package/dist/session/utils/types.d.ts +1 -1
- package/dist/storage/provider-settings-legacy-migration.d.ts +25 -0
- package/dist/telemetry/core-events.d.ts +122 -0
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/executors/file-read.d.ts +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/presets.d.ts +1 -1
- package/dist/tools/schemas.d.ts +48 -11
- package/dist/tools/types.d.ts +3 -3
- package/dist/types/config.d.ts +1 -1
- package/dist/types/events.d.ts +1 -1
- package/dist/types/provider-settings.d.ts +4 -4
- package/dist/types.d.ts +1 -1
- package/package.json +4 -3
- package/src/agents/hooks-config-loader.ts +2 -0
- package/src/auth/cline.ts +35 -1
- package/src/auth/codex.ts +27 -2
- package/src/auth/oca.ts +31 -4
- package/src/auth/types.ts +3 -0
- package/src/index.node.ts +4 -0
- package/src/index.ts +27 -0
- package/src/input/file-indexer.test.ts +40 -0
- package/src/input/file-indexer.ts +21 -0
- package/src/input/mention-enricher.test.ts +3 -0
- package/src/input/mention-enricher.ts +3 -0
- package/src/providers/local-provider-service.ts +6 -7
- package/src/runtime/hook-file-hooks.test.ts +51 -1
- package/src/runtime/hook-file-hooks.ts +91 -11
- package/src/runtime/session-runtime.ts +1 -1
- package/src/session/default-session-manager.e2e.test.ts +2 -1
- package/src/session/default-session-manager.ts +367 -601
- package/src/session/rpc-spawn-lease.test.ts +49 -0
- package/src/session/rpc-spawn-lease.ts +122 -0
- package/src/session/runtime-oauth-token-manager.ts +21 -14
- package/src/session/session-agent-events.ts +159 -0
- package/src/session/session-config-builder.ts +111 -0
- package/src/session/session-graph.ts +2 -0
- package/src/session/session-host.ts +21 -0
- package/src/session/session-manager.ts +2 -2
- package/src/session/session-team-coordination.ts +198 -0
- package/src/session/session-telemetry.ts +95 -0
- package/src/session/unified-session-persistence-service.test.ts +81 -0
- package/src/session/unified-session-persistence-service.ts +470 -469
- package/src/session/utils/helpers.ts +1 -1
- package/src/session/utils/types.ts +1 -1
- package/src/storage/provider-settings-legacy-migration.test.ts +133 -1
- package/src/storage/provider-settings-legacy-migration.ts +63 -11
- package/src/telemetry/core-events.ts +344 -0
- package/src/tools/definitions.test.ts +203 -36
- package/src/tools/definitions.ts +66 -28
- package/src/tools/executors/editor.test.ts +35 -0
- package/src/tools/executors/editor.ts +33 -46
- package/src/tools/executors/file-read.test.ts +29 -5
- package/src/tools/executors/file-read.ts +17 -6
- package/src/tools/index.ts +2 -0
- package/src/tools/presets.ts +1 -1
- package/src/tools/schemas.ts +88 -38
- package/src/tools/types.ts +7 -3
- package/src/types/config.ts +1 -1
- package/src/types/events.ts +6 -1
- package/src/types/provider-settings.ts +6 -6
- package/src/types.ts +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { tryAcquireRpcSpawnLease } from "./rpc-spawn-lease";
|
|
6
|
+
|
|
7
|
+
describe("tryAcquireRpcSpawnLease", () => {
|
|
8
|
+
const tempDirs: string[] = [];
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
delete process.env.CLINE_DATA_DIR;
|
|
12
|
+
for (const dir of tempDirs.splice(0)) {
|
|
13
|
+
rmSync(dir, { recursive: true, force: true });
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("allows only one active lease per address", () => {
|
|
18
|
+
const dataDir = mkdtempSync(path.join(os.tmpdir(), "rpc-spawn-lease-"));
|
|
19
|
+
tempDirs.push(dataDir);
|
|
20
|
+
process.env.CLINE_DATA_DIR = dataDir;
|
|
21
|
+
|
|
22
|
+
const first = tryAcquireRpcSpawnLease("127.0.0.1:4317");
|
|
23
|
+
const second = tryAcquireRpcSpawnLease("127.0.0.1:4317");
|
|
24
|
+
|
|
25
|
+
expect(first).toBeDefined();
|
|
26
|
+
expect(second).toBeUndefined();
|
|
27
|
+
|
|
28
|
+
first?.release();
|
|
29
|
+
|
|
30
|
+
const third = tryAcquireRpcSpawnLease("127.0.0.1:4317");
|
|
31
|
+
expect(third).toBeDefined();
|
|
32
|
+
third?.release();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("lets different addresses acquire independent leases", () => {
|
|
36
|
+
const dataDir = mkdtempSync(path.join(os.tmpdir(), "rpc-spawn-lease-"));
|
|
37
|
+
tempDirs.push(dataDir);
|
|
38
|
+
process.env.CLINE_DATA_DIR = dataDir;
|
|
39
|
+
|
|
40
|
+
const first = tryAcquireRpcSpawnLease("127.0.0.1:4317");
|
|
41
|
+
const second = tryAcquireRpcSpawnLease("127.0.0.1:4318");
|
|
42
|
+
|
|
43
|
+
expect(first).toBeDefined();
|
|
44
|
+
expect(second).toBeDefined();
|
|
45
|
+
|
|
46
|
+
first?.release();
|
|
47
|
+
second?.release();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
openSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
11
|
+
import { resolveSessionDataDir } from "@clinebot/shared/storage";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_LEASE_TTL_MS = 15_000;
|
|
14
|
+
|
|
15
|
+
interface RpcSpawnLeaseRecord {
|
|
16
|
+
address: string;
|
|
17
|
+
pid: number;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RpcSpawnLease {
|
|
22
|
+
path: string;
|
|
23
|
+
release: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function encodeAddress(address: string): string {
|
|
27
|
+
return Buffer.from(address).toString("base64url");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getLeasePath(address: string): string {
|
|
31
|
+
return resolve(
|
|
32
|
+
resolveSessionDataDir(),
|
|
33
|
+
"rpc",
|
|
34
|
+
"spawn-leases",
|
|
35
|
+
`${encodeAddress(address)}.lock`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isProcessAlive(pid: number): boolean {
|
|
40
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
process.kill(pid, 0);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shouldClearLease(path: string, ttlMs: number): boolean {
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(path, "utf8");
|
|
54
|
+
const parsed = JSON.parse(raw) as Partial<RpcSpawnLeaseRecord>;
|
|
55
|
+
const createdAt = Number(parsed.createdAt ?? 0);
|
|
56
|
+
if (!Number.isFinite(createdAt) || createdAt <= 0) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (Date.now() - createdAt > ttlMs) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return !isProcessAlive(Number(parsed.pid ?? 0));
|
|
63
|
+
} catch {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function tryAcquireRpcSpawnLease(
|
|
69
|
+
address: string,
|
|
70
|
+
options?: { ttlMs?: number },
|
|
71
|
+
): RpcSpawnLease | undefined {
|
|
72
|
+
const ttlMs = Math.max(1_000, options?.ttlMs ?? DEFAULT_LEASE_TTL_MS);
|
|
73
|
+
const path = getLeasePath(address);
|
|
74
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
75
|
+
|
|
76
|
+
if (existsSync(path) && shouldClearLease(path, ttlMs)) {
|
|
77
|
+
rmSync(path, { force: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let fd: number | undefined;
|
|
81
|
+
try {
|
|
82
|
+
fd = openSync(path, "wx");
|
|
83
|
+
const record: RpcSpawnLeaseRecord = {
|
|
84
|
+
address,
|
|
85
|
+
pid: process.pid,
|
|
86
|
+
createdAt: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
writeFileSync(fd, JSON.stringify(record), "utf8");
|
|
89
|
+
} catch {
|
|
90
|
+
if (typeof fd === "number") {
|
|
91
|
+
try {
|
|
92
|
+
closeSync(fd);
|
|
93
|
+
} catch {
|
|
94
|
+
// Best effort.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let released = false;
|
|
101
|
+
return {
|
|
102
|
+
path,
|
|
103
|
+
release: () => {
|
|
104
|
+
if (released) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
released = true;
|
|
108
|
+
try {
|
|
109
|
+
if (typeof fd === "number") {
|
|
110
|
+
closeSync(fd);
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Best effort.
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
rmSync(path, { force: true });
|
|
117
|
+
} catch {
|
|
118
|
+
// Best effort.
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LlmsProviders } from "@clinebot/llms";
|
|
2
|
+
import {
|
|
3
|
+
type ITelemetryService,
|
|
4
|
+
isOAuthProviderId,
|
|
5
|
+
type OAuthProviderId,
|
|
6
|
+
} from "@clinebot/shared";
|
|
2
7
|
import {
|
|
3
8
|
type ClineOAuthCredentials,
|
|
4
9
|
getValidClineCredentials,
|
|
@@ -11,14 +16,7 @@ import { ProviderSettingsManager } from "../storage/provider-settings-manager";
|
|
|
11
16
|
const DEFAULT_CLINE_API_BASE_URL = "https://api.cline.bot";
|
|
12
17
|
const WORKOS_TOKEN_PREFIX = "workos:";
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
type ManagedOAuthProviderId = (typeof MANAGED_OAUTH_PROVIDERS)[number];
|
|
16
|
-
|
|
17
|
-
function isManagedOAuthProviderId(
|
|
18
|
-
providerId: string,
|
|
19
|
-
): providerId is ManagedOAuthProviderId {
|
|
20
|
-
return (MANAGED_OAUTH_PROVIDERS as readonly string[]).includes(providerId);
|
|
21
|
-
}
|
|
19
|
+
type ManagedOAuthProviderId = OAuthProviderId;
|
|
22
20
|
|
|
23
21
|
function toStoredAccessToken(
|
|
24
22
|
providerId: ManagedOAuthProviderId,
|
|
@@ -143,21 +141,26 @@ export type RuntimeOAuthResolution = {
|
|
|
143
141
|
|
|
144
142
|
export class RuntimeOAuthTokenManager {
|
|
145
143
|
private readonly providerSettingsManager: ProviderSettingsManager;
|
|
144
|
+
private readonly telemetry?: ITelemetryService;
|
|
146
145
|
private readonly refreshInFlight = new Map<
|
|
147
146
|
ManagedOAuthProviderId,
|
|
148
147
|
Promise<RuntimeOAuthResolution | null>
|
|
149
148
|
>();
|
|
150
149
|
|
|
151
|
-
constructor(options?: {
|
|
150
|
+
constructor(options?: {
|
|
151
|
+
providerSettingsManager?: ProviderSettingsManager;
|
|
152
|
+
telemetry?: ITelemetryService;
|
|
153
|
+
}) {
|
|
152
154
|
this.providerSettingsManager =
|
|
153
155
|
options?.providerSettingsManager ?? new ProviderSettingsManager();
|
|
156
|
+
this.telemetry = options?.telemetry;
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
public async resolveProviderApiKey(input: {
|
|
157
160
|
providerId: string;
|
|
158
161
|
forceRefresh?: boolean;
|
|
159
162
|
}): Promise<RuntimeOAuthResolution | null> {
|
|
160
|
-
if (!
|
|
163
|
+
if (!isOAuthProviderId(input.providerId)) {
|
|
161
164
|
return null;
|
|
162
165
|
}
|
|
163
166
|
return this.resolveWithSingleFlight(input.providerId, input.forceRefresh);
|
|
@@ -249,6 +252,7 @@ export class RuntimeOAuthTokenManager {
|
|
|
249
252
|
currentCredentials,
|
|
250
253
|
{
|
|
251
254
|
apiBaseUrl: settings.baseUrl?.trim() || DEFAULT_CLINE_API_BASE_URL,
|
|
255
|
+
telemetry: this.telemetry,
|
|
252
256
|
},
|
|
253
257
|
{ forceRefresh },
|
|
254
258
|
);
|
|
@@ -256,10 +260,13 @@ export class RuntimeOAuthTokenManager {
|
|
|
256
260
|
if (providerId === "oca") {
|
|
257
261
|
return getValidOcaCredentials(
|
|
258
262
|
currentCredentials,
|
|
259
|
-
{ forceRefresh },
|
|
260
|
-
{ mode: settings.oca?.mode },
|
|
263
|
+
{ forceRefresh, telemetry: this.telemetry },
|
|
264
|
+
{ mode: settings.oca?.mode, telemetry: this.telemetry },
|
|
261
265
|
);
|
|
262
266
|
}
|
|
263
|
-
return getValidOpenAICodexCredentials(currentCredentials, {
|
|
267
|
+
return getValidOpenAICodexCredentials(currentCredentials, {
|
|
268
|
+
forceRefresh,
|
|
269
|
+
telemetry: this.telemetry,
|
|
270
|
+
});
|
|
264
271
|
}
|
|
265
272
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { AgentEvent } from "@clinebot/agents";
|
|
2
|
+
import {
|
|
3
|
+
captureConversationTurnEvent,
|
|
4
|
+
captureDiffEditFailure,
|
|
5
|
+
captureProviderApiError,
|
|
6
|
+
captureSkillUsed,
|
|
7
|
+
captureTokenUsage,
|
|
8
|
+
captureToolUsage,
|
|
9
|
+
} from "../telemetry/core-events";
|
|
10
|
+
import type { CoreSessionConfig } from "../types/config";
|
|
11
|
+
import type { CoreSessionEvent } from "../types/events";
|
|
12
|
+
import type { SessionAccumulatedUsage } from "./session-manager";
|
|
13
|
+
import { serializeAgentEvent } from "./utils/helpers";
|
|
14
|
+
import type { ActiveSession } from "./utils/types";
|
|
15
|
+
import { accumulateUsageTotals } from "./utils/usage";
|
|
16
|
+
|
|
17
|
+
export function extractSkillNameFromToolInput(
|
|
18
|
+
input: unknown,
|
|
19
|
+
): string | undefined {
|
|
20
|
+
if (!input || typeof input !== "object") return undefined;
|
|
21
|
+
const record = input as Record<string, unknown>;
|
|
22
|
+
const skillName = record.skill ?? record.skill_name ?? record.skillName;
|
|
23
|
+
if (typeof skillName !== "string") return undefined;
|
|
24
|
+
const trimmed = skillName.trim();
|
|
25
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AgentEventContext {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
config: CoreSessionConfig;
|
|
31
|
+
liveSession: ActiveSession | undefined;
|
|
32
|
+
usageBySession: Map<string, SessionAccumulatedUsage>;
|
|
33
|
+
persistMessages: (
|
|
34
|
+
sessionId: string,
|
|
35
|
+
messages: unknown[],
|
|
36
|
+
systemPrompt?: string,
|
|
37
|
+
) => void;
|
|
38
|
+
emit: (event: CoreSessionEvent) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function handleAgentEvent(
|
|
42
|
+
ctx: AgentEventContext,
|
|
43
|
+
event: AgentEvent,
|
|
44
|
+
): void {
|
|
45
|
+
const { sessionId, config, liveSession, emit } = ctx;
|
|
46
|
+
const telemetry = config.telemetry;
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
event.type === "content_start" &&
|
|
50
|
+
event.contentType === "tool" &&
|
|
51
|
+
event.toolName === "skills"
|
|
52
|
+
) {
|
|
53
|
+
const skillName = extractSkillNameFromToolInput(event.input);
|
|
54
|
+
if (skillName) {
|
|
55
|
+
captureSkillUsed(telemetry, {
|
|
56
|
+
ulid: sessionId,
|
|
57
|
+
skillName,
|
|
58
|
+
skillSource: "project",
|
|
59
|
+
skillsAvailableGlobal: 0,
|
|
60
|
+
skillsAvailableProject: 0,
|
|
61
|
+
provider: config.providerId,
|
|
62
|
+
modelId: config.modelId,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (event.type === "content_end" && event.contentType === "tool") {
|
|
68
|
+
const toolName = event.toolName ?? "unknown";
|
|
69
|
+
const success = !event.error;
|
|
70
|
+
captureToolUsage(telemetry, {
|
|
71
|
+
ulid: sessionId,
|
|
72
|
+
tool: toolName,
|
|
73
|
+
autoApproved: undefined,
|
|
74
|
+
success,
|
|
75
|
+
modelId: config.modelId,
|
|
76
|
+
provider: config.providerId,
|
|
77
|
+
isNativeToolCall: false,
|
|
78
|
+
});
|
|
79
|
+
if (!success && (toolName === "editor" || toolName === "apply_patch")) {
|
|
80
|
+
captureDiffEditFailure(telemetry, {
|
|
81
|
+
ulid: sessionId,
|
|
82
|
+
modelId: config.modelId,
|
|
83
|
+
provider: config.providerId,
|
|
84
|
+
errorType: event.error,
|
|
85
|
+
isNativeToolCall: false,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (event.type === "notice" && event.reason === "api_error") {
|
|
91
|
+
captureProviderApiError(telemetry, {
|
|
92
|
+
ulid: sessionId,
|
|
93
|
+
model: config.modelId,
|
|
94
|
+
provider: config.providerId,
|
|
95
|
+
errorMessage: event.message,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (event.type === "error") {
|
|
100
|
+
captureProviderApiError(telemetry, {
|
|
101
|
+
ulid: sessionId,
|
|
102
|
+
model: config.modelId,
|
|
103
|
+
provider: config.providerId,
|
|
104
|
+
errorMessage: event.error?.message ?? "unknown error",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (event.type === "usage" && liveSession?.turnUsageBaseline) {
|
|
109
|
+
ctx.usageBySession.set(
|
|
110
|
+
sessionId,
|
|
111
|
+
accumulateUsageTotals(liveSession.turnUsageBaseline, {
|
|
112
|
+
inputTokens: event.totalInputTokens,
|
|
113
|
+
outputTokens: event.totalOutputTokens,
|
|
114
|
+
totalCost: event.totalCost,
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
captureConversationTurnEvent(telemetry, {
|
|
118
|
+
ulid: sessionId,
|
|
119
|
+
provider: config.providerId,
|
|
120
|
+
model: config.modelId,
|
|
121
|
+
source: "assistant",
|
|
122
|
+
mode: config.mode,
|
|
123
|
+
tokensIn: event.inputTokens,
|
|
124
|
+
tokensOut: event.outputTokens,
|
|
125
|
+
cacheWriteTokens: event.cacheWriteTokens,
|
|
126
|
+
cacheReadTokens: event.cacheReadTokens,
|
|
127
|
+
totalCost: event.cost,
|
|
128
|
+
isNativeToolCall: false,
|
|
129
|
+
});
|
|
130
|
+
captureTokenUsage(telemetry, {
|
|
131
|
+
ulid: sessionId,
|
|
132
|
+
tokensIn: event.inputTokens,
|
|
133
|
+
tokensOut: event.outputTokens,
|
|
134
|
+
model: config.modelId,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (event.type === "iteration_end") {
|
|
139
|
+
ctx.persistMessages(
|
|
140
|
+
sessionId,
|
|
141
|
+
liveSession?.agent.getMessages() ?? [],
|
|
142
|
+
liveSession?.config.systemPrompt,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
emit({
|
|
147
|
+
type: "agent_event",
|
|
148
|
+
payload: { sessionId, event },
|
|
149
|
+
});
|
|
150
|
+
emit({
|
|
151
|
+
type: "chunk",
|
|
152
|
+
payload: {
|
|
153
|
+
sessionId,
|
|
154
|
+
stream: "agent",
|
|
155
|
+
chunk: serializeAgentEvent(event),
|
|
156
|
+
ts: Date.now(),
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { LlmsProviders } from "@clinebot/llms";
|
|
2
|
+
import type { ITelemetryService } from "@clinebot/shared";
|
|
3
|
+
import { resolveAndLoadAgentPlugins } from "../agents/plugin-config-loader";
|
|
4
|
+
import {
|
|
5
|
+
createHookAuditHooks,
|
|
6
|
+
createHookConfigFileHooks,
|
|
7
|
+
mergeAgentHooks,
|
|
8
|
+
} from "../runtime/hook-file-hooks";
|
|
9
|
+
import type { ProviderSettingsManager } from "../storage/provider-settings-manager";
|
|
10
|
+
import type { CoreSessionConfig } from "../types/config";
|
|
11
|
+
import {
|
|
12
|
+
type ProviderSettings,
|
|
13
|
+
toProviderConfig,
|
|
14
|
+
} from "../types/provider-settings";
|
|
15
|
+
import type { StartSessionInput } from "./session-manager";
|
|
16
|
+
import { hasRuntimeHooks, mergeAgentExtensions } from "./utils/helpers";
|
|
17
|
+
|
|
18
|
+
export function resolveWorkspacePath(config: CoreSessionConfig): string {
|
|
19
|
+
return config.workspaceRoot ?? config.cwd;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function buildEffectiveConfig(
|
|
23
|
+
input: StartSessionInput,
|
|
24
|
+
hookPath: string,
|
|
25
|
+
sessionId: string,
|
|
26
|
+
defaultTelemetry: ITelemetryService | undefined,
|
|
27
|
+
): Promise<{
|
|
28
|
+
config: CoreSessionConfig;
|
|
29
|
+
pluginSandboxShutdown?: () => Promise<void>;
|
|
30
|
+
}> {
|
|
31
|
+
const workspacePath = resolveWorkspacePath(input.config);
|
|
32
|
+
|
|
33
|
+
const fileHooks = createHookConfigFileHooks({
|
|
34
|
+
cwd: input.config.cwd,
|
|
35
|
+
workspacePath,
|
|
36
|
+
rootSessionId: sessionId,
|
|
37
|
+
hookLogPath: hookPath,
|
|
38
|
+
logger: input.config.logger,
|
|
39
|
+
});
|
|
40
|
+
const auditHooks = hasRuntimeHooks(input.config.hooks)
|
|
41
|
+
? undefined
|
|
42
|
+
: createHookAuditHooks({
|
|
43
|
+
hookLogPath: hookPath,
|
|
44
|
+
rootSessionId: sessionId,
|
|
45
|
+
workspacePath,
|
|
46
|
+
});
|
|
47
|
+
const effectiveHooks = mergeAgentHooks([
|
|
48
|
+
input.config.hooks,
|
|
49
|
+
fileHooks,
|
|
50
|
+
auditHooks,
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const loadedPlugins = await resolveAndLoadAgentPlugins({
|
|
54
|
+
pluginPaths: input.config.pluginPaths,
|
|
55
|
+
workspacePath,
|
|
56
|
+
cwd: input.config.cwd,
|
|
57
|
+
});
|
|
58
|
+
const effectiveExtensions = mergeAgentExtensions(
|
|
59
|
+
input.config.extensions,
|
|
60
|
+
loadedPlugins.extensions,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
config: {
|
|
65
|
+
...input.config,
|
|
66
|
+
hooks: effectiveHooks,
|
|
67
|
+
extensions: effectiveExtensions,
|
|
68
|
+
telemetry: input.config.telemetry ?? defaultTelemetry,
|
|
69
|
+
},
|
|
70
|
+
pluginSandboxShutdown: loadedPlugins.shutdown,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function buildResolvedProviderConfig(
|
|
75
|
+
config: CoreSessionConfig,
|
|
76
|
+
providerSettingsManager: ProviderSettingsManager,
|
|
77
|
+
resolveReasoningFn: (
|
|
78
|
+
config: CoreSessionConfig,
|
|
79
|
+
storedReasoning: ProviderSettings["reasoning"],
|
|
80
|
+
) => ProviderSettings["reasoning"],
|
|
81
|
+
): LlmsProviders.ProviderConfig {
|
|
82
|
+
const stored = providerSettingsManager.getProviderSettings(config.providerId);
|
|
83
|
+
const settings: ProviderSettings = {
|
|
84
|
+
...(stored ?? {}),
|
|
85
|
+
provider: config.providerId,
|
|
86
|
+
model: config.modelId,
|
|
87
|
+
apiKey: config.apiKey ?? stored?.apiKey,
|
|
88
|
+
baseUrl: config.baseUrl ?? stored?.baseUrl,
|
|
89
|
+
headers: config.headers ?? stored?.headers,
|
|
90
|
+
reasoning: resolveReasoningFn(config, stored?.reasoning),
|
|
91
|
+
};
|
|
92
|
+
const providerConfig = toProviderConfig(settings);
|
|
93
|
+
if (config.knownModels) {
|
|
94
|
+
providerConfig.knownModels = config.knownModels;
|
|
95
|
+
}
|
|
96
|
+
return providerConfig;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolveReasoningSettings(
|
|
100
|
+
config: CoreSessionConfig,
|
|
101
|
+
storedReasoning: ProviderSettings["reasoning"],
|
|
102
|
+
): ProviderSettings["reasoning"] {
|
|
103
|
+
const hasThinking = typeof config.thinking === "boolean";
|
|
104
|
+
const hasEffort = typeof config.reasoningEffort === "string";
|
|
105
|
+
if (!hasThinking && !hasEffort) return storedReasoning;
|
|
106
|
+
return {
|
|
107
|
+
...(storedReasoning ?? {}),
|
|
108
|
+
...(hasThinking ? { enabled: config.thinking } : {}),
|
|
109
|
+
...(hasEffort ? { effort: config.reasoningEffort } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -70,6 +70,8 @@ export function deriveSubsessionStatus(event: HookEventPayload): SessionStatus {
|
|
|
70
70
|
switch (event.hookName) {
|
|
71
71
|
case "agent_end":
|
|
72
72
|
return "completed";
|
|
73
|
+
case "agent_error":
|
|
74
|
+
return "failed";
|
|
73
75
|
case "session_shutdown": {
|
|
74
76
|
const reason = String(event.reason ?? "").toLowerCase();
|
|
75
77
|
if (
|
|
@@ -14,6 +14,7 @@ import { SqliteSessionStore } from "../storage/sqlite-session-store";
|
|
|
14
14
|
import type { ToolExecutors } from "../tools";
|
|
15
15
|
import { DefaultSessionManager } from "./default-session-manager";
|
|
16
16
|
import { RpcCoreSessionService } from "./rpc-session-service";
|
|
17
|
+
import { tryAcquireRpcSpawnLease } from "./rpc-spawn-lease";
|
|
17
18
|
import type { SessionManager } from "./session-manager";
|
|
18
19
|
import { CoreSessionService } from "./session-service";
|
|
19
20
|
|
|
@@ -43,14 +44,29 @@ export interface CreateSessionHostOptions {
|
|
|
43
44
|
|
|
44
45
|
export type SessionHost = SessionManager;
|
|
45
46
|
|
|
47
|
+
async function reconcileDeadSessionsIfSupported(
|
|
48
|
+
backend: SessionBackend,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const service = backend as SessionBackend & {
|
|
51
|
+
reconcileDeadSessions?: (limit?: number) => Promise<number>;
|
|
52
|
+
};
|
|
53
|
+
await service.reconcileDeadSessions?.().catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
function startRpcServerInBackground(address: string): void {
|
|
57
|
+
const lease = tryAcquireRpcSpawnLease(address);
|
|
58
|
+
if (!lease) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
47
61
|
const launcher = process.execPath;
|
|
48
62
|
const entryArg = process.argv[1]?.trim();
|
|
49
63
|
if (!entryArg) {
|
|
64
|
+
lease.release();
|
|
50
65
|
return;
|
|
51
66
|
}
|
|
52
67
|
const entry = resolve(process.cwd(), entryArg);
|
|
53
68
|
if (!existsSync(entry)) {
|
|
69
|
+
lease.release();
|
|
54
70
|
return;
|
|
55
71
|
}
|
|
56
72
|
const conditionsArg = process.execArgv.find((arg) =>
|
|
@@ -75,6 +91,7 @@ function startRpcServerInBackground(address: string): void {
|
|
|
75
91
|
cwd: process.cwd(),
|
|
76
92
|
});
|
|
77
93
|
child.unref();
|
|
94
|
+
setTimeout(() => lease.release(), 10_000).unref();
|
|
78
95
|
}
|
|
79
96
|
|
|
80
97
|
async function tryConnectRpcBackend(
|
|
@@ -148,12 +165,14 @@ export async function resolveSessionBackend(
|
|
|
148
165
|
backendInitPromise = (async () => {
|
|
149
166
|
if (mode === "local") {
|
|
150
167
|
cachedBackend = createLocalBackend();
|
|
168
|
+
await reconcileDeadSessionsIfSupported(cachedBackend);
|
|
151
169
|
return cachedBackend;
|
|
152
170
|
}
|
|
153
171
|
|
|
154
172
|
const existingRpcBackend = await tryConnectRpcBackend(address);
|
|
155
173
|
if (existingRpcBackend) {
|
|
156
174
|
cachedBackend = existingRpcBackend;
|
|
175
|
+
await reconcileDeadSessionsIfSupported(cachedBackend);
|
|
157
176
|
return cachedBackend;
|
|
158
177
|
}
|
|
159
178
|
|
|
@@ -172,6 +191,7 @@ export async function resolveSessionBackend(
|
|
|
172
191
|
const rpcBackend = await tryConnectRpcBackend(address);
|
|
173
192
|
if (rpcBackend) {
|
|
174
193
|
cachedBackend = rpcBackend;
|
|
194
|
+
await reconcileDeadSessionsIfSupported(cachedBackend);
|
|
175
195
|
return cachedBackend;
|
|
176
196
|
}
|
|
177
197
|
if (delayMs > 0) {
|
|
@@ -181,6 +201,7 @@ export async function resolveSessionBackend(
|
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
cachedBackend = createLocalBackend();
|
|
204
|
+
await reconcileDeadSessionsIfSupported(cachedBackend);
|
|
184
205
|
return cachedBackend;
|
|
185
206
|
})().finally(() => {
|
|
186
207
|
backendInitPromise = undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentResult } from "@clinebot/agents";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LlmsProviders } from "@clinebot/llms";
|
|
3
3
|
import type { SessionSource } from "../types/common";
|
|
4
4
|
import type { CoreSessionConfig } from "../types/config";
|
|
5
5
|
import type { CoreSessionEvent } from "../types/events";
|
|
@@ -54,7 +54,7 @@ export interface SessionManager {
|
|
|
54
54
|
getAccumulatedUsage(
|
|
55
55
|
sessionId: string,
|
|
56
56
|
): Promise<SessionAccumulatedUsage | undefined>;
|
|
57
|
-
abort(sessionId: string): Promise<void>;
|
|
57
|
+
abort(sessionId: string, reason?: unknown): Promise<void>;
|
|
58
58
|
stop(sessionId: string): Promise<void>;
|
|
59
59
|
dispose(reason?: string): Promise<void>;
|
|
60
60
|
get(sessionId: string): Promise<SessionRecord | undefined>;
|