@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,515 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
AgentTeamsRuntime,
|
|
5
|
+
bootstrapAgentTeams,
|
|
6
|
+
type TeamEvent,
|
|
7
|
+
type TeamTeammateSpec,
|
|
8
|
+
type Tool,
|
|
9
|
+
} from "@clinebot/agents";
|
|
10
|
+
import { resolveSkillsConfigSearchPaths } from "@clinebot/shared/storage";
|
|
11
|
+
import { nanoid } from "nanoid";
|
|
12
|
+
import {
|
|
13
|
+
createUserInstructionConfigWatcher,
|
|
14
|
+
type SkillConfig,
|
|
15
|
+
type UserInstructionConfigWatcher,
|
|
16
|
+
} from "../agents";
|
|
17
|
+
import {
|
|
18
|
+
createBuiltinTools,
|
|
19
|
+
type SkillsExecutor,
|
|
20
|
+
type ToolExecutors,
|
|
21
|
+
ToolPresets,
|
|
22
|
+
} from "../default-tools";
|
|
23
|
+
import { SqliteTeamStore } from "../storage/sqlite-team-store";
|
|
24
|
+
import type { CoreAgentMode, CoreSessionConfig } from "../types/config";
|
|
25
|
+
import type {
|
|
26
|
+
RuntimeBuilder,
|
|
27
|
+
RuntimeBuilderInput,
|
|
28
|
+
BuiltRuntime as RuntimeEnvironment,
|
|
29
|
+
} from "./session-runtime";
|
|
30
|
+
|
|
31
|
+
type SkillsExecutorMetadataItem = {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
disabled: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const WORKSPACE_CONFIGURATION_MARKER = "# Workspace Configuration";
|
|
39
|
+
|
|
40
|
+
type SkillsExecutorWithMetadata = SkillsExecutor & {
|
|
41
|
+
configuredSkills?: SkillsExecutorMetadataItem[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function createTeamName(): string {
|
|
45
|
+
return `agent-team-${nanoid(5)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createBuiltinToolsList(
|
|
49
|
+
cwd: string,
|
|
50
|
+
mode: CoreAgentMode,
|
|
51
|
+
skillsExecutor?: SkillsExecutorWithMetadata,
|
|
52
|
+
executorOverrides?: Partial<ToolExecutors>,
|
|
53
|
+
): Tool[] {
|
|
54
|
+
const preset =
|
|
55
|
+
mode === "plan" ? ToolPresets.readonly : ToolPresets.development;
|
|
56
|
+
return createBuiltinTools({
|
|
57
|
+
cwd,
|
|
58
|
+
...preset,
|
|
59
|
+
enableSkills: !!skillsExecutor,
|
|
60
|
+
executors: {
|
|
61
|
+
...(skillsExecutor
|
|
62
|
+
? {
|
|
63
|
+
skills: skillsExecutor,
|
|
64
|
+
}
|
|
65
|
+
: {}),
|
|
66
|
+
...(executorOverrides ?? {}),
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const SKILL_FILE_NAME = "SKILL.md";
|
|
72
|
+
|
|
73
|
+
function listAvailableSkillNames(
|
|
74
|
+
watcher: UserInstructionConfigWatcher,
|
|
75
|
+
): string[] {
|
|
76
|
+
return listConfiguredSkills(watcher)
|
|
77
|
+
.filter((skill) => !skill.disabled)
|
|
78
|
+
.map((skill) => skill.name.trim())
|
|
79
|
+
.filter((name) => name.length > 0)
|
|
80
|
+
.sort((a, b) => a.localeCompare(b));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function listConfiguredSkills(
|
|
84
|
+
watcher: UserInstructionConfigWatcher,
|
|
85
|
+
): SkillsExecutorMetadataItem[] {
|
|
86
|
+
const snapshot = watcher.getSnapshot("skill");
|
|
87
|
+
return [...snapshot.entries()].map(([id, record]) => {
|
|
88
|
+
const skill = record.item as SkillConfig;
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
name: skill.name.trim(),
|
|
92
|
+
description: skill.description?.trim(),
|
|
93
|
+
disabled: skill.disabled === true,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function hasSkillsFiles(workspacePath: string): boolean {
|
|
99
|
+
for (const directoryPath of resolveSkillsConfigSearchPaths(workspacePath)) {
|
|
100
|
+
if (!existsSync(directoryPath)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const directSkillPath = join(directoryPath, SKILL_FILE_NAME);
|
|
105
|
+
if (existsSync(directSkillPath)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const entries = readdirSync(directoryPath, { withFileTypes: true });
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
if (!entry.isDirectory()) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (existsSync(join(directoryPath, entry.name, SKILL_FILE_NAME))) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Ignore inaccessible directories while probing for local skills.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resolveSkillRecord(
|
|
128
|
+
watcher: UserInstructionConfigWatcher,
|
|
129
|
+
requestedSkill: string,
|
|
130
|
+
): { id: string; skill: SkillConfig } | { error: string } {
|
|
131
|
+
const normalized = requestedSkill.trim().replace(/^\/+/, "").toLowerCase();
|
|
132
|
+
if (!normalized) {
|
|
133
|
+
return { error: "Missing skill name." };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const snapshot = watcher.getSnapshot("skill");
|
|
137
|
+
const exact = snapshot.get(normalized);
|
|
138
|
+
if (exact) {
|
|
139
|
+
const skill = exact.item as SkillConfig;
|
|
140
|
+
if (skill.disabled === true) {
|
|
141
|
+
return {
|
|
142
|
+
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
id: normalized,
|
|
147
|
+
skill,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const bareName = normalized.includes(":")
|
|
152
|
+
? (normalized.split(":").at(-1) ?? normalized)
|
|
153
|
+
: normalized;
|
|
154
|
+
|
|
155
|
+
const suffixMatches = [...snapshot.entries()].filter(([id]) => {
|
|
156
|
+
if (id === bareName) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return id.endsWith(`:${bareName}`);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (suffixMatches.length === 1) {
|
|
163
|
+
const [id, record] = suffixMatches[0];
|
|
164
|
+
const skill = record.item as SkillConfig;
|
|
165
|
+
if (skill.disabled === true) {
|
|
166
|
+
return {
|
|
167
|
+
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
id,
|
|
172
|
+
skill,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (suffixMatches.length > 1) {
|
|
177
|
+
return {
|
|
178
|
+
error: `Skill "${requestedSkill}" is ambiguous. Use one of: ${suffixMatches.map(([id]) => id).join(", ")}`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const available = listAvailableSkillNames(watcher);
|
|
183
|
+
return {
|
|
184
|
+
error:
|
|
185
|
+
available.length > 0
|
|
186
|
+
? `Skill "${requestedSkill}" not found. Available skills: ${available.join(", ")}`
|
|
187
|
+
: "No skills are currently available.",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function createSkillsExecutor(
|
|
192
|
+
watcher: UserInstructionConfigWatcher,
|
|
193
|
+
watcherReady: Promise<void>,
|
|
194
|
+
): SkillsExecutorWithMetadata {
|
|
195
|
+
const runningSkills = new Set<string>();
|
|
196
|
+
const executor: SkillsExecutorWithMetadata = async (skillName, args) => {
|
|
197
|
+
await watcherReady;
|
|
198
|
+
const resolved = resolveSkillRecord(watcher, skillName);
|
|
199
|
+
if ("error" in resolved) {
|
|
200
|
+
return resolved.error;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { id, skill } = resolved;
|
|
204
|
+
if (runningSkills.has(id)) {
|
|
205
|
+
return `Skill "${skill.name}" is already running.`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
runningSkills.add(id);
|
|
209
|
+
try {
|
|
210
|
+
const trimmedArgs = args?.trim();
|
|
211
|
+
const argsTag = trimmedArgs
|
|
212
|
+
? `\n<command-args>${trimmedArgs}</command-args>`
|
|
213
|
+
: "";
|
|
214
|
+
const description = skill.description?.trim()
|
|
215
|
+
? `Description: ${skill.description.trim()}\n\n`
|
|
216
|
+
: "";
|
|
217
|
+
|
|
218
|
+
return `<command-name>${skill.name}</command-name>${argsTag}\n<command-instructions>\n${description}${skill.instructions}\n</command-instructions>`;
|
|
219
|
+
} finally {
|
|
220
|
+
runningSkills.delete(id);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
Object.defineProperty(executor, "configuredSkills", {
|
|
224
|
+
get: () => listConfiguredSkills(watcher),
|
|
225
|
+
enumerable: true,
|
|
226
|
+
configurable: false,
|
|
227
|
+
});
|
|
228
|
+
return executor;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function shutdownTeamRuntime(
|
|
232
|
+
teamRuntime: AgentTeamsRuntime | undefined,
|
|
233
|
+
reason: string,
|
|
234
|
+
): void {
|
|
235
|
+
if (!teamRuntime) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
for (const teammateId of teamRuntime.getTeammateIds()) {
|
|
239
|
+
try {
|
|
240
|
+
teamRuntime.shutdownTeammate(teammateId, reason);
|
|
241
|
+
} catch {
|
|
242
|
+
// Best-effort shutdown for all teammates.
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function extractWorkspaceMetadataFromSystemPrompt(
|
|
248
|
+
systemPrompt: string,
|
|
249
|
+
): string | undefined {
|
|
250
|
+
const markerIndex = systemPrompt.lastIndexOf(WORKSPACE_CONFIGURATION_MARKER);
|
|
251
|
+
if (markerIndex < 0) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const metadata = systemPrompt.slice(markerIndex).trim();
|
|
255
|
+
return metadata.length > 0 ? metadata : undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function normalizeConfig(
|
|
259
|
+
config: CoreSessionConfig,
|
|
260
|
+
): Required<
|
|
261
|
+
Pick<
|
|
262
|
+
CoreSessionConfig,
|
|
263
|
+
| "mode"
|
|
264
|
+
| "enableTools"
|
|
265
|
+
| "enableSpawnAgent"
|
|
266
|
+
| "enableAgentTeams"
|
|
267
|
+
| "missionLogIntervalSteps"
|
|
268
|
+
| "missionLogIntervalMs"
|
|
269
|
+
>
|
|
270
|
+
> {
|
|
271
|
+
return {
|
|
272
|
+
mode: config.mode === "plan" ? "plan" : "act",
|
|
273
|
+
enableTools: config.enableTools !== false,
|
|
274
|
+
enableSpawnAgent: config.enableSpawnAgent !== false,
|
|
275
|
+
enableAgentTeams: config.enableAgentTeams !== false,
|
|
276
|
+
missionLogIntervalSteps:
|
|
277
|
+
typeof config.missionLogIntervalSteps === "number" &&
|
|
278
|
+
Number.isFinite(config.missionLogIntervalSteps)
|
|
279
|
+
? config.missionLogIntervalSteps
|
|
280
|
+
: 3,
|
|
281
|
+
missionLogIntervalMs:
|
|
282
|
+
typeof config.missionLogIntervalMs === "number" &&
|
|
283
|
+
Number.isFinite(config.missionLogIntervalMs)
|
|
284
|
+
? config.missionLogIntervalMs
|
|
285
|
+
: 120000,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
290
|
+
build(input: RuntimeBuilderInput): RuntimeEnvironment {
|
|
291
|
+
const {
|
|
292
|
+
config,
|
|
293
|
+
hooks,
|
|
294
|
+
extensions,
|
|
295
|
+
logger,
|
|
296
|
+
createSpawnTool,
|
|
297
|
+
onTeamRestored,
|
|
298
|
+
userInstructionWatcher: sharedUserInstructionWatcher,
|
|
299
|
+
defaultToolExecutors,
|
|
300
|
+
} = input;
|
|
301
|
+
const onTeamEvent = input.onTeamEvent ?? (() => {});
|
|
302
|
+
const normalized = normalizeConfig(config);
|
|
303
|
+
const tools: Tool[] = [];
|
|
304
|
+
const effectiveTeamName = config.teamName?.trim() || createTeamName();
|
|
305
|
+
let teamToolsRegistered = false;
|
|
306
|
+
const watcherProvided = Boolean(sharedUserInstructionWatcher);
|
|
307
|
+
let userInstructionWatcher = sharedUserInstructionWatcher;
|
|
308
|
+
let watcherReady = Promise.resolve();
|
|
309
|
+
let skillsExecutor: SkillsExecutorWithMetadata | undefined;
|
|
310
|
+
|
|
311
|
+
if (
|
|
312
|
+
!userInstructionWatcher &&
|
|
313
|
+
normalized.enableTools &&
|
|
314
|
+
hasSkillsFiles(config.cwd)
|
|
315
|
+
) {
|
|
316
|
+
userInstructionWatcher = createUserInstructionConfigWatcher({
|
|
317
|
+
skills: { workspacePath: config.cwd },
|
|
318
|
+
rules: { workspacePath: config.cwd },
|
|
319
|
+
workflows: { workspacePath: config.cwd },
|
|
320
|
+
});
|
|
321
|
+
watcherReady = userInstructionWatcher.start().catch(() => {});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (
|
|
325
|
+
normalized.enableTools &&
|
|
326
|
+
userInstructionWatcher &&
|
|
327
|
+
(watcherProvided ||
|
|
328
|
+
hasSkillsFiles(config.cwd) ||
|
|
329
|
+
listConfiguredSkills(userInstructionWatcher).length > 0)
|
|
330
|
+
) {
|
|
331
|
+
skillsExecutor = createSkillsExecutor(
|
|
332
|
+
userInstructionWatcher,
|
|
333
|
+
watcherReady,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (normalized.enableTools) {
|
|
338
|
+
tools.push(
|
|
339
|
+
...createBuiltinToolsList(
|
|
340
|
+
config.cwd,
|
|
341
|
+
normalized.mode,
|
|
342
|
+
skillsExecutor,
|
|
343
|
+
defaultToolExecutors,
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let teamRuntime: AgentTeamsRuntime | undefined;
|
|
349
|
+
const teamStore = normalized.enableAgentTeams
|
|
350
|
+
? new SqliteTeamStore()
|
|
351
|
+
: undefined;
|
|
352
|
+
teamStore?.init();
|
|
353
|
+
const restoredTeam = teamStore?.loadRuntime(effectiveTeamName);
|
|
354
|
+
const restoredTeamState = restoredTeam?.state;
|
|
355
|
+
const restoredTeammateSpecs = restoredTeam?.teammates ?? [];
|
|
356
|
+
const teammateSpecs = new Map(
|
|
357
|
+
restoredTeammateSpecs.map((spec) => [spec.agentId, spec] as const),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const ensureTeamRuntime = (): AgentTeamsRuntime | undefined => {
|
|
361
|
+
if (!normalized.enableAgentTeams) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!teamRuntime) {
|
|
366
|
+
teamRuntime = new AgentTeamsRuntime({
|
|
367
|
+
teamName: effectiveTeamName,
|
|
368
|
+
leadAgentId: "lead",
|
|
369
|
+
missionLogIntervalSteps: normalized.missionLogIntervalSteps,
|
|
370
|
+
missionLogIntervalMs: normalized.missionLogIntervalMs,
|
|
371
|
+
onTeamEvent: (event: TeamEvent) => {
|
|
372
|
+
onTeamEvent(event);
|
|
373
|
+
if (teamRuntime && teamStore) {
|
|
374
|
+
if (
|
|
375
|
+
event.type === "teammate_spawned" &&
|
|
376
|
+
event.teammate?.rolePrompt
|
|
377
|
+
) {
|
|
378
|
+
const spec: TeamTeammateSpec = {
|
|
379
|
+
agentId: event.agentId,
|
|
380
|
+
rolePrompt: event.teammate.rolePrompt,
|
|
381
|
+
modelId: event.teammate.modelId,
|
|
382
|
+
maxIterations: event.teammate.maxIterations,
|
|
383
|
+
};
|
|
384
|
+
teammateSpecs.set(spec.agentId, spec);
|
|
385
|
+
}
|
|
386
|
+
if (event.type === "teammate_shutdown") {
|
|
387
|
+
teammateSpecs.delete(event.agentId);
|
|
388
|
+
}
|
|
389
|
+
teamStore.handleTeamEvent(effectiveTeamName, event);
|
|
390
|
+
teamStore.persistRuntime(
|
|
391
|
+
effectiveTeamName,
|
|
392
|
+
teamRuntime.exportState(),
|
|
393
|
+
Array.from(teammateSpecs.values()),
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
if (restoredTeamState) {
|
|
399
|
+
teamRuntime.hydrateState(restoredTeamState);
|
|
400
|
+
teamRuntime.markStaleRunsInterrupted("runtime_recovered");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!teamToolsRegistered) {
|
|
405
|
+
if (!teamRuntime) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
teamToolsRegistered = true;
|
|
409
|
+
|
|
410
|
+
const teamBootstrap = bootstrapAgentTeams({
|
|
411
|
+
runtime: teamRuntime,
|
|
412
|
+
leadAgentId: "lead",
|
|
413
|
+
restoredFromPersistence: Boolean(restoredTeamState),
|
|
414
|
+
restoredTeammates: restoredTeammateSpecs,
|
|
415
|
+
createBaseTools: normalized.enableTools
|
|
416
|
+
? () =>
|
|
417
|
+
createBuiltinToolsList(
|
|
418
|
+
config.cwd,
|
|
419
|
+
normalized.mode,
|
|
420
|
+
skillsExecutor,
|
|
421
|
+
defaultToolExecutors,
|
|
422
|
+
)
|
|
423
|
+
: undefined,
|
|
424
|
+
teammateRuntime: {
|
|
425
|
+
providerId: config.providerId,
|
|
426
|
+
modelId: config.modelId,
|
|
427
|
+
cwd: config.cwd,
|
|
428
|
+
apiKey: config.apiKey ?? "",
|
|
429
|
+
baseUrl: config.baseUrl,
|
|
430
|
+
headers: config.headers,
|
|
431
|
+
providerConfig: config.providerConfig,
|
|
432
|
+
knownModels: config.knownModels,
|
|
433
|
+
thinking: config.thinking,
|
|
434
|
+
clineWorkspaceMetadata:
|
|
435
|
+
config.providerId === "cline"
|
|
436
|
+
? extractWorkspaceMetadataFromSystemPrompt(config.systemPrompt)
|
|
437
|
+
: undefined,
|
|
438
|
+
maxIterations: config.maxIterations,
|
|
439
|
+
hooks,
|
|
440
|
+
extensions: extensions ?? config.extensions,
|
|
441
|
+
logger: logger ?? config.logger,
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
if (teamBootstrap.restoredFromPersistence) {
|
|
446
|
+
onTeamRestored?.();
|
|
447
|
+
}
|
|
448
|
+
tools.push(...teamBootstrap.tools);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return teamRuntime;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
if (normalized.enableSpawnAgent && createSpawnTool) {
|
|
455
|
+
const spawnTool = createSpawnTool();
|
|
456
|
+
tools.push({
|
|
457
|
+
...spawnTool,
|
|
458
|
+
execute: async (spawnInput, context) => {
|
|
459
|
+
ensureTeamRuntime();
|
|
460
|
+
return spawnTool.execute(spawnInput, context);
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (normalized.enableAgentTeams) {
|
|
466
|
+
ensureTeamRuntime();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const completionGuard = teamRuntime
|
|
470
|
+
? () => {
|
|
471
|
+
const rt = teamRuntime;
|
|
472
|
+
if (!rt) return undefined;
|
|
473
|
+
const tasks = rt.listTasks();
|
|
474
|
+
const hasInProgress = tasks.some(
|
|
475
|
+
(t) => t.status === "in_progress" || t.status === "pending",
|
|
476
|
+
);
|
|
477
|
+
const runs = rt.listRuns({});
|
|
478
|
+
const hasActiveRuns = runs.some(
|
|
479
|
+
(r) => r.status === "running" || r.status === "queued",
|
|
480
|
+
);
|
|
481
|
+
if (hasInProgress || hasActiveRuns) {
|
|
482
|
+
const pending = tasks
|
|
483
|
+
.filter(
|
|
484
|
+
(t) => t.status === "in_progress" || t.status === "pending",
|
|
485
|
+
)
|
|
486
|
+
.map((t) => `${t.id} (${t.status}): ${t.title}`)
|
|
487
|
+
.join(", ");
|
|
488
|
+
const activeRunSummary = runs
|
|
489
|
+
.filter((r) => r.status === "running" || r.status === "queued")
|
|
490
|
+
.map((r) => `${r.id} (${r.status})`)
|
|
491
|
+
.join(", ");
|
|
492
|
+
const parts = [];
|
|
493
|
+
if (pending) parts.push(`Unfinished tasks: ${pending}`);
|
|
494
|
+
if (activeRunSummary)
|
|
495
|
+
parts.push(`Active runs: ${activeRunSummary}`);
|
|
496
|
+
return `[SYSTEM] You still have team obligations. ${parts.join(". ")}. Use team_run_task to delegate work, or team_complete_task to mark tasks done, or team_await_run / team_await_all_runs to wait for active runs. Do NOT stop until all tasks are completed.`;
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
: undefined;
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
tools,
|
|
504
|
+
logger: logger ?? config.logger,
|
|
505
|
+
teamRuntime,
|
|
506
|
+
completionGuard,
|
|
507
|
+
shutdown: (reason: string) => {
|
|
508
|
+
shutdownTeamRuntime(teamRuntime, reason);
|
|
509
|
+
if (!watcherProvided) {
|
|
510
|
+
userInstructionWatcher?.stop();
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { Tool } from "@clinebot/agents";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { createBuiltinTools } from "../default-tools";
|
|
7
|
+
import { DefaultRuntimeBuilder } from "./runtime-builder";
|
|
8
|
+
|
|
9
|
+
type LegacyConfig = {
|
|
10
|
+
providerId: string;
|
|
11
|
+
modelId: string;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
systemPrompt: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
enableTools: boolean;
|
|
16
|
+
enableSpawnAgent: boolean;
|
|
17
|
+
enableAgentTeams: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function legacyBuiltinTools(cwd: string): Tool[] {
|
|
21
|
+
return createBuiltinTools({
|
|
22
|
+
cwd,
|
|
23
|
+
enableReadFiles: true,
|
|
24
|
+
enableSearch: true,
|
|
25
|
+
enableBash: true,
|
|
26
|
+
enableWebFetch: true,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function legacyBuildRuntimeEnvironment(
|
|
31
|
+
config: LegacyConfig,
|
|
32
|
+
createSpawnTool?: () => Tool,
|
|
33
|
+
): Tool[] {
|
|
34
|
+
const tools: Tool[] = [];
|
|
35
|
+
if (config.enableTools) {
|
|
36
|
+
tools.push(...legacyBuiltinTools(config.cwd));
|
|
37
|
+
}
|
|
38
|
+
if (config.enableSpawnAgent && createSpawnTool) {
|
|
39
|
+
const spawnTool = createSpawnTool();
|
|
40
|
+
tools.push({
|
|
41
|
+
...spawnTool,
|
|
42
|
+
execute: async (input, context) => spawnTool.execute(input, context),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return tools;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeEmptyWorkspaceCwd(): string {
|
|
49
|
+
return mkdtempSync(join(tmpdir(), "runtime-parity-"));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function makeSpawnTool(): Tool {
|
|
53
|
+
return {
|
|
54
|
+
name: "spawn_agent",
|
|
55
|
+
description: "Spawn a subagent",
|
|
56
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
57
|
+
execute: async () => ({ ok: true }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("runtime tool parity", () => {
|
|
62
|
+
it("matches legacy tool list when tools+spawn are enabled", () => {
|
|
63
|
+
const config: LegacyConfig = {
|
|
64
|
+
providerId: "anthropic",
|
|
65
|
+
modelId: "claude-sonnet-4-6",
|
|
66
|
+
apiKey: "key",
|
|
67
|
+
systemPrompt: "test",
|
|
68
|
+
cwd: makeEmptyWorkspaceCwd(),
|
|
69
|
+
enableTools: true,
|
|
70
|
+
enableSpawnAgent: true,
|
|
71
|
+
enableAgentTeams: false,
|
|
72
|
+
};
|
|
73
|
+
const createSpawnTool = makeSpawnTool;
|
|
74
|
+
const expected = legacyBuildRuntimeEnvironment(config, createSpawnTool).map(
|
|
75
|
+
(tool) => tool.name,
|
|
76
|
+
);
|
|
77
|
+
const actual = new DefaultRuntimeBuilder()
|
|
78
|
+
.build({
|
|
79
|
+
config,
|
|
80
|
+
createSpawnTool,
|
|
81
|
+
})
|
|
82
|
+
.tools.map((tool) => tool.name);
|
|
83
|
+
|
|
84
|
+
expect(actual).toEqual(expected);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("matches legacy tool list when only spawn is enabled", () => {
|
|
88
|
+
const config: LegacyConfig = {
|
|
89
|
+
providerId: "anthropic",
|
|
90
|
+
modelId: "claude-sonnet-4-6",
|
|
91
|
+
apiKey: "key",
|
|
92
|
+
systemPrompt: "test",
|
|
93
|
+
cwd: makeEmptyWorkspaceCwd(),
|
|
94
|
+
enableTools: false,
|
|
95
|
+
enableSpawnAgent: true,
|
|
96
|
+
enableAgentTeams: false,
|
|
97
|
+
};
|
|
98
|
+
const createSpawnTool = makeSpawnTool;
|
|
99
|
+
const expected = legacyBuildRuntimeEnvironment(config, createSpawnTool).map(
|
|
100
|
+
(tool) => tool.name,
|
|
101
|
+
);
|
|
102
|
+
const actual = new DefaultRuntimeBuilder()
|
|
103
|
+
.build({
|
|
104
|
+
config,
|
|
105
|
+
createSpawnTool,
|
|
106
|
+
})
|
|
107
|
+
.tools.map((tool) => tool.name);
|
|
108
|
+
|
|
109
|
+
expect(actual).toEqual(expected);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("matches legacy tool list when tools+spawn are disabled", () => {
|
|
113
|
+
const config: LegacyConfig = {
|
|
114
|
+
providerId: "anthropic",
|
|
115
|
+
modelId: "claude-sonnet-4-6",
|
|
116
|
+
apiKey: "key",
|
|
117
|
+
systemPrompt: "test",
|
|
118
|
+
cwd: makeEmptyWorkspaceCwd(),
|
|
119
|
+
enableTools: false,
|
|
120
|
+
enableSpawnAgent: false,
|
|
121
|
+
enableAgentTeams: false,
|
|
122
|
+
};
|
|
123
|
+
const expected = legacyBuildRuntimeEnvironment(config).map(
|
|
124
|
+
(tool) => tool.name,
|
|
125
|
+
);
|
|
126
|
+
const actual = new DefaultRuntimeBuilder()
|
|
127
|
+
.build({ config })
|
|
128
|
+
.tools.map((tool) => tool.name);
|
|
129
|
+
|
|
130
|
+
expect(actual).toEqual(expected);
|
|
131
|
+
});
|
|
132
|
+
});
|