@ai-sdk/harness-cline 1.0.60 → 1.0.61

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/index.js CHANGED
@@ -1,2144 +1,1886 @@
1
- // src/cline-harness.ts
2
- import {
3
- commonTool,
4
- HARNESS_V1_BUILTIN_TOOLS
5
- } from "@ai-sdk/harness";
6
- import { isHarnessAuthenticationEnvironment as isHarnessAuthenticationEnvironment3 } from "@ai-sdk/harness/utils";
7
- import { tool } from "@ai-sdk/provider-utils";
8
- import { z as z2 } from "zod/v4";
9
-
10
- // src/cline-subscription.ts
11
- import { homedir } from "os";
12
- import { join, resolve } from "path";
13
- import {
14
- isHarnessAuthenticationEnvironment as isHarnessAuthenticationEnvironment2,
15
- shouldResolveNativeSubscription
16
- } from "@ai-sdk/harness/utils";
17
- import {
18
- Llms,
19
- ProviderSettingsManager,
20
- RuntimeOAuthTokenManager
21
- } from "@cline/core";
22
-
23
- // src/cline-auth.ts
24
- import {
25
- getAiGatewayAuthFromEnv,
26
- isHarnessAuthenticationEnvironment
27
- } from "@ai-sdk/harness/utils";
28
- function resolveClineEnv({
29
- auth = "auto",
30
- env = process.env
31
- }) {
32
- if (isHarnessAuthenticationEnvironment(auth)) {
33
- const gatewayAuth2 = getAiGatewayAuthFromEnv({ env: auth });
34
- return gatewayAuth2.apiKey ? toGatewayClineEnv(gatewayAuth2) : pickDirectClineEnv({ env: auth });
35
- }
36
- if (auth === "direct") {
37
- return pickDirectClineEnv({ env });
38
- }
39
- const gatewayAuth = getAiGatewayAuthFromEnv({ env });
40
- if (auth === "ai-gateway" || gatewayAuth.apiKey) {
41
- return toGatewayClineEnv(gatewayAuth);
42
- }
43
- return pickDirectClineEnv({ env });
1
+ import { HARNESS_V1_BUILTIN_TOOLS, HarnessCapabilityUnsupportedError, commonTool } from "@ai-sdk/harness";
2
+ import { getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isHarnessAuthenticationEnvironment, resolveSandboxHomeDir, shellQuote, shouldResolveNativeSubscription } from "@ai-sdk/harness/utils";
3
+ import { safeParseJSON, secureJsonParse, tool } from "@ai-sdk/provider-utils";
4
+ import { z } from "zod/v4";
5
+ import { homedir } from "node:os";
6
+ import path, { join, resolve } from "node:path";
7
+ import { Llms, ProviderSettingsManager, RuntimeOAuthTokenManager, createDefaultTools } from "@cline/core";
8
+ import { createHash } from "node:crypto";
9
+ import { Agent, createTool } from "@cline/agents";
10
+ //#region src/cline-auth.ts
11
+ function resolveClineEnv({ auth = "auto", env = process.env }) {
12
+ if (isHarnessAuthenticationEnvironment(auth)) {
13
+ const gatewayAuth = getAiGatewayAuthFromEnv({ env: auth });
14
+ return gatewayAuth.apiKey ? toGatewayClineEnv(gatewayAuth) : pickDirectClineEnv({ env: auth });
15
+ }
16
+ if (auth === "direct") return pickDirectClineEnv({ env });
17
+ const gatewayAuth = getAiGatewayAuthFromEnv({ env });
18
+ if (auth === "ai-gateway" || gatewayAuth.apiKey) return toGatewayClineEnv(gatewayAuth);
19
+ return pickDirectClineEnv({ env });
44
20
  }
45
21
  function toGatewayClineEnv(gatewayAuth) {
46
- return {
47
- ...gatewayAuth.apiKey ? { AI_GATEWAY_API_KEY: gatewayAuth.apiKey } : {},
48
- AI_GATEWAY_BASE_URL: gatewayAuth.baseUrl
49
- };
22
+ return {
23
+ ...gatewayAuth.apiKey ? { AI_GATEWAY_API_KEY: gatewayAuth.apiKey } : {},
24
+ AI_GATEWAY_BASE_URL: gatewayAuth.baseUrl
25
+ };
50
26
  }
51
- function pickDirectClineEnv({
52
- env
53
- }) {
54
- return {
55
- ...env.CLINE_API_KEY ? { CLINE_API_KEY: env.CLINE_API_KEY } : {},
56
- ...env.CLINE_API_BASE_URL ? { CLINE_API_BASE_URL: env.CLINE_API_BASE_URL } : {}
57
- };
27
+ function pickDirectClineEnv({ env }) {
28
+ return {
29
+ ...env.CLINE_API_KEY ? { CLINE_API_KEY: env.CLINE_API_KEY } : {},
30
+ ...env.CLINE_API_BASE_URL ? { CLINE_API_BASE_URL: env.CLINE_API_BASE_URL } : {}
31
+ };
58
32
  }
59
-
60
- // src/cline-subscription.ts
61
- async function resolveClineAuthentication({
62
- auth,
63
- env = process.env,
64
- providerId = "cline",
65
- apiKey,
66
- readSubscription = readClineSubscription
67
- }) {
68
- const environment = resolveClineEnv({ auth, env });
69
- if (isHarnessAuthenticationEnvironment2(auth)) {
70
- return { environment };
71
- }
72
- if (!shouldResolveNativeSubscription({
73
- auth,
74
- env: environment,
75
- hasDirectCredential: apiKey != null || hasApplicableProviderApiKey({ providerId, env })
76
- })) {
77
- return { environment };
78
- }
79
- const subscriptionApiKey = await readSubscription({ providerId, env });
80
- return subscriptionApiKey == null ? { environment } : { environment, subscriptionApiKey };
33
+ //#endregion
34
+ //#region src/cline-subscription.ts
35
+ async function resolveClineAuthentication({ auth, env = process.env, providerId = "cline", apiKey, readSubscription = readClineSubscription }) {
36
+ const environment = resolveClineEnv({
37
+ auth,
38
+ env
39
+ });
40
+ if (isHarnessAuthenticationEnvironment(auth)) return { environment };
41
+ if (!shouldResolveNativeSubscription({
42
+ auth,
43
+ env: environment,
44
+ hasDirectCredential: apiKey != null || hasApplicableProviderApiKey({
45
+ providerId,
46
+ env
47
+ })
48
+ })) return { environment };
49
+ const subscriptionApiKey = await readSubscription({
50
+ providerId,
51
+ env
52
+ });
53
+ return subscriptionApiKey == null ? { environment } : {
54
+ environment,
55
+ subscriptionApiKey
56
+ };
81
57
  }
82
- async function readClineSubscription({
83
- providerId,
84
- env = process.env,
85
- homeDirectory = homedir()
86
- }) {
87
- const filePath = resolveClineProviderSettingsPath({
88
- env,
89
- homeDirectory
90
- });
91
- const manager = new ProviderSettingsManager({ filePath });
92
- const entry = manager.read().providers[providerId];
93
- if (entry?.tokenSource !== "oauth") return void 0;
94
- const resolution = await new RuntimeOAuthTokenManager({
95
- providerSettingsManager: manager
96
- }).resolveProviderApiKey({ providerId });
97
- return resolution?.apiKey;
58
+ async function readClineSubscription({ providerId, env = process.env, homeDirectory = homedir() }) {
59
+ const filePath = resolveClineProviderSettingsPath({
60
+ env,
61
+ homeDirectory
62
+ });
63
+ const manager = new ProviderSettingsManager({ filePath });
64
+ if (manager.read().providers[providerId]?.tokenSource !== "oauth") return void 0;
65
+ return (await new RuntimeOAuthTokenManager({ providerSettingsManager: manager }).resolveProviderApiKey({ providerId }))?.apiKey;
98
66
  }
99
- function resolveClineProviderSettingsPath({
100
- env,
101
- homeDirectory
102
- }) {
103
- const explicitPath = env.CLINE_PROVIDER_SETTINGS_PATH?.trim();
104
- if (explicitPath) return resolve(explicitPath);
105
- const dataDirectory = env.CLINE_DATA_DIR?.trim();
106
- return join(
107
- dataDirectory ? resolve(dataDirectory) : join(homeDirectory, ".cline", "data"),
108
- "settings",
109
- "providers.json"
110
- );
67
+ function resolveClineProviderSettingsPath({ env, homeDirectory }) {
68
+ const explicitPath = env.CLINE_PROVIDER_SETTINGS_PATH?.trim();
69
+ if (explicitPath) return resolve(explicitPath);
70
+ const dataDirectory = env.CLINE_DATA_DIR?.trim();
71
+ return join(dataDirectory ? resolve(dataDirectory) : join(homeDirectory, ".cline", "data"), "settings", "providers.json");
111
72
  }
112
- function hasApplicableProviderApiKey({
113
- providerId,
114
- env
115
- }) {
116
- if (providerId === "cline" && env.CLINE_API_KEY != null) return true;
117
- return Llms.getProviderCollectionSync(providerId)?.provider.env?.some(
118
- (name) => env[name] != null
119
- ) ?? false;
73
+ function hasApplicableProviderApiKey({ providerId, env }) {
74
+ if (providerId === "cline" && env.CLINE_API_KEY != null) return true;
75
+ return Llms.getProviderCollectionSync(providerId)?.provider.env?.some((name) => env[name] != null) ?? false;
120
76
  }
121
-
122
- // src/cline-resume-state.ts
123
- import { createHash } from "crypto";
124
- import path from "path";
125
- import {
126
- safeParseJSON
127
- } from "@ai-sdk/provider-utils";
128
- import { z } from "zod/v4";
129
- var CLINE_HISTORY_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
77
+ //#endregion
78
+ //#region src/cline-resume-state.ts
79
+ const CLINE_HISTORY_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
130
80
  function safeClineHistoryFileName(historyFileName) {
131
- if (!CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName)) {
132
- throw new Error(`Invalid Cline history file name: ${historyFileName}`);
133
- }
134
- return historyFileName;
81
+ if (!CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName)) throw new Error(`Invalid Cline history file name: ${historyFileName}`);
82
+ return historyFileName;
135
83
  }
136
- var clineHistoryFileNameSchema = z.string().refine(
137
- (historyFileName) => CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName),
138
- "Cline historyFileName must be a safe .json basename."
139
- );
140
- var clineResumeStateSchema = z.looseObject({
141
- historyFileName: clineHistoryFileNameSchema.optional()
142
- });
143
- var CLINE_DEFAULT_HISTORY_FILE_NAME = "history.json";
84
+ const clineHistoryFileNameSchema = z.string().refine((historyFileName) => CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName), "Cline historyFileName must be a safe .json basename.");
85
+ /**
86
+ * Schema for the adapter-specific portion of lifecycle state `data` produced
87
+ * by the Cline harness's resumable lifecycle methods. Carries the basename of
88
+ * the serialized conversation history. The actual history bytes live in a
89
+ * private, session-scoped directory under sandbox HOME so they survive
90
+ * cross-process resume without appearing in the agent workspace.
91
+ */
92
+ const clineResumeStateSchema = z.looseObject({ historyFileName: clineHistoryFileNameSchema.optional() });
93
+ const CLINE_DEFAULT_HISTORY_FILE_NAME = "history.json";
144
94
  function resolveClinePrivateSessionDirectory(input) {
145
- const sessionKey = createHash("sha256").update(input.sessionId).digest("hex");
146
- const privateSessionDir = path.posix.join(
147
- input.sandboxHomeDir,
148
- ".ai-sdk",
149
- "harness-cline",
150
- sessionKey
151
- );
152
- const relativePath = path.posix.relative(
153
- input.sessionWorkDir,
154
- privateSessionDir
155
- );
156
- if (relativePath === "" || !relativePath.startsWith("../") && !path.posix.isAbsolute(relativePath)) {
157
- throw new Error(
158
- `Cline private session directory ${JSON.stringify(privateSessionDir)} must be outside sessionWorkDir ${JSON.stringify(input.sessionWorkDir)}.`
159
- );
160
- }
161
- return privateSessionDir;
95
+ const sessionKey = createHash("sha256").update(input.sessionId).digest("hex");
96
+ const privateSessionDir = path.posix.join(input.sandboxHomeDir, ".ai-sdk", "harness-cline", sessionKey);
97
+ const relativePath = path.posix.relative(input.sessionWorkDir, privateSessionDir);
98
+ if (relativePath === "" || !relativePath.startsWith("../") && !path.posix.isAbsolute(relativePath)) throw new Error(`Cline private session directory ${JSON.stringify(privateSessionDir)} must be outside sessionWorkDir ${JSON.stringify(input.sessionWorkDir)}.`);
99
+ return privateSessionDir;
162
100
  }
163
101
  function resolveContainedSandboxPath(input) {
164
- const historyDir = path.posix.resolve(input.privateSessionDir);
165
- const filePath = path.posix.resolve(
166
- historyDir,
167
- safeClineHistoryFileName(input.historyFileName)
168
- );
169
- const relativePath = path.posix.relative(historyDir, filePath);
170
- if (relativePath === "" || relativePath.startsWith("..") || path.posix.isAbsolute(relativePath)) {
171
- throw new Error(
172
- `Invalid Cline history file name: ${input.historyFileName}`
173
- );
174
- }
175
- return filePath;
102
+ const historyDir = path.posix.resolve(input.privateSessionDir);
103
+ const filePath = path.posix.resolve(historyDir, safeClineHistoryFileName(input.historyFileName));
104
+ const relativePath = path.posix.relative(historyDir, filePath);
105
+ if (relativePath === "" || relativePath.startsWith("..") || path.posix.isAbsolute(relativePath)) throw new Error(`Invalid Cline history file name: ${input.historyFileName}`);
106
+ return filePath;
176
107
  }
108
+ /**
109
+ * Persist the runtime's conversation history into private sandbox state so a
110
+ * future process can resume the session after
111
+ * `HarnessV1SandboxProvider.resume?.({ sessionId })` reattaches the sandbox.
112
+ */
177
113
  async function persistHistoryToSandbox(args) {
178
- const remotePath = resolveContainedSandboxPath({
179
- privateSessionDir: args.privateSessionDir,
180
- historyFileName: args.historyFileName
181
- });
182
- await args.sandbox.writeTextFile({
183
- path: remotePath,
184
- content: JSON.stringify(args.messages),
185
- ...args.abortSignal ? { abortSignal: args.abortSignal } : {}
186
- });
114
+ const remotePath = resolveContainedSandboxPath({
115
+ privateSessionDir: args.privateSessionDir,
116
+ historyFileName: args.historyFileName
117
+ });
118
+ await args.sandbox.writeTextFile({
119
+ path: remotePath,
120
+ content: JSON.stringify(args.messages),
121
+ ...args.abortSignal ? { abortSignal: args.abortSignal } : {}
122
+ });
187
123
  }
124
+ /**
125
+ * Pull a previously persisted conversation history from the sandbox. Returns
126
+ * `undefined` when the file is missing or unparsable — resume then falls back
127
+ * to a fresh conversation rather than failing `doStart`.
128
+ */
188
129
  async function pullHistoryFromSandbox(args) {
189
- const remotePath = resolveContainedSandboxPath({
190
- privateSessionDir: args.privateSessionDir,
191
- historyFileName: args.historyFileName
192
- });
193
- const content = await args.sandbox.readTextFile({
194
- path: remotePath,
195
- ...args.abortSignal ? { abortSignal: args.abortSignal } : {}
196
- });
197
- if (content == null) return void 0;
198
- const parsed = await safeParseJSON({ text: content });
199
- return parsed.success && Array.isArray(parsed.value) ? parsed.value : void 0;
130
+ const remotePath = resolveContainedSandboxPath({
131
+ privateSessionDir: args.privateSessionDir,
132
+ historyFileName: args.historyFileName
133
+ });
134
+ const content = await args.sandbox.readTextFile({
135
+ path: remotePath,
136
+ ...args.abortSignal ? { abortSignal: args.abortSignal } : {}
137
+ });
138
+ if (content == null) return void 0;
139
+ const parsed = await safeParseJSON({ text: content });
140
+ return parsed.success && Array.isArray(parsed.value) ? parsed.value : void 0;
200
141
  }
201
-
202
- // src/cline-session.ts
203
- import {
204
- HarnessCapabilityUnsupportedError as HarnessCapabilityUnsupportedError2
205
- } from "@ai-sdk/harness";
206
- import {
207
- getRestrictedSandboxSession,
208
- resolveSandboxHomeDir
209
- } from "@ai-sdk/harness/utils";
210
- import {
211
- Agent,
212
- createTool as createTool2
213
- } from "@cline/agents";
214
- import { Llms as Llms2 } from "@cline/core";
215
-
216
- // src/cline-mcp.ts
217
- var CLINE_CORE_PACKAGE = "@cline/core";
142
+ //#endregion
143
+ //#region src/cline-mcp.ts
144
+ const CLINE_CORE_PACKAGE = "@cline/core";
218
145
  function isRecord(value) {
219
- return value != null && typeof value === "object" && !Array.isArray(value);
146
+ return value != null && typeof value === "object" && !Array.isArray(value);
220
147
  }
221
148
  function optionalStringArray(value) {
222
- return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
149
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
223
150
  }
224
151
  function optionalStringRecord(value) {
225
- return isRecord(value) && Object.values(value).every((item) => typeof item === "string") ? value : void 0;
152
+ return isRecord(value) && Object.values(value).every((item) => typeof item === "string") ? value : void 0;
226
153
  }
227
154
  function resolveTransport(input) {
228
- if (isRecord(input.config.transport)) {
229
- return input.config.transport;
230
- }
231
- if (typeof input.config.command === "string") {
232
- const args = optionalStringArray(input.config.args);
233
- const env = optionalStringRecord(input.config.env);
234
- return {
235
- type: "stdio",
236
- command: input.config.command,
237
- ...args ? { args } : {},
238
- ...typeof input.config.cwd === "string" ? { cwd: input.config.cwd } : {},
239
- ...env ? { env } : {}
240
- };
241
- }
242
- if (typeof input.config.url === "string") {
243
- const configuredType = input.config.type ?? input.config.transportType;
244
- const headers = optionalStringRecord(input.config.headers);
245
- return {
246
- type: configuredType === "http" || configuredType === "streamableHttp" ? "streamableHttp" : "sse",
247
- url: input.config.url,
248
- ...headers ? { headers } : {}
249
- };
250
- }
251
- throw new Error(
252
- `Cline MCP server ${JSON.stringify(input.serverName)} must define a transport, command, or URL.`
253
- );
155
+ if (isRecord(input.config.transport)) return input.config.transport;
156
+ if (typeof input.config.command === "string") {
157
+ const args = optionalStringArray(input.config.args);
158
+ const env = optionalStringRecord(input.config.env);
159
+ return {
160
+ type: "stdio",
161
+ command: input.config.command,
162
+ ...args ? { args } : {},
163
+ ...typeof input.config.cwd === "string" ? { cwd: input.config.cwd } : {},
164
+ ...env ? { env } : {}
165
+ };
166
+ }
167
+ if (typeof input.config.url === "string") {
168
+ const configuredType = input.config.type ?? input.config.transportType;
169
+ const headers = optionalStringRecord(input.config.headers);
170
+ return {
171
+ type: configuredType === "http" || configuredType === "streamableHttp" ? "streamableHttp" : "sse",
172
+ url: input.config.url,
173
+ ...headers ? { headers } : {}
174
+ };
175
+ }
176
+ throw new Error(`Cline MCP server ${JSON.stringify(input.serverName)} must define a transport, command, or URL.`);
254
177
  }
255
178
  function resolveRegistration(input) {
256
- if (!isRecord(input.config)) {
257
- throw new Error(
258
- `Cline MCP server ${JSON.stringify(input.serverName)} must be configured with an object value.`
259
- );
260
- }
261
- return {
262
- name: input.serverName,
263
- transport: resolveTransport({
264
- serverName: input.serverName,
265
- config: input.config
266
- }),
267
- ...typeof input.config.disabled === "boolean" ? { disabled: input.config.disabled } : {},
268
- ...isRecord(input.config.metadata) ? { metadata: input.config.metadata } : {},
269
- ...isRecord(input.config.oauth) ? { oauth: input.config.oauth } : {}
270
- };
179
+ if (!isRecord(input.config)) throw new Error(`Cline MCP server ${JSON.stringify(input.serverName)} must be configured with an object value.`);
180
+ return {
181
+ name: input.serverName,
182
+ transport: resolveTransport({
183
+ serverName: input.serverName,
184
+ config: input.config
185
+ }),
186
+ ...typeof input.config.disabled === "boolean" ? { disabled: input.config.disabled } : {},
187
+ ...isRecord(input.config.metadata) ? { metadata: input.config.metadata } : {},
188
+ ...isRecord(input.config.oauth) ? { oauth: input.config.oauth } : {}
189
+ };
271
190
  }
272
191
  async function createClineMcpRuntime(input) {
273
- if (input.mcpServers == null || Object.keys(input.mcpServers).length === 0) {
274
- return {
275
- tools: [],
276
- toolNames: /* @__PURE__ */ new Set(),
277
- dispose: async () => {
278
- }
279
- };
280
- }
281
- const {
282
- createDefaultMcpServerClientFactory,
283
- createMcpTools,
284
- InMemoryMcpManager
285
- } = await import(CLINE_CORE_PACKAGE);
286
- const manager = new InMemoryMcpManager({
287
- clientFactory: createDefaultMcpServerClientFactory()
288
- });
289
- const tools = [];
290
- try {
291
- for (const [serverName, config] of Object.entries(input.mcpServers)) {
292
- const registration = resolveRegistration({ serverName, config });
293
- await manager.registerServer(registration);
294
- if (registration.disabled) continue;
295
- tools.push(
296
- ...await createMcpTools({
297
- serverName,
298
- provider: manager
299
- })
300
- );
301
- }
302
- } catch (error) {
303
- await manager.dispose().catch(() => {
304
- });
305
- throw error;
306
- }
307
- return {
308
- tools,
309
- toolNames: new Set(tools.map((tool2) => tool2.name)),
310
- dispose: async () => manager.dispose()
311
- };
192
+ if (input.mcpServers == null || Object.keys(input.mcpServers).length === 0) return {
193
+ tools: [],
194
+ toolNames: /* @__PURE__ */ new Set(),
195
+ dispose: async () => {}
196
+ };
197
+ const { createDefaultMcpServerClientFactory, createMcpTools, InMemoryMcpManager } = await import(CLINE_CORE_PACKAGE);
198
+ const manager = new InMemoryMcpManager({ clientFactory: createDefaultMcpServerClientFactory() });
199
+ const tools = [];
200
+ try {
201
+ for (const [serverName, config] of Object.entries(input.mcpServers)) {
202
+ const registration = resolveRegistration({
203
+ serverName,
204
+ config
205
+ });
206
+ await manager.registerServer(registration);
207
+ if (registration.disabled) continue;
208
+ tools.push(...await createMcpTools({
209
+ serverName,
210
+ provider: manager
211
+ }));
212
+ }
213
+ } catch (error) {
214
+ await manager.dispose().catch(() => {});
215
+ throw error;
216
+ }
217
+ return {
218
+ tools,
219
+ toolNames: new Set(tools.map((tool) => tool.name)),
220
+ dispose: async () => manager.dispose()
221
+ };
312
222
  }
313
-
314
- // src/cline-remote-ops.ts
315
- import path2 from "path";
316
- import { shellQuote } from "@ai-sdk/harness/utils";
223
+ //#endregion
224
+ //#region src/cline-remote-ops.ts
317
225
  function createClineRemoteOps(options) {
318
- const { sandbox, workDir } = options;
319
- const normalizedWorkDir = path2.posix.normalize(workDir);
320
- const isInsidePath = ({
321
- parent,
322
- candidate
323
- }) => {
324
- const relative = path2.posix.relative(parent, candidate);
325
- return relative === "" || relative !== ".." && !relative.startsWith("../") && !path2.posix.isAbsolute(relative);
326
- };
327
- const assertWorkspacePath = (inputPath) => {
328
- const normalized = path2.posix.normalize(inputPath);
329
- if (!isInsidePath({ parent: normalizedWorkDir, candidate: normalized })) {
330
- throw new Error(`Cline path escapes the workspace: ${inputPath}`);
331
- }
332
- return normalized;
333
- };
334
- const resolvePath = (inputPath) => {
335
- const resolved = path2.posix.isAbsolute(inputPath) ? path2.posix.normalize(inputPath) : path2.posix.normalize(path2.posix.join(normalizedWorkDir, inputPath));
336
- return assertWorkspacePath(resolved);
337
- };
338
- const runShell = async ({
339
- command,
340
- signal
341
- }) => {
342
- const result = await sandbox.run({
343
- command,
344
- workingDirectory: normalizedWorkDir,
345
- ...signal ? { abortSignal: signal } : {}
346
- });
347
- return {
348
- exitCode: result.exitCode,
349
- output: `${result.stdout}${result.stderr}`
350
- };
351
- };
352
- const lastOutputLine = (output) => output.trim().split("\n").filter(Boolean).at(-1);
353
- const resolveExistingSandboxPath = async ({
354
- remotePath,
355
- inputPath,
356
- missingMessage
357
- }) => {
358
- const result = await runShell({
359
- command: [
360
- `target=${shellQuote(remotePath)}`,
361
- 'if [ ! -e "$target" ]; then echo "__CLINE_REALPATH_NOT_FOUND__"; exit 2; fi',
362
- 'resolved=$(realpath "$target" 2>/dev/null) || { echo "__CLINE_REALPATH_FAILED__"; exit 3; }',
363
- `printf '%s\\n' "$resolved"`
364
- ].join("; ")
365
- });
366
- if (result.output.includes("__CLINE_REALPATH_NOT_FOUND__")) {
367
- throw new Error(missingMessage ?? `Path not found: ${inputPath}`);
368
- }
369
- if (result.output.includes("__CLINE_REALPATH_FAILED__") || result.exitCode !== 0) {
370
- throw new Error(`Unable to resolve path: ${inputPath}`);
371
- }
372
- const resolvedPath = lastOutputLine(result.output);
373
- if (!resolvedPath) {
374
- throw new Error(`Unable to resolve path: ${inputPath}`);
375
- }
376
- return resolvedPath;
377
- };
378
- const resolveReadableSandboxPath = async ({
379
- remotePath,
380
- inputPath,
381
- missingMessage
382
- }) => assertWorkspacePath(
383
- await resolveExistingSandboxPath({
384
- remotePath,
385
- inputPath,
386
- ...missingMessage ? { missingMessage } : {}
387
- })
388
- );
389
- const resolveWritableSandboxPath = async ({
390
- remotePath,
391
- inputPath
392
- }) => {
393
- const result = await runShell({
394
- command: [
395
- `target=${shellQuote(remotePath)}`,
396
- `if [ -e "$target" ] || [ -L "$target" ]; then resolved=$(realpath "$target" 2>/dev/null) || { echo "__CLINE_REALPATH_FAILED__"; exit 3; }; printf '%s\\n' "$resolved"; exit 0; fi`,
397
- 'dir=$(dirname "$target")',
398
- 'base=$(basename "$target")',
399
- 'missing="$base"',
400
- 'while [ ! -e "$dir" ] && [ ! -L "$dir" ]; do parent=$(dirname "$dir"); if [ "$parent" = "$dir" ]; then echo "__CLINE_REALPATH_NOT_FOUND__"; exit 2; fi; missing="$(basename "$dir")/$missing"; dir="$parent"; done',
401
- 'resolved_dir=$(realpath "$dir" 2>/dev/null) || { echo "__CLINE_REALPATH_FAILED__"; exit 3; }',
402
- `printf '%s/%s\\n' "$resolved_dir" "$missing"`
403
- ].join("; ")
404
- });
405
- if (result.output.includes("__CLINE_REALPATH_NOT_FOUND__") || result.output.includes("__CLINE_REALPATH_FAILED__") || result.exitCode !== 0) {
406
- throw new Error(`Unable to resolve path: ${inputPath}`);
407
- }
408
- const resolvedPath = lastOutputLine(result.output);
409
- if (!resolvedPath) {
410
- throw new Error(`Unable to resolve path: ${inputPath}`);
411
- }
412
- return assertWorkspacePath(resolvedPath);
413
- };
414
- const readFile = async (inputPath) => {
415
- const remotePath = resolvePath(inputPath);
416
- const resolved = await resolveReadableSandboxPath({
417
- remotePath,
418
- inputPath,
419
- missingMessage: `File not found: ${inputPath}`
420
- });
421
- const content = await sandbox.readTextFile({ path: resolved });
422
- if (content == null) {
423
- throw new Error(`File not found: ${inputPath}`);
424
- }
425
- return content;
426
- };
427
- const writeFile = async (inputPath, content) => {
428
- const remotePath = resolvePath(inputPath);
429
- const resolved = await resolveWritableSandboxPath({
430
- remotePath,
431
- inputPath
432
- });
433
- await sandbox.writeTextFile({ path: resolved, content });
434
- };
435
- const editFile = async (inputPath, oldText, newText) => {
436
- const remotePath = resolvePath(inputPath);
437
- const resolved = assertWorkspacePath(
438
- await resolveExistingSandboxPath({
439
- remotePath,
440
- inputPath,
441
- missingMessage: `File not found: ${inputPath}`
442
- })
443
- );
444
- const current = await sandbox.readTextFile({ path: resolved });
445
- if (current == null) {
446
- throw new Error(`File not found: ${inputPath}`);
447
- }
448
- const index = current.indexOf(oldText);
449
- if (index === -1) {
450
- throw new Error(`Text to replace was not found in ${inputPath}`);
451
- }
452
- const updated = `${current.slice(0, index)}${newText}${current.slice(
453
- index + oldText.length
454
- )}`;
455
- await writeFile(inputPath, updated);
456
- };
457
- return {
458
- resolvePath,
459
- readFile,
460
- writeFile,
461
- editFile,
462
- async bash(command, input) {
463
- const controller = new AbortController();
464
- const timeoutId = typeof input?.timeout === "number" && input.timeout > 0 ? setTimeout(() => controller.abort(), input.timeout * 1e3) : void 0;
465
- const forwardedSignal = input?.signal;
466
- const onAbort = () => controller.abort();
467
- forwardedSignal?.addEventListener("abort", onAbort, { once: true });
468
- try {
469
- const result = await runShell({
470
- command,
471
- signal: controller.signal
472
- });
473
- return { output: result.output, exitCode: result.exitCode };
474
- } finally {
475
- forwardedSignal?.removeEventListener("abort", onAbort);
476
- if (timeoutId) {
477
- clearTimeout(timeoutId);
478
- }
479
- }
480
- },
481
- async grep(pattern, input = {}) {
482
- const inputPath = input.path ?? ".";
483
- const remotePath = resolvePath(inputPath);
484
- const target = await resolveReadableSandboxPath({
485
- remotePath,
486
- inputPath
487
- });
488
- const flags = [
489
- "-r",
490
- "-n",
491
- "--binary-files=without-match",
492
- ...input.ignoreCase ? ["-i"] : [],
493
- ...input.literal ? ["-F"] : [],
494
- ...typeof input.context === "number" && input.context > 0 ? ["-C", String(input.context)] : [],
495
- ...input.glob ? ["--include", input.glob] : []
496
- ];
497
- const limit = Math.max(1, input.limit ?? 100);
498
- const result = await runShell({
499
- command: [
500
- `if [ ! -e ${shellQuote(
501
- target
502
- )} ]; then echo "__CLINE_GREP_NOT_FOUND__"; exit 2; fi`,
503
- `grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(
504
- pattern
505
- )} ${shellQuote(target)} 2>/dev/null | head -n ${limit}`
506
- ].join("; ")
507
- });
508
- const output = result.output.trim();
509
- if (output.includes("__CLINE_GREP_NOT_FOUND__")) {
510
- throw new Error(`Path not found: ${input.path ?? "."}`);
511
- }
512
- return output || "No matches found";
513
- },
514
- async glob(pattern, inputPath = ".", limit = 1e3) {
515
- const remotePath = resolvePath(inputPath);
516
- const target = await resolveReadableSandboxPath({
517
- remotePath,
518
- inputPath
519
- });
520
- const result = await runShell({
521
- command: [
522
- `if [ ! -e ${shellQuote(
523
- target
524
- )} ]; then echo "__CLINE_FIND_NOT_FOUND__"; exit 2; fi`,
525
- `if [ -d ${shellQuote(target)} ]; then find ${shellQuote(
526
- target
527
- )} -type f -print; else printf '%s\\n' ${shellQuote(target)}; fi`
528
- ].join("; ")
529
- });
530
- const output = result.output.trim();
531
- if (output.includes("__CLINE_FIND_NOT_FOUND__")) {
532
- throw new Error(`Path not found: ${inputPath}`);
533
- }
534
- return output.split("\n").filter(Boolean).map((absolutePath) => {
535
- if (absolutePath === target) {
536
- return path2.posix.basename(absolutePath);
537
- }
538
- return path2.posix.relative(target, absolutePath);
539
- }).filter(
540
- (candidate) => candidate.length > 0 && path2.matchesGlob(candidate, pattern)
541
- ).sort(
542
- (left, right) => left.toLowerCase().localeCompare(right.toLowerCase())
543
- ).slice(0, limit);
544
- },
545
- async ls(inputPath = ".", limit = 500) {
546
- const remotePath = resolvePath(inputPath);
547
- const target = await resolveReadableSandboxPath({
548
- remotePath,
549
- inputPath
550
- });
551
- const result = await runShell({
552
- command: [
553
- `if [ ! -e ${shellQuote(
554
- target
555
- )} ]; then echo "__CLINE_LS_NOT_FOUND__"; exit 2; fi`,
556
- `if [ ! -d ${shellQuote(
557
- target
558
- )} ]; then echo "__CLINE_LS_NOT_DIR__"; exit 3; fi`,
559
- `cd ${shellQuote(target)}`,
560
- "ls -1Ap"
561
- ].join("; ")
562
- });
563
- const output = result.output.trim();
564
- if (output.includes("__CLINE_LS_NOT_FOUND__")) {
565
- throw new Error(`Path not found: ${inputPath}`);
566
- }
567
- if (output.includes("__CLINE_LS_NOT_DIR__")) {
568
- throw new Error(`Not a directory: ${inputPath}`);
569
- }
570
- return output.split("\n").filter(Boolean).map((line) => line.replace(/[*=@|]$/, "")).sort(
571
- (left, right) => left.toLowerCase().localeCompare(right.toLowerCase())
572
- ).slice(0, limit);
573
- }
574
- };
226
+ const { sandbox, workDir } = options;
227
+ const normalizedWorkDir = path.posix.normalize(workDir);
228
+ const isInsidePath = ({ parent, candidate }) => {
229
+ const relative = path.posix.relative(parent, candidate);
230
+ return relative === "" || relative !== ".." && !relative.startsWith("../") && !path.posix.isAbsolute(relative);
231
+ };
232
+ const assertWorkspacePath = (inputPath) => {
233
+ const normalized = path.posix.normalize(inputPath);
234
+ if (!isInsidePath({
235
+ parent: normalizedWorkDir,
236
+ candidate: normalized
237
+ })) throw new Error(`Cline path escapes the workspace: ${inputPath}`);
238
+ return normalized;
239
+ };
240
+ const resolvePath = (inputPath) => {
241
+ const resolved = path.posix.isAbsolute(inputPath) ? path.posix.normalize(inputPath) : path.posix.normalize(path.posix.join(normalizedWorkDir, inputPath));
242
+ return assertWorkspacePath(resolved);
243
+ };
244
+ const runShell = async ({ command, signal }) => {
245
+ const result = await sandbox.run({
246
+ command,
247
+ workingDirectory: normalizedWorkDir,
248
+ ...signal ? { abortSignal: signal } : {}
249
+ });
250
+ return {
251
+ exitCode: result.exitCode,
252
+ output: `${result.stdout}${result.stderr}`
253
+ };
254
+ };
255
+ const lastOutputLine = (output) => output.trim().split("\n").filter(Boolean).at(-1);
256
+ const resolveExistingSandboxPath = async ({ remotePath, inputPath, missingMessage }) => {
257
+ const result = await runShell({ command: [
258
+ `target=${shellQuote(remotePath)}`,
259
+ "if [ ! -e \"$target\" ]; then echo \"__CLINE_REALPATH_NOT_FOUND__\"; exit 2; fi",
260
+ "resolved=$(realpath \"$target\" 2>/dev/null) || { echo \"__CLINE_REALPATH_FAILED__\"; exit 3; }",
261
+ "printf '%s\\n' \"$resolved\""
262
+ ].join("; ") });
263
+ if (result.output.includes("__CLINE_REALPATH_NOT_FOUND__")) throw new Error(missingMessage ?? `Path not found: ${inputPath}`);
264
+ if (result.output.includes("__CLINE_REALPATH_FAILED__") || result.exitCode !== 0) throw new Error(`Unable to resolve path: ${inputPath}`);
265
+ const resolvedPath = lastOutputLine(result.output);
266
+ if (!resolvedPath) throw new Error(`Unable to resolve path: ${inputPath}`);
267
+ return resolvedPath;
268
+ };
269
+ const resolveReadableSandboxPath = async ({ remotePath, inputPath, missingMessage }) => assertWorkspacePath(await resolveExistingSandboxPath({
270
+ remotePath,
271
+ inputPath,
272
+ ...missingMessage ? { missingMessage } : {}
273
+ }));
274
+ const resolveWritableSandboxPath = async ({ remotePath, inputPath }) => {
275
+ const result = await runShell({ command: [
276
+ `target=${shellQuote(remotePath)}`,
277
+ "if [ -e \"$target\" ] || [ -L \"$target\" ]; then resolved=$(realpath \"$target\" 2>/dev/null) || { echo \"__CLINE_REALPATH_FAILED__\"; exit 3; }; printf '%s\\n' \"$resolved\"; exit 0; fi",
278
+ "dir=$(dirname \"$target\")",
279
+ "base=$(basename \"$target\")",
280
+ "missing=\"$base\"",
281
+ "while [ ! -e \"$dir\" ] && [ ! -L \"$dir\" ]; do parent=$(dirname \"$dir\"); if [ \"$parent\" = \"$dir\" ]; then echo \"__CLINE_REALPATH_NOT_FOUND__\"; exit 2; fi; missing=\"$(basename \"$dir\")/$missing\"; dir=\"$parent\"; done",
282
+ "resolved_dir=$(realpath \"$dir\" 2>/dev/null) || { echo \"__CLINE_REALPATH_FAILED__\"; exit 3; }",
283
+ "printf '%s/%s\\n' \"$resolved_dir\" \"$missing\""
284
+ ].join("; ") });
285
+ if (result.output.includes("__CLINE_REALPATH_NOT_FOUND__") || result.output.includes("__CLINE_REALPATH_FAILED__") || result.exitCode !== 0) throw new Error(`Unable to resolve path: ${inputPath}`);
286
+ const resolvedPath = lastOutputLine(result.output);
287
+ if (!resolvedPath) throw new Error(`Unable to resolve path: ${inputPath}`);
288
+ return assertWorkspacePath(resolvedPath);
289
+ };
290
+ const readFile = async (inputPath) => {
291
+ const remotePath = resolvePath(inputPath);
292
+ const resolved = await resolveReadableSandboxPath({
293
+ remotePath,
294
+ inputPath,
295
+ missingMessage: `File not found: ${inputPath}`
296
+ });
297
+ const content = await sandbox.readTextFile({ path: resolved });
298
+ if (content == null) throw new Error(`File not found: ${inputPath}`);
299
+ return content;
300
+ };
301
+ const writeFile = async (inputPath, content) => {
302
+ const remotePath = resolvePath(inputPath);
303
+ const resolved = await resolveWritableSandboxPath({
304
+ remotePath,
305
+ inputPath
306
+ });
307
+ await sandbox.writeTextFile({
308
+ path: resolved,
309
+ content
310
+ });
311
+ };
312
+ const editFile = async (inputPath, oldText, newText) => {
313
+ const remotePath = resolvePath(inputPath);
314
+ const resolved = assertWorkspacePath(await resolveExistingSandboxPath({
315
+ remotePath,
316
+ inputPath,
317
+ missingMessage: `File not found: ${inputPath}`
318
+ }));
319
+ const current = await sandbox.readTextFile({ path: resolved });
320
+ if (current == null) throw new Error(`File not found: ${inputPath}`);
321
+ const index = current.indexOf(oldText);
322
+ if (index === -1) throw new Error(`Text to replace was not found in ${inputPath}`);
323
+ const updated = `${current.slice(0, index)}${newText}${current.slice(index + oldText.length)}`;
324
+ await writeFile(inputPath, updated);
325
+ };
326
+ return {
327
+ resolvePath,
328
+ readFile,
329
+ writeFile,
330
+ editFile,
331
+ async bash(command, input) {
332
+ const controller = new AbortController();
333
+ const timeoutId = typeof input?.timeout === "number" && input.timeout > 0 ? setTimeout(() => controller.abort(), input.timeout * 1e3) : void 0;
334
+ const forwardedSignal = input?.signal;
335
+ const onAbort = () => controller.abort();
336
+ forwardedSignal?.addEventListener("abort", onAbort, { once: true });
337
+ try {
338
+ const result = await runShell({
339
+ command,
340
+ signal: controller.signal
341
+ });
342
+ return {
343
+ output: result.output,
344
+ exitCode: result.exitCode
345
+ };
346
+ } finally {
347
+ forwardedSignal?.removeEventListener("abort", onAbort);
348
+ if (timeoutId) clearTimeout(timeoutId);
349
+ }
350
+ },
351
+ async grep(pattern, input = {}) {
352
+ const inputPath = input.path ?? ".";
353
+ const remotePath = resolvePath(inputPath);
354
+ const target = await resolveReadableSandboxPath({
355
+ remotePath,
356
+ inputPath
357
+ });
358
+ const flags = [
359
+ "-r",
360
+ "-n",
361
+ "--binary-files=without-match",
362
+ ...input.ignoreCase ? ["-i"] : [],
363
+ ...input.literal ? ["-F"] : [],
364
+ ...typeof input.context === "number" && input.context > 0 ? ["-C", String(input.context)] : [],
365
+ ...input.glob ? ["--include", input.glob] : []
366
+ ];
367
+ const limit = Math.max(1, input.limit ?? 100);
368
+ const output = (await runShell({ command: [`if [ ! -e ${shellQuote(target)} ]; then echo "__CLINE_GREP_NOT_FOUND__"; exit 2; fi`, `grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(pattern)} ${shellQuote(target)} 2>/dev/null | head -n ${limit}`].join("; ") })).output.trim();
369
+ if (output.includes("__CLINE_GREP_NOT_FOUND__")) throw new Error(`Path not found: ${input.path ?? "."}`);
370
+ return output || "No matches found";
371
+ },
372
+ async glob(pattern, inputPath = ".", limit = 1e3) {
373
+ const remotePath = resolvePath(inputPath);
374
+ const target = await resolveReadableSandboxPath({
375
+ remotePath,
376
+ inputPath
377
+ });
378
+ const output = (await runShell({ command: [`if [ ! -e ${shellQuote(target)} ]; then echo "__CLINE_FIND_NOT_FOUND__"; exit 2; fi`, `if [ -d ${shellQuote(target)} ]; then find ${shellQuote(target)} -type f -print; else printf '%s\\n' ${shellQuote(target)}; fi`].join("; ") })).output.trim();
379
+ if (output.includes("__CLINE_FIND_NOT_FOUND__")) throw new Error(`Path not found: ${inputPath}`);
380
+ return output.split("\n").filter(Boolean).map((absolutePath) => {
381
+ if (absolutePath === target) return path.posix.basename(absolutePath);
382
+ return path.posix.relative(target, absolutePath);
383
+ }).filter((candidate) => candidate.length > 0 && path.matchesGlob(candidate, pattern)).sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase())).slice(0, limit);
384
+ },
385
+ async ls(inputPath = ".", limit = 500) {
386
+ const remotePath = resolvePath(inputPath);
387
+ const target = await resolveReadableSandboxPath({
388
+ remotePath,
389
+ inputPath
390
+ });
391
+ const output = (await runShell({ command: [
392
+ `if [ ! -e ${shellQuote(target)} ]; then echo "__CLINE_LS_NOT_FOUND__"; exit 2; fi`,
393
+ `if [ ! -d ${shellQuote(target)} ]; then echo "__CLINE_LS_NOT_DIR__"; exit 3; fi`,
394
+ `cd ${shellQuote(target)}`,
395
+ "ls -1Ap"
396
+ ].join("; ") })).output.trim();
397
+ if (output.includes("__CLINE_LS_NOT_FOUND__")) throw new Error(`Path not found: ${inputPath}`);
398
+ if (output.includes("__CLINE_LS_NOT_DIR__")) throw new Error(`Not a directory: ${inputPath}`);
399
+ return output.split("\n").filter(Boolean).map((line) => line.replace(/[*=@|]$/, "")).sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase())).slice(0, limit);
400
+ }
401
+ };
575
402
  }
576
-
577
- // src/cline-skills.ts
578
- import path3 from "path";
579
- import { createDefaultTools } from "@cline/core";
580
- var CLINE_SKILL_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
581
- function createClineSkillsRuntime({
582
- skills
583
- }) {
584
- const projectedSkills = projectClineSkills({ skills });
585
- const signature = JSON.stringify(projectedSkills);
586
- if (projectedSkills.length === 0) {
587
- return { signature };
588
- }
589
- const executor = async (...executorArguments) => {
590
- const [requestedSkill, args] = executorArguments;
591
- const skill = projectedSkills.find(
592
- (candidate) => candidate.id === normalizeSkillToken(requestedSkill)
593
- );
594
- if (skill == null) {
595
- const availableSkills = projectedSkills.map((skill2) => skill2.name);
596
- return availableSkills.length > 0 ? `Skill "${requestedSkill}" not found. Available skills: ${availableSkills.join(", ")}` : "No skills are currently available.";
597
- }
598
- return renderSkillInstructions({ skill, args });
599
- };
600
- executor.configuredSkills = projectedSkills.map((skill) => ({
601
- id: skill.id,
602
- name: skill.name,
603
- description: skill.description,
604
- disabled: false
605
- }));
606
- const tool2 = createDefaultTools({
607
- executors: { skills: executor },
608
- enableReadFiles: false,
609
- enableSearch: false,
610
- enableBash: false,
611
- enableWebFetch: false,
612
- enableApplyPatch: false,
613
- enableEditor: false,
614
- enableSkills: true,
615
- enableAskQuestion: false,
616
- enableSubmitAndExit: false
617
- }).find((tool3) => tool3.name === "skills");
618
- if (tool2 == null) {
619
- throw new Error("Cline did not create its native skills tool.");
620
- }
621
- return { signature, tool: tool2 };
403
+ //#endregion
404
+ //#region src/cline-skills.ts
405
+ const CLINE_SKILL_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
406
+ /**
407
+ * Build Cline's native skills tool around an immutable in-host projection of
408
+ * the harness skills. The low-level Agent does not discover skills itself, so
409
+ * the adapter provides the same tool contract that Cline's core orchestration
410
+ * layer uses without involving the sandbox filesystem.
411
+ */
412
+ function createClineSkillsRuntime({ skills }) {
413
+ const projectedSkills = projectClineSkills({ skills });
414
+ const signature = JSON.stringify(projectedSkills);
415
+ if (projectedSkills.length === 0) return { signature };
416
+ const executor = async (...executorArguments) => {
417
+ const [requestedSkill, args] = executorArguments;
418
+ const skill = projectedSkills.find((candidate) => candidate.id === normalizeSkillToken(requestedSkill));
419
+ if (skill == null) {
420
+ const availableSkills = projectedSkills.map((skill) => skill.name);
421
+ return availableSkills.length > 0 ? `Skill "${requestedSkill}" not found. Available skills: ${availableSkills.join(", ")}` : "No skills are currently available.";
422
+ }
423
+ return renderSkillInstructions({
424
+ skill,
425
+ args
426
+ });
427
+ };
428
+ executor.configuredSkills = projectedSkills.map((skill) => ({
429
+ id: skill.id,
430
+ name: skill.name,
431
+ description: skill.description,
432
+ disabled: false
433
+ }));
434
+ const tool = createDefaultTools({
435
+ executors: { skills: executor },
436
+ enableReadFiles: false,
437
+ enableSearch: false,
438
+ enableBash: false,
439
+ enableWebFetch: false,
440
+ enableApplyPatch: false,
441
+ enableEditor: false,
442
+ enableSkills: true,
443
+ enableAskQuestion: false,
444
+ enableSubmitAndExit: false
445
+ }).find((tool) => tool.name === "skills");
446
+ if (tool == null) throw new Error("Cline did not create its native skills tool.");
447
+ return {
448
+ signature,
449
+ tool
450
+ };
622
451
  }
623
- function projectClineSkills({
624
- skills
625
- }) {
626
- const names = /* @__PURE__ */ new Set();
627
- const ids = /* @__PURE__ */ new Set();
628
- return skills.map((skill) => {
629
- if (!CLINE_SKILL_NAME_PATTERN.test(skill.name) || skill.name === "." || skill.name === "..") {
630
- throw new Error(`Invalid Cline skill name: ${skill.name}`);
631
- }
632
- if (names.has(skill.name)) {
633
- throw new Error(`Duplicate Cline skill name: ${skill.name}`);
634
- }
635
- names.add(skill.name);
636
- const id = normalizeSkillToken(skill.name);
637
- if (ids.has(id)) {
638
- throw new Error(`Duplicate Cline skill identifier: ${id}`);
639
- }
640
- ids.add(id);
641
- return {
642
- id,
643
- name: skill.name,
644
- description: skill.description,
645
- content: skill.content,
646
- files: (skill.files ?? []).map((file) => ({
647
- path: normalizeSkillFilePath({
648
- skillName: skill.name,
649
- filePath: file.path
650
- }),
651
- content: file.content
652
- })).sort(
653
- (left, right) => left.path.localeCompare(right.path) || left.content.localeCompare(right.content)
654
- )
655
- };
656
- }).sort((left, right) => left.name.localeCompare(right.name));
452
+ function projectClineSkills({ skills }) {
453
+ const names = /* @__PURE__ */ new Set();
454
+ const ids = /* @__PURE__ */ new Set();
455
+ return skills.map((skill) => {
456
+ if (!CLINE_SKILL_NAME_PATTERN.test(skill.name) || skill.name === "." || skill.name === "..") throw new Error(`Invalid Cline skill name: ${skill.name}`);
457
+ if (names.has(skill.name)) throw new Error(`Duplicate Cline skill name: ${skill.name}`);
458
+ names.add(skill.name);
459
+ const id = normalizeSkillToken(skill.name);
460
+ if (ids.has(id)) throw new Error(`Duplicate Cline skill identifier: ${id}`);
461
+ ids.add(id);
462
+ return {
463
+ id,
464
+ name: skill.name,
465
+ description: skill.description,
466
+ content: skill.content,
467
+ files: (skill.files ?? []).map((file) => ({
468
+ path: normalizeSkillFilePath({
469
+ skillName: skill.name,
470
+ filePath: file.path
471
+ }),
472
+ content: file.content
473
+ })).sort((left, right) => left.path.localeCompare(right.path) || left.content.localeCompare(right.content))
474
+ };
475
+ }).sort((left, right) => left.name.localeCompare(right.name));
657
476
  }
658
477
  function normalizeSkillToken(skill) {
659
- return skill.trim().replace(/^\/+/, "").toLowerCase();
478
+ return skill.trim().replace(/^\/+/, "").toLowerCase();
660
479
  }
661
- function normalizeSkillFilePath({
662
- skillName,
663
- filePath
664
- }) {
665
- const normalized = path3.posix.normalize(filePath);
666
- if (normalized === "" || normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..") || path3.posix.isAbsolute(normalized)) {
667
- throw new Error(
668
- `Invalid Cline skill file path for ${skillName}: ${filePath}`
669
- );
670
- }
671
- return normalized;
480
+ function normalizeSkillFilePath({ skillName, filePath }) {
481
+ const normalized = path.posix.normalize(filePath);
482
+ if (normalized === "" || normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..") || path.posix.isAbsolute(normalized)) throw new Error(`Invalid Cline skill file path for ${skillName}: ${filePath}`);
483
+ return normalized;
672
484
  }
673
- function renderSkillInstructions({
674
- skill,
675
- args
676
- }) {
677
- const trimmedArgs = args?.trim();
678
- const argsTag = trimmedArgs ? `
679
- <command-args>${trimmedArgs}</command-args>` : "";
680
- const description = skill.description.trim() ? `Description: ${skill.description.trim()}
681
-
682
- ` : "";
683
- const files = skill.files.length === 0 ? "" : `
684
-
685
- <skill-files>
686
- ${skill.files.map(
687
- (file) => `<skill-file path=${JSON.stringify(file.path)}>
688
- ${file.content}
689
- </skill-file>`
690
- ).join("\n")}
691
- </skill-files>`;
692
- return `<command-name>${skill.name}</command-name>${argsTag}
693
- <command-instructions>
694
- ${description}${skill.content}${files}
695
- </command-instructions>`;
485
+ function renderSkillInstructions({ skill, args }) {
486
+ const trimmedArgs = args?.trim();
487
+ const argsTag = trimmedArgs ? `\n<command-args>${trimmedArgs}</command-args>` : "";
488
+ const description = skill.description.trim() ? `Description: ${skill.description.trim()}\n\n` : "";
489
+ const files = skill.files.length === 0 ? "" : `\n\n<skill-files>\n${skill.files.map((file) => `<skill-file path=${JSON.stringify(file.path)}>\n${file.content}\n</skill-file>`).join("\n")}\n</skill-files>`;
490
+ return `<command-name>${skill.name}</command-name>${argsTag}\n<command-instructions>\n${description}${skill.content}${files}\n</command-instructions>`;
696
491
  }
697
-
698
- // src/cline-tools.ts
699
- import {
700
- createTool
701
- } from "@cline/agents";
702
- import { createDefaultTools as createDefaultTools2 } from "@cline/core";
703
-
704
- // src/cline-utils.ts
705
- import {
706
- HarnessCapabilityUnsupportedError
707
- } from "@ai-sdk/harness";
708
- import { secureJsonParse } from "@ai-sdk/provider-utils";
709
- var HARNESS_ID = "cline";
492
+ //#endregion
493
+ //#region src/cline-utils.ts
494
+ const HARNESS_ID$1 = "cline";
495
+ /**
496
+ * Extract a single user text string from a `HarnessV1Prompt`. The Cline
497
+ * runtime's `run`/`continue` accept a plain string; multimodal user content
498
+ * is not supported in this foundational version.
499
+ */
710
500
  function extractUserText(prompt) {
711
- if (typeof prompt === "string") {
712
- return prompt;
713
- }
714
- const { content } = prompt;
715
- if (typeof content === "string") {
716
- return content;
717
- }
718
- const parts = [];
719
- for (const part of content) {
720
- if (part.type !== "text") {
721
- throw new HarnessCapabilityUnsupportedError({
722
- message: `cline: only text user-message parts are supported; got '${part.type}'.`,
723
- harnessId: HARNESS_ID
724
- });
725
- }
726
- parts.push(part.text);
727
- }
728
- return parts.join("\n\n");
501
+ if (typeof prompt === "string") return prompt;
502
+ const { content } = prompt;
503
+ if (typeof content === "string") return content;
504
+ const parts = [];
505
+ for (const part of content) {
506
+ if (part.type !== "text") throw new HarnessCapabilityUnsupportedError({
507
+ message: `cline: only text user-message parts are supported; got '${part.type}'.`,
508
+ harnessId: HARNESS_ID$1
509
+ });
510
+ parts.push(part.text);
511
+ }
512
+ return parts.join("\n\n");
729
513
  }
730
514
  function getErrorText(error) {
731
- return error instanceof Error ? error.message : String(error);
515
+ return error instanceof Error ? error.message : String(error);
732
516
  }
517
+ /**
518
+ * Coerce an arbitrary tool output into a JSON-safe value for a `tool-result`
519
+ * stream part. Strings and JSON-serializable objects pass through; anything
520
+ * that can't survive `JSON.stringify` round-tripping (functions, cycles) is
521
+ * stringified. `undefined` becomes `null` — the runtime schema deliberately
522
+ * accepts `null` for tools with no output.
523
+ */
733
524
  function toToolResultValue(output) {
734
- if (output === void 0 || output === null) {
735
- return null;
736
- }
737
- if (typeof output === "string") {
738
- return output;
739
- }
740
- try {
741
- const serialized = JSON.stringify(output);
742
- return serialized === void 0 ? String(output) : secureJsonParse(serialized);
743
- } catch {
744
- return String(output);
745
- }
525
+ if (output === void 0 || output === null) return null;
526
+ if (typeof output === "string") return output;
527
+ try {
528
+ const serialized = JSON.stringify(output);
529
+ return serialized === void 0 ? String(output) : secureJsonParse(serialized);
530
+ } catch {
531
+ return String(output);
532
+ }
746
533
  }
534
+ /**
535
+ * Map the Cline model-level finish reason (`AgentModelFinishReason`) onto the
536
+ * V4 unified finish-reason vocabulary. Used for `finish-step` parts.
537
+ */
747
538
  function mapModelFinishReason(reason) {
748
- switch (reason) {
749
- case "stop":
750
- return { unified: "stop", raw: reason };
751
- case "tool-calls":
752
- return { unified: "tool-calls", raw: reason };
753
- case "max-tokens":
754
- return { unified: "length", raw: reason };
755
- case "error":
756
- return { unified: "error", raw: reason };
757
- default:
758
- return { unified: "other", raw: reason };
759
- }
539
+ switch (reason) {
540
+ case "stop": return {
541
+ unified: "stop",
542
+ raw: reason
543
+ };
544
+ case "tool-calls": return {
545
+ unified: "tool-calls",
546
+ raw: reason
547
+ };
548
+ case "max-tokens": return {
549
+ unified: "length",
550
+ raw: reason
551
+ };
552
+ case "error": return {
553
+ unified: "error",
554
+ raw: reason
555
+ };
556
+ default: return {
557
+ unified: "other",
558
+ raw: reason
559
+ };
560
+ }
760
561
  }
562
+ /**
563
+ * Map a completed run's status onto the V4 unified finish reason. Used for
564
+ * the terminal `finish` part.
565
+ */
761
566
  function mapRunFinishReason(status) {
762
- switch (status) {
763
- case "completed":
764
- return { unified: "stop", raw: status };
765
- case "aborted":
766
- return { unified: "other", raw: status };
767
- case "failed":
768
- return { unified: "error", raw: status };
769
- }
567
+ switch (status) {
568
+ case "completed": return {
569
+ unified: "stop",
570
+ raw: status
571
+ };
572
+ case "aborted": return {
573
+ unified: "other",
574
+ raw: status
575
+ };
576
+ case "failed": return {
577
+ unified: "error",
578
+ raw: status
579
+ };
580
+ }
770
581
  }
582
+ /**
583
+ * Convert the Cline runtime's cumulative `AgentUsage` into the V4 usage shape.
584
+ */
771
585
  function usageFromAgentUsage(usage) {
772
- return {
773
- inputTokens: {
774
- total: usage.inputTokens,
775
- noCache: void 0,
776
- cacheRead: usage.cacheReadTokens,
777
- cacheWrite: usage.cacheWriteTokens
778
- },
779
- outputTokens: {
780
- total: usage.outputTokens,
781
- text: void 0,
782
- reasoning: usage.reasoningTokenCount
783
- },
784
- ...usage.totalCost !== void 0 ? { raw: { totalCost: usage.totalCost } } : {}
785
- };
586
+ return {
587
+ inputTokens: {
588
+ total: usage.inputTokens,
589
+ noCache: void 0,
590
+ cacheRead: usage.cacheReadTokens,
591
+ cacheWrite: usage.cacheWriteTokens
592
+ },
593
+ outputTokens: {
594
+ total: usage.outputTokens,
595
+ text: void 0,
596
+ reasoning: usage.reasoningTokenCount
597
+ },
598
+ ...usage.totalCost !== void 0 ? { raw: { totalCost: usage.totalCost } } : {}
599
+ };
786
600
  }
601
+ /**
602
+ * Convert a single assistant message's per-call metrics into the V4 usage
603
+ * shape. Metrics may be absent; all fields stay optional in that case.
604
+ */
787
605
  function usageFromMessageMetrics(metrics) {
788
- return {
789
- inputTokens: {
790
- total: metrics?.inputTokens,
791
- noCache: void 0,
792
- cacheRead: metrics?.cacheReadTokens,
793
- cacheWrite: metrics?.cacheWriteTokens
794
- },
795
- outputTokens: {
796
- total: metrics?.outputTokens,
797
- text: void 0,
798
- reasoning: metrics?.reasoningTokenCount
799
- }
800
- };
606
+ return {
607
+ inputTokens: {
608
+ total: metrics?.inputTokens,
609
+ noCache: void 0,
610
+ cacheRead: metrics?.cacheReadTokens,
611
+ cacheWrite: metrics?.cacheWriteTokens
612
+ },
613
+ outputTokens: {
614
+ total: metrics?.outputTokens,
615
+ text: void 0,
616
+ reasoning: metrics?.reasoningTokenCount
617
+ }
618
+ };
801
619
  }
802
-
803
- // src/cline-tools.ts
804
- var CLINE_NATIVE_BUILTIN_NAMES = [
805
- "ask_question",
806
- "read",
807
- "write",
808
- "edit",
809
- "bash",
810
- "grep",
811
- "glob",
812
- "ls",
813
- "skills"
620
+ //#endregion
621
+ //#region src/cline-tools.ts
622
+ /**
623
+ * Native names of the Cline harness's built-in tools. The Cline runtime
624
+ * requires snake_case tool names, and every one of these already matches its
625
+ * cross-harness common name, so the public (wire) names and native names are
626
+ * identical.
627
+ */
628
+ const CLINE_NATIVE_BUILTIN_NAMES = [
629
+ "ask_question",
630
+ "read",
631
+ "write",
632
+ "edit",
633
+ "bash",
634
+ "grep",
635
+ "glob",
636
+ "ls",
637
+ "skills"
814
638
  ];
815
- var CLINE_NATIVE_TOOL_KINDS = {
816
- ask_question: "readonly",
817
- read: "readonly",
818
- write: "edit",
819
- edit: "edit",
820
- bash: "bash",
821
- grep: "readonly",
822
- glob: "readonly",
823
- ls: "readonly",
824
- skills: "readonly"
639
+ const CLINE_NATIVE_TOOL_KINDS = {
640
+ ask_question: "readonly",
641
+ read: "readonly",
642
+ write: "edit",
643
+ edit: "edit",
644
+ bash: "bash",
645
+ grep: "readonly",
646
+ glob: "readonly",
647
+ ls: "readonly",
648
+ skills: "readonly"
825
649
  };
826
650
  function isClineBuiltinToolName(name) {
827
- return CLINE_NATIVE_BUILTIN_NAMES.includes(name);
651
+ return CLINE_NATIVE_BUILTIN_NAMES.includes(name);
828
652
  }
653
+ /**
654
+ * Resolve which built-in tools stay active for a session under the framework's
655
+ * `builtinToolFiltering`. Names map 1:1 (native === common), so filtering is a
656
+ * plain include/exclude.
657
+ */
829
658
  function resolveActiveClineBuiltinNames(toolFiltering) {
830
- if (toolFiltering == null) return CLINE_NATIVE_BUILTIN_NAMES;
831
- const filteredNames = toolFiltering.toolNames.map(
832
- (name) => name === "askUserQuestions" ? "ask_question" : name
833
- );
834
- if (toolFiltering.mode === "allow") {
835
- return CLINE_NATIVE_BUILTIN_NAMES.filter(
836
- (name) => filteredNames.includes(name)
837
- );
838
- }
839
- return CLINE_NATIVE_BUILTIN_NAMES.filter(
840
- (name) => !filteredNames.includes(name)
841
- );
659
+ if (toolFiltering == null) return CLINE_NATIVE_BUILTIN_NAMES;
660
+ const filteredNames = toolFiltering.toolNames.map((name) => name === "askUserQuestions" ? "ask_question" : name);
661
+ if (toolFiltering.mode === "allow") return CLINE_NATIVE_BUILTIN_NAMES.filter((name) => filteredNames.includes(name));
662
+ return CLINE_NATIVE_BUILTIN_NAMES.filter((name) => !filteredNames.includes(name));
842
663
  }
843
664
  function clineQuestionKey(input) {
844
- return JSON.stringify([input.question, input.options]);
665
+ return JSON.stringify([input.question, input.options]);
845
666
  }
846
- var clineToolResultMarker = /* @__PURE__ */ Symbol("cline-tool-result");
847
- function createClineToolResult({
848
- output,
849
- isError
850
- }) {
851
- return {
852
- [clineToolResultMarker]: true,
853
- output,
854
- ...isError ? { isError: true } : {}
855
- };
667
+ const clineToolResultMarker = Symbol("cline-tool-result");
668
+ function createClineToolResult({ output, isError }) {
669
+ return {
670
+ [clineToolResultMarker]: true,
671
+ output,
672
+ ...isError ? { isError: true } : {}
673
+ };
856
674
  }
857
675
  function unwrapClineToolResult(value) {
858
- if (value == null || typeof value !== "object" || value[clineToolResultMarker] !== true) {
859
- return void 0;
860
- }
861
- const result = value;
862
- return {
863
- output: result.output,
864
- ...result.isError ? { isError: true } : {}
865
- };
676
+ if (value == null || typeof value !== "object" || value[clineToolResultMarker] !== true) return;
677
+ const result = value;
678
+ return {
679
+ output: result.output,
680
+ ...result.isError ? { isError: true } : {}
681
+ };
866
682
  }
867
683
  async function guarded(run) {
868
- try {
869
- return createClineToolResult({ output: await run() });
870
- } catch (error) {
871
- return createClineToolResult({
872
- output: { error: getErrorText(error) },
873
- isError: true
874
- });
875
- }
684
+ try {
685
+ return createClineToolResult({ output: await run() });
686
+ } catch (error) {
687
+ return createClineToolResult({
688
+ output: { error: getErrorText(error) },
689
+ isError: true
690
+ });
691
+ }
876
692
  }
877
- function buildBuiltinAgentTools({
878
- ops,
879
- activeNames,
880
- pendingQuestions = /* @__PURE__ */ new Map(),
881
- pendingQuestionResults = /* @__PURE__ */ new Map()
882
- }) {
883
- const tools = {
884
- ask_question: createDefaultTools2({
885
- executors: {
886
- askQuestion: async (question, options, context) => {
887
- if (context.toolCallId == null) {
888
- throw new Error(
889
- "Cline ask_question did not provide a tool call id."
890
- );
891
- }
892
- const questionKey = clineQuestionKey({ question, options });
893
- const pendingResult = pendingQuestionResults.get(questionKey);
894
- if (pendingResult != null) {
895
- pendingQuestionResults.delete(questionKey);
896
- return toClineQuestionResult({
897
- nativeInput: { question, options },
898
- output: pendingResult.output
899
- });
900
- }
901
- return new Promise((resolve2) => {
902
- pendingQuestions.set(context.toolCallId, {
903
- input: { question, options },
904
- resolve: resolve2
905
- });
906
- });
907
- }
908
- },
909
- enableReadFiles: false,
910
- enableSearch: false,
911
- enableBash: false,
912
- enableWebFetch: false,
913
- enableApplyPatch: false,
914
- enableEditor: false,
915
- enableSkills: false,
916
- enableAskQuestion: true
917
- }).find((tool2) => tool2.name === "ask_question"),
918
- read: createTool({
919
- name: "read",
920
- description: "Read file contents.",
921
- inputSchema: {
922
- type: "object",
923
- properties: {
924
- file_path: {
925
- type: "string",
926
- description: "Path of the file to read."
927
- }
928
- },
929
- required: ["file_path"]
930
- },
931
- execute: async (input) => guarded(() => ops.readFile(input.file_path))
932
- }),
933
- write: createTool({
934
- name: "write",
935
- description: "Overwrite or create a file.",
936
- inputSchema: {
937
- type: "object",
938
- properties: {
939
- file_path: {
940
- type: "string",
941
- description: "Path of the file to write."
942
- },
943
- content: {
944
- type: "string",
945
- description: "Full file content to write."
946
- }
947
- },
948
- required: ["file_path", "content"]
949
- },
950
- execute: async (input) => guarded(async () => {
951
- await ops.writeFile(input.file_path, input.content);
952
- return `Wrote ${input.file_path}`;
953
- })
954
- }),
955
- edit: createTool({
956
- name: "edit",
957
- description: "Edit a file by exact string replacement.",
958
- inputSchema: {
959
- type: "object",
960
- properties: {
961
- file_path: {
962
- type: "string",
963
- description: "Path of the file to edit."
964
- },
965
- old_string: { type: "string", description: "Exact text to replace." },
966
- new_string: { type: "string", description: "Replacement text." }
967
- },
968
- required: ["file_path", "old_string", "new_string"]
969
- },
970
- execute: async (input) => guarded(async () => {
971
- await ops.editFile(
972
- input.file_path,
973
- input.old_string,
974
- input.new_string
975
- );
976
- return `Edited ${input.file_path}`;
977
- })
978
- }),
979
- bash: createTool({
980
- name: "bash",
981
- description: "Execute a shell command in the sandbox workspace. Returns combined stdout/stderr and the exit code.",
982
- inputSchema: {
983
- type: "object",
984
- properties: {
985
- command: { type: "string", description: "Shell command to execute." },
986
- timeout: {
987
- type: "number",
988
- description: "Optional timeout in seconds."
989
- }
990
- },
991
- required: ["command"]
992
- },
993
- execute: async (input, context) => guarded(async () => {
994
- const result = await ops.bash(input.command, {
995
- ...typeof input.timeout === "number" ? { timeout: input.timeout } : {},
996
- ...context.signal ? { signal: context.signal } : {}
997
- });
998
- return `${result.output}${result.exitCode != null ? `
999
-
1000
- (exit ${result.exitCode})` : ""}`.trim();
1001
- })
1002
- }),
1003
- grep: createTool({
1004
- name: "grep",
1005
- description: "Search file contents with regex.",
1006
- inputSchema: {
1007
- type: "object",
1008
- properties: {
1009
- pattern: {
1010
- type: "string",
1011
- description: "Regex pattern to search for."
1012
- },
1013
- path: { type: "string", description: "File or directory to search." },
1014
- glob: { type: "string", description: "Glob filter for file names." },
1015
- ignoreCase: { type: "boolean" },
1016
- literal: {
1017
- type: "boolean",
1018
- description: "Treat pattern as a literal string."
1019
- },
1020
- context: {
1021
- type: "number",
1022
- description: "Lines of context around matches."
1023
- },
1024
- limit: {
1025
- type: "number",
1026
- description: "Maximum matching lines to return."
1027
- }
1028
- },
1029
- required: ["pattern"]
1030
- },
1031
- execute: async (input) => guarded(() => ops.grep(input.pattern, input))
1032
- }),
1033
- glob: createTool({
1034
- name: "glob",
1035
- description: "Find files matching a glob pattern.",
1036
- inputSchema: {
1037
- type: "object",
1038
- properties: {
1039
- pattern: { type: "string", description: "Glob pattern to match." },
1040
- path: { type: "string", description: "Directory to search from." },
1041
- limit: { type: "number", description: "Maximum paths to return." }
1042
- },
1043
- required: ["pattern"]
1044
- },
1045
- execute: async (input) => guarded(async () => {
1046
- const matches = await ops.glob(
1047
- input.pattern,
1048
- input.path ?? ".",
1049
- input.limit ?? 1e3
1050
- );
1051
- return matches.length > 0 ? matches.join("\n") : "No files found";
1052
- })
1053
- }),
1054
- ls: createTool({
1055
- name: "ls",
1056
- description: "List directory entries.",
1057
- inputSchema: {
1058
- type: "object",
1059
- properties: {
1060
- path: { type: "string", description: "Directory to list." },
1061
- limit: { type: "number", description: "Maximum entries to return." }
1062
- }
1063
- },
1064
- execute: async (input) => guarded(async () => {
1065
- const entries = await ops.ls(input.path ?? ".", input.limit ?? 500);
1066
- return entries.join("\n");
1067
- })
1068
- })
1069
- };
1070
- return activeNames.map((name) => tools[name]).filter((tool2) => tool2 != null);
693
+ /**
694
+ * Build the sandbox-backed coding tools handed to the Cline runtime. These
695
+ * schemas mirror their declarations in `CLINE_BUILTIN_TOOLS`
696
+ * (cline-harness.ts). The in-host skills tool is built separately.
697
+ */
698
+ function buildBuiltinAgentTools({ ops, activeNames, pendingQuestions = /* @__PURE__ */ new Map(), pendingQuestionResults = /* @__PURE__ */ new Map() }) {
699
+ const tools = {
700
+ ask_question: createDefaultTools({
701
+ executors: { askQuestion: async (question, options, context) => {
702
+ if (context.toolCallId == null) throw new Error("Cline ask_question did not provide a tool call id.");
703
+ const questionKey = clineQuestionKey({
704
+ question,
705
+ options
706
+ });
707
+ const pendingResult = pendingQuestionResults.get(questionKey);
708
+ if (pendingResult != null) {
709
+ pendingQuestionResults.delete(questionKey);
710
+ return toClineQuestionResult({
711
+ nativeInput: {
712
+ question,
713
+ options
714
+ },
715
+ output: pendingResult.output
716
+ });
717
+ }
718
+ return new Promise((resolve) => {
719
+ pendingQuestions.set(context.toolCallId, {
720
+ input: {
721
+ question,
722
+ options
723
+ },
724
+ resolve
725
+ });
726
+ });
727
+ } },
728
+ enableReadFiles: false,
729
+ enableSearch: false,
730
+ enableBash: false,
731
+ enableWebFetch: false,
732
+ enableApplyPatch: false,
733
+ enableEditor: false,
734
+ enableSkills: false,
735
+ enableAskQuestion: true
736
+ }).find((tool) => tool.name === "ask_question"),
737
+ read: createTool({
738
+ name: "read",
739
+ description: "Read file contents.",
740
+ inputSchema: {
741
+ type: "object",
742
+ properties: { file_path: {
743
+ type: "string",
744
+ description: "Path of the file to read."
745
+ } },
746
+ required: ["file_path"]
747
+ },
748
+ execute: async (input) => guarded(() => ops.readFile(input.file_path))
749
+ }),
750
+ write: createTool({
751
+ name: "write",
752
+ description: "Overwrite or create a file.",
753
+ inputSchema: {
754
+ type: "object",
755
+ properties: {
756
+ file_path: {
757
+ type: "string",
758
+ description: "Path of the file to write."
759
+ },
760
+ content: {
761
+ type: "string",
762
+ description: "Full file content to write."
763
+ }
764
+ },
765
+ required: ["file_path", "content"]
766
+ },
767
+ execute: async (input) => guarded(async () => {
768
+ await ops.writeFile(input.file_path, input.content);
769
+ return `Wrote ${input.file_path}`;
770
+ })
771
+ }),
772
+ edit: createTool({
773
+ name: "edit",
774
+ description: "Edit a file by exact string replacement.",
775
+ inputSchema: {
776
+ type: "object",
777
+ properties: {
778
+ file_path: {
779
+ type: "string",
780
+ description: "Path of the file to edit."
781
+ },
782
+ old_string: {
783
+ type: "string",
784
+ description: "Exact text to replace."
785
+ },
786
+ new_string: {
787
+ type: "string",
788
+ description: "Replacement text."
789
+ }
790
+ },
791
+ required: [
792
+ "file_path",
793
+ "old_string",
794
+ "new_string"
795
+ ]
796
+ },
797
+ execute: async (input) => guarded(async () => {
798
+ await ops.editFile(input.file_path, input.old_string, input.new_string);
799
+ return `Edited ${input.file_path}`;
800
+ })
801
+ }),
802
+ bash: createTool({
803
+ name: "bash",
804
+ description: "Execute a shell command in the sandbox workspace. Returns combined stdout/stderr and the exit code.",
805
+ inputSchema: {
806
+ type: "object",
807
+ properties: {
808
+ command: {
809
+ type: "string",
810
+ description: "Shell command to execute."
811
+ },
812
+ timeout: {
813
+ type: "number",
814
+ description: "Optional timeout in seconds."
815
+ }
816
+ },
817
+ required: ["command"]
818
+ },
819
+ execute: async (input, context) => guarded(async () => {
820
+ const result = await ops.bash(input.command, {
821
+ ...typeof input.timeout === "number" ? { timeout: input.timeout } : {},
822
+ ...context.signal ? { signal: context.signal } : {}
823
+ });
824
+ return `${result.output}${result.exitCode != null ? `\n\n(exit ${result.exitCode})` : ""}`.trim();
825
+ })
826
+ }),
827
+ grep: createTool({
828
+ name: "grep",
829
+ description: "Search file contents with regex.",
830
+ inputSchema: {
831
+ type: "object",
832
+ properties: {
833
+ pattern: {
834
+ type: "string",
835
+ description: "Regex pattern to search for."
836
+ },
837
+ path: {
838
+ type: "string",
839
+ description: "File or directory to search."
840
+ },
841
+ glob: {
842
+ type: "string",
843
+ description: "Glob filter for file names."
844
+ },
845
+ ignoreCase: { type: "boolean" },
846
+ literal: {
847
+ type: "boolean",
848
+ description: "Treat pattern as a literal string."
849
+ },
850
+ context: {
851
+ type: "number",
852
+ description: "Lines of context around matches."
853
+ },
854
+ limit: {
855
+ type: "number",
856
+ description: "Maximum matching lines to return."
857
+ }
858
+ },
859
+ required: ["pattern"]
860
+ },
861
+ execute: async (input) => guarded(() => ops.grep(input.pattern, input))
862
+ }),
863
+ glob: createTool({
864
+ name: "glob",
865
+ description: "Find files matching a glob pattern.",
866
+ inputSchema: {
867
+ type: "object",
868
+ properties: {
869
+ pattern: {
870
+ type: "string",
871
+ description: "Glob pattern to match."
872
+ },
873
+ path: {
874
+ type: "string",
875
+ description: "Directory to search from."
876
+ },
877
+ limit: {
878
+ type: "number",
879
+ description: "Maximum paths to return."
880
+ }
881
+ },
882
+ required: ["pattern"]
883
+ },
884
+ execute: async (input) => guarded(async () => {
885
+ const matches = await ops.glob(input.pattern, input.path ?? ".", input.limit ?? 1e3);
886
+ return matches.length > 0 ? matches.join("\n") : "No files found";
887
+ })
888
+ }),
889
+ ls: createTool({
890
+ name: "ls",
891
+ description: "List directory entries.",
892
+ inputSchema: {
893
+ type: "object",
894
+ properties: {
895
+ path: {
896
+ type: "string",
897
+ description: "Directory to list."
898
+ },
899
+ limit: {
900
+ type: "number",
901
+ description: "Maximum entries to return."
902
+ }
903
+ }
904
+ },
905
+ execute: async (input) => guarded(async () => {
906
+ return (await ops.ls(input.path ?? ".", input.limit ?? 500)).join("\n");
907
+ })
908
+ })
909
+ };
910
+ return activeNames.map((name) => tools[name]).filter((tool) => tool != null);
1071
911
  }
1072
912
  function toClineQuestionResult(input) {
1073
- if (input.output.action === "declined" || input.output.action === "cancelled") {
1074
- return `The user ${input.output.action} the question.`;
1075
- }
1076
- const answer = input.output.answers["question-1"];
1077
- if (answer == null) return "The user did not answer the question.";
1078
- if (answer.freeform != null) return answer.freeform;
1079
- const result = answer.optionIds.map((id) => {
1080
- const index = Number(id.replace(/^option-/, "")) - 1;
1081
- return input.nativeInput.options[index];
1082
- }).filter((value) => value != null).join(", ");
1083
- return result;
913
+ if (input.output.action === "declined" || input.output.action === "cancelled") return `The user ${input.output.action} the question.`;
914
+ const answer = input.output.answers["question-1"];
915
+ if (answer == null) return "The user did not answer the question.";
916
+ if (answer.freeform != null) return answer.freeform;
917
+ return answer.optionIds.map((id) => {
918
+ const index = Number(id.replace(/^option-/, "")) - 1;
919
+ return input.nativeInput.options[index];
920
+ }).filter((value) => value != null).join(", ");
1084
921
  }
1085
- function buildUserAgentTools({
1086
- specs,
1087
- pendingToolResults
1088
- }) {
1089
- return specs.map(
1090
- (spec) => createTool({
1091
- name: spec.name,
1092
- description: spec.description ?? `User-registered tool ${spec.name}`,
1093
- inputSchema: spec.inputSchema ?? {
1094
- type: "object",
1095
- properties: {},
1096
- additionalProperties: true
1097
- },
1098
- execute: async (_input, context) => {
1099
- const toolCallId = context.toolCallId;
1100
- if (!toolCallId) {
1101
- return createClineToolResult({
1102
- output: { error: "Tool call is missing a toolCallId." },
1103
- isError: true
1104
- });
1105
- }
1106
- return new Promise((resolve2) => {
1107
- pendingToolResults.set(toolCallId, { resolve: resolve2 });
1108
- context.signal?.addEventListener(
1109
- "abort",
1110
- () => {
1111
- if (pendingToolResults.delete(toolCallId)) {
1112
- resolve2(
1113
- createClineToolResult({
1114
- output: {
1115
- error: "Turn was aborted before a tool result was submitted."
1116
- },
1117
- isError: true
1118
- })
1119
- );
1120
- }
1121
- },
1122
- { once: true }
1123
- );
1124
- });
1125
- }
1126
- })
1127
- );
922
+ /**
923
+ * Build host-executed user tools. When the model calls one, the tool parks a
924
+ * promise keyed by the runtime's `toolCallId`; the harness caller supplies the
925
+ * output via `submitToolResult`, which resolves the promise and lets the loop
926
+ * continue. The adapter never executes these itself.
927
+ */
928
+ function buildUserAgentTools({ specs, pendingToolResults }) {
929
+ return specs.map((spec) => createTool({
930
+ name: spec.name,
931
+ description: spec.description ?? `User-registered tool ${spec.name}`,
932
+ inputSchema: spec.inputSchema ?? {
933
+ type: "object",
934
+ properties: {},
935
+ additionalProperties: true
936
+ },
937
+ execute: async (_input, context) => {
938
+ const toolCallId = context.toolCallId;
939
+ if (!toolCallId) return createClineToolResult({
940
+ output: { error: "Tool call is missing a toolCallId." },
941
+ isError: true
942
+ });
943
+ return new Promise((resolve) => {
944
+ pendingToolResults.set(toolCallId, { resolve });
945
+ context.signal?.addEventListener("abort", () => {
946
+ if (pendingToolResults.delete(toolCallId)) resolve(createClineToolResult({
947
+ output: { error: "Turn was aborted before a tool result was submitted." },
948
+ isError: true
949
+ }));
950
+ }, { once: true });
951
+ });
952
+ }
953
+ }));
1128
954
  }
1129
-
1130
- // src/cline-translate.ts
1131
- function createClineTranslatorState({
1132
- builtinToolNames,
1133
- hostToolNames = [],
1134
- ignoredToolNames = [],
1135
- mcpToolNames = []
1136
- }) {
1137
- return {
1138
- builtinToolNames: new Set(builtinToolNames),
1139
- dynamicToolCallIds: /* @__PURE__ */ new Set(),
1140
- hostToolNames: new Set(hostToolNames),
1141
- ignoredToolNames: new Set(ignoredToolNames),
1142
- mcpToolNames: new Set(mcpToolNames),
1143
- emittedToolCalls: /* @__PURE__ */ new Set(),
1144
- blockCounter: 0
1145
- };
955
+ //#endregion
956
+ //#region src/cline-translate.ts
957
+ function createClineTranslatorState({ builtinToolNames, hostToolNames = [], ignoredToolNames = [], mcpToolNames = [] }) {
958
+ return {
959
+ builtinToolNames: new Set(builtinToolNames),
960
+ dynamicToolCallIds: /* @__PURE__ */ new Set(),
961
+ hostToolNames: new Set(hostToolNames),
962
+ ignoredToolNames: new Set(ignoredToolNames),
963
+ mcpToolNames: new Set(mcpToolNames),
964
+ emittedToolCalls: /* @__PURE__ */ new Set(),
965
+ blockCounter: 0
966
+ };
1146
967
  }
1147
968
  function closeOpenBlocks(state) {
1148
- const parts = [];
1149
- if (state.openReasoningBlockId) {
1150
- parts.push({ type: "reasoning-end", id: state.openReasoningBlockId });
1151
- state.openReasoningBlockId = void 0;
1152
- }
1153
- if (state.openTextBlockId) {
1154
- parts.push({ type: "text-end", id: state.openTextBlockId });
1155
- state.openTextBlockId = void 0;
1156
- }
1157
- return parts;
969
+ const parts = [];
970
+ if (state.openReasoningBlockId) {
971
+ parts.push({
972
+ type: "reasoning-end",
973
+ id: state.openReasoningBlockId
974
+ });
975
+ state.openReasoningBlockId = void 0;
976
+ }
977
+ if (state.openTextBlockId) {
978
+ parts.push({
979
+ type: "text-end",
980
+ id: state.openTextBlockId
981
+ });
982
+ state.openTextBlockId = void 0;
983
+ }
984
+ return parts;
1158
985
  }
1159
986
  function toolCallPart(state, toolCall) {
1160
- const isMcpTool = state.mcpToolNames.has(toolCall.toolName) && !state.hostToolNames.has(toolCall.toolName);
1161
- if (isMcpTool) {
1162
- state.dynamicToolCallIds.add(toolCall.toolCallId);
1163
- }
1164
- if (toolCall.toolName === "ask_question") {
1165
- const nativeInput = toolCall.input;
1166
- return {
1167
- type: "tool-call",
1168
- toolCallId: toolCall.toolCallId,
1169
- toolName: "askUserQuestions",
1170
- input: JSON.stringify({
1171
- allowPartialAnswers: false,
1172
- questions: [
1173
- {
1174
- id: "question-1",
1175
- question: nativeInput.question,
1176
- options: nativeInput.options.map((label, index) => ({
1177
- id: `option-${index + 1}`,
1178
- label
1179
- })),
1180
- allowFreeForm: true
1181
- }
1182
- ]
1183
- }),
1184
- providerExecuted: false,
1185
- providerMetadata: {
1186
- cline: {
1187
- nativeRequest: nativeInput
1188
- }
1189
- },
1190
- nativeName: "ask_question"
1191
- };
1192
- }
1193
- return {
1194
- type: "tool-call",
1195
- toolCallId: toolCall.toolCallId,
1196
- toolName: toolCall.toolName,
1197
- input: JSON.stringify(toolCall.input ?? {}),
1198
- /*
1199
- * Built-ins and external MCP tools are executed by the Cline runtime.
1200
- * Typed host tools wait for dispatch through `submitToolResult`, including
1201
- * when a host tool has the same name as a configured MCP tool.
1202
- */
1203
- ...state.builtinToolNames.has(toolCall.toolName) || isMcpTool ? { providerExecuted: true } : {},
1204
- ...isMcpTool ? { dynamic: true } : {}
1205
- };
987
+ const isMcpTool = state.mcpToolNames.has(toolCall.toolName) && !state.hostToolNames.has(toolCall.toolName);
988
+ if (isMcpTool) state.dynamicToolCallIds.add(toolCall.toolCallId);
989
+ if (toolCall.toolName === "ask_question") {
990
+ const nativeInput = toolCall.input;
991
+ return {
992
+ type: "tool-call",
993
+ toolCallId: toolCall.toolCallId,
994
+ toolName: "askUserQuestions",
995
+ input: JSON.stringify({
996
+ allowPartialAnswers: false,
997
+ questions: [{
998
+ id: "question-1",
999
+ question: nativeInput.question,
1000
+ options: nativeInput.options.map((label, index) => ({
1001
+ id: `option-${index + 1}`,
1002
+ label
1003
+ })),
1004
+ allowFreeForm: true
1005
+ }]
1006
+ }),
1007
+ providerExecuted: false,
1008
+ providerMetadata: { cline: { nativeRequest: nativeInput } },
1009
+ nativeName: "ask_question"
1010
+ };
1011
+ }
1012
+ return {
1013
+ type: "tool-call",
1014
+ toolCallId: toolCall.toolCallId,
1015
+ toolName: toolCall.toolName,
1016
+ input: JSON.stringify(toolCall.input ?? {}),
1017
+ ...state.builtinToolNames.has(toolCall.toolName) || isMcpTool ? { providerExecuted: true } : {},
1018
+ ...isMcpTool ? { dynamic: true } : {}
1019
+ };
1206
1020
  }
1021
+ /**
1022
+ * Emit the `tool-call` + `tool-approval-request` pair for a built-in tool
1023
+ * that requires approval. The Cline runtime asks for approval *before* its
1024
+ * `tool-started` event fires, so the `tool-call` part is emitted here and
1025
+ * deduplicated when `tool-started` arrives.
1026
+ */
1207
1027
  function toolApprovalParts(state, toolCall) {
1208
- const parts = [];
1209
- if (!state.emittedToolCalls.has(toolCall.toolCallId)) {
1210
- state.emittedToolCalls.add(toolCall.toolCallId);
1211
- parts.push(toolCallPart(state, toolCall));
1212
- }
1213
- parts.push({
1214
- type: "tool-approval-request",
1215
- approvalId: toolCall.toolCallId,
1216
- toolCallId: toolCall.toolCallId
1217
- });
1218
- return parts;
1028
+ const parts = [];
1029
+ if (!state.emittedToolCalls.has(toolCall.toolCallId)) {
1030
+ state.emittedToolCalls.add(toolCall.toolCallId);
1031
+ parts.push(toolCallPart(state, toolCall));
1032
+ }
1033
+ parts.push({
1034
+ type: "tool-approval-request",
1035
+ approvalId: toolCall.toolCallId,
1036
+ toolCallId: toolCall.toolCallId
1037
+ });
1038
+ return parts;
1219
1039
  }
1040
+ /**
1041
+ * Translate one Cline runtime event into zero or more harness stream parts.
1042
+ * Terminal events (`run-finished`, `run-failed`) are intentionally not
1043
+ * translated here — the session driver emits `finish`/`error` after the
1044
+ * runtime's promise settles, so a suspend can close the stream cleanly.
1045
+ */
1220
1046
  function translateClineEvent(event, state) {
1221
- switch (event.type) {
1222
- case "assistant-reasoning-delta": {
1223
- const parts = [];
1224
- if (state.openTextBlockId) {
1225
- parts.push({ type: "text-end", id: state.openTextBlockId });
1226
- state.openTextBlockId = void 0;
1227
- }
1228
- if (!state.openReasoningBlockId) {
1229
- state.openReasoningBlockId = `reasoning-${++state.blockCounter}`;
1230
- parts.push({ type: "reasoning-start", id: state.openReasoningBlockId });
1231
- }
1232
- parts.push({
1233
- type: "reasoning-delta",
1234
- id: state.openReasoningBlockId,
1235
- delta: event.text
1236
- });
1237
- return parts;
1238
- }
1239
- case "assistant-text-delta": {
1240
- const parts = [];
1241
- if (state.openReasoningBlockId) {
1242
- parts.push({ type: "reasoning-end", id: state.openReasoningBlockId });
1243
- state.openReasoningBlockId = void 0;
1244
- }
1245
- if (!state.openTextBlockId) {
1246
- state.openTextBlockId = `text-${++state.blockCounter}`;
1247
- parts.push({ type: "text-start", id: state.openTextBlockId });
1248
- }
1249
- parts.push({
1250
- type: "text-delta",
1251
- id: state.openTextBlockId,
1252
- delta: event.text
1253
- });
1254
- return parts;
1255
- }
1256
- case "assistant-message": {
1257
- const parts = closeOpenBlocks(state);
1258
- state.pendingFinishStep = {
1259
- type: "finish-step",
1260
- finishReason: mapModelFinishReason(event.finishReason),
1261
- usage: usageFromMessageMetrics(event.message.metrics)
1262
- };
1263
- return parts;
1264
- }
1265
- case "turn-finished": {
1266
- const pendingFinishStep = state.pendingFinishStep;
1267
- if (!pendingFinishStep) {
1268
- return [];
1269
- }
1270
- state.pendingFinishStep = void 0;
1271
- return [pendingFinishStep];
1272
- }
1273
- case "tool-started": {
1274
- if (state.ignoredToolNames.has(event.toolCall.toolName)) return [];
1275
- if (state.emittedToolCalls.has(event.toolCall.toolCallId)) {
1276
- return [];
1277
- }
1278
- state.emittedToolCalls.add(event.toolCall.toolCallId);
1279
- return [toolCallPart(state, event.toolCall)];
1280
- }
1281
- case "tool-finished": {
1282
- if (state.ignoredToolNames.has(event.toolCall.toolName)) return [];
1283
- const resultPart = event.message.content.find(
1284
- (part) => part.type === "tool-result" && part.toolCallId === event.toolCall.toolCallId
1285
- );
1286
- const output = resultPart?.output;
1287
- const dynamic = state.dynamicToolCallIds.delete(
1288
- event.toolCall.toolCallId
1289
- );
1290
- return [
1291
- {
1292
- type: "tool-result",
1293
- toolCallId: event.toolCall.toolCallId,
1294
- toolName: event.toolCall.toolName === "ask_question" ? "askUserQuestions" : event.toolCall.toolName,
1295
- result: toToolResultValue(output),
1296
- ...resultPart?.isError ? { isError: true } : {},
1297
- ...dynamic ? { dynamic: true } : {}
1298
- }
1299
- ];
1300
- }
1301
- case "tool-updated":
1302
- return [
1303
- {
1304
- type: "raw",
1305
- rawValue: {
1306
- type: "tool-updated",
1307
- toolCallId: event.toolCall.toolCallId,
1308
- toolName: event.toolCall.toolName,
1309
- update: event.update
1310
- }
1311
- }
1312
- ];
1313
- case "status-notice":
1314
- return [
1315
- {
1316
- type: "raw",
1317
- rawValue: {
1318
- type: "status-notice",
1319
- message: event.message,
1320
- ...event.metadata ? { metadata: event.metadata } : {}
1321
- }
1322
- }
1323
- ];
1324
- default:
1325
- return [];
1326
- }
1047
+ switch (event.type) {
1048
+ case "assistant-reasoning-delta": {
1049
+ const parts = [];
1050
+ if (state.openTextBlockId) {
1051
+ parts.push({
1052
+ type: "text-end",
1053
+ id: state.openTextBlockId
1054
+ });
1055
+ state.openTextBlockId = void 0;
1056
+ }
1057
+ if (!state.openReasoningBlockId) {
1058
+ state.openReasoningBlockId = `reasoning-${++state.blockCounter}`;
1059
+ parts.push({
1060
+ type: "reasoning-start",
1061
+ id: state.openReasoningBlockId
1062
+ });
1063
+ }
1064
+ parts.push({
1065
+ type: "reasoning-delta",
1066
+ id: state.openReasoningBlockId,
1067
+ delta: event.text
1068
+ });
1069
+ return parts;
1070
+ }
1071
+ case "assistant-text-delta": {
1072
+ const parts = [];
1073
+ if (state.openReasoningBlockId) {
1074
+ parts.push({
1075
+ type: "reasoning-end",
1076
+ id: state.openReasoningBlockId
1077
+ });
1078
+ state.openReasoningBlockId = void 0;
1079
+ }
1080
+ if (!state.openTextBlockId) {
1081
+ state.openTextBlockId = `text-${++state.blockCounter}`;
1082
+ parts.push({
1083
+ type: "text-start",
1084
+ id: state.openTextBlockId
1085
+ });
1086
+ }
1087
+ parts.push({
1088
+ type: "text-delta",
1089
+ id: state.openTextBlockId,
1090
+ delta: event.text
1091
+ });
1092
+ return parts;
1093
+ }
1094
+ case "assistant-message": {
1095
+ const parts = closeOpenBlocks(state);
1096
+ state.pendingFinishStep = {
1097
+ type: "finish-step",
1098
+ finishReason: mapModelFinishReason(event.finishReason),
1099
+ usage: usageFromMessageMetrics(event.message.metrics)
1100
+ };
1101
+ return parts;
1102
+ }
1103
+ case "turn-finished": {
1104
+ const pendingFinishStep = state.pendingFinishStep;
1105
+ if (!pendingFinishStep) return [];
1106
+ state.pendingFinishStep = void 0;
1107
+ return [pendingFinishStep];
1108
+ }
1109
+ case "tool-started":
1110
+ if (state.ignoredToolNames.has(event.toolCall.toolName)) return [];
1111
+ if (state.emittedToolCalls.has(event.toolCall.toolCallId)) return [];
1112
+ state.emittedToolCalls.add(event.toolCall.toolCallId);
1113
+ return [toolCallPart(state, event.toolCall)];
1114
+ case "tool-finished": {
1115
+ if (state.ignoredToolNames.has(event.toolCall.toolName)) return [];
1116
+ const resultPart = event.message.content.find((part) => part.type === "tool-result" && part.toolCallId === event.toolCall.toolCallId);
1117
+ const output = resultPart?.output;
1118
+ const dynamic = state.dynamicToolCallIds.delete(event.toolCall.toolCallId);
1119
+ return [{
1120
+ type: "tool-result",
1121
+ toolCallId: event.toolCall.toolCallId,
1122
+ toolName: event.toolCall.toolName === "ask_question" ? "askUserQuestions" : event.toolCall.toolName,
1123
+ result: toToolResultValue(output),
1124
+ ...resultPart?.isError ? { isError: true } : {},
1125
+ ...dynamic ? { dynamic: true } : {}
1126
+ }];
1127
+ }
1128
+ case "tool-updated": return [{
1129
+ type: "raw",
1130
+ rawValue: {
1131
+ type: "tool-updated",
1132
+ toolCallId: event.toolCall.toolCallId,
1133
+ toolName: event.toolCall.toolName,
1134
+ update: event.update
1135
+ }
1136
+ }];
1137
+ case "status-notice": return [{
1138
+ type: "raw",
1139
+ rawValue: {
1140
+ type: "status-notice",
1141
+ message: event.message,
1142
+ ...event.metadata ? { metadata: event.metadata } : {}
1143
+ }
1144
+ }];
1145
+ default: return [];
1146
+ }
1327
1147
  }
1148
+ /**
1149
+ * Close any blocks still open when the turn ends (e.g. an aborted turn whose
1150
+ * `assistant-message` never arrived). A buffered step that never reached
1151
+ * Cline's `turn-finished` boundary is incomplete and must not be emitted.
1152
+ */
1328
1153
  function finishClineTranslation(state) {
1329
- const parts = closeOpenBlocks(state);
1330
- state.pendingFinishStep = void 0;
1331
- return parts;
1154
+ const parts = closeOpenBlocks(state);
1155
+ state.pendingFinishStep = void 0;
1156
+ return parts;
1332
1157
  }
1333
-
1334
- // src/cline-session.ts
1335
- var HARNESS_ID2 = "cline";
1336
- var parkedClineSessions = /* @__PURE__ */ new Map();
1158
+ //#endregion
1159
+ //#region src/cline-session.ts
1160
+ const HARNESS_ID = "cline";
1161
+ const parkedClineSessions = /* @__PURE__ */ new Map();
1162
+ /**
1163
+ * Whether a thrown error is an abort — the expected result of
1164
+ * `doSuspendTurn` aborting the in-flight turn. Only these are safe to swallow
1165
+ * while `suspending`; any other error must surface as an `error` chunk.
1166
+ */
1337
1167
  function isAbortError(value) {
1338
- if (value == null) return false;
1339
- const name = value.name;
1340
- if (name === "AbortError" || name === "AgentRuntimeAbortError") {
1341
- return true;
1342
- }
1343
- const text = typeof value === "string" ? value : value instanceof Error ? value.message : String(value);
1344
- return /\baborted\b|AbortError/i.test(text);
1168
+ if (value == null) return false;
1169
+ const name = value.name;
1170
+ if (name === "AbortError" || name === "AgentRuntimeAbortError") return true;
1171
+ const text = typeof value === "string" ? value : value instanceof Error ? value.message : String(value);
1172
+ return /\baborted\b|AbortError/i.test(text);
1345
1173
  }
1346
1174
  function clineBuiltinToolRequiresApproval(input) {
1347
- if (input.permissionMode === "allow-all") return false;
1348
- if (input.permissionMode === "allow-edits") return input.kind === "bash";
1349
- return input.kind === "edit" || input.kind === "bash";
1175
+ if (input.permissionMode === "allow-all") return false;
1176
+ if (input.permissionMode === "allow-edits") return input.kind === "bash";
1177
+ return input.kind === "edit" || input.kind === "bash";
1350
1178
  }
1351
1179
  function buildSystemPrompt(input) {
1352
- return [
1353
- "You are Cline, an autonomous coding agent operating inside a sandboxed workspace.",
1354
- `## Workspace
1355
-
1356
- The workspace root is \`${input.sessionWorkDir}\`. All relative file paths and shell commands resolve against it. Use the provided tools (read, write, edit, bash, grep, glob, ls) to inspect and modify the workspace.`,
1357
- `## Sandbox
1358
-
1359
- ${input.sandboxDescription}`
1360
- ].join("\n\n");
1180
+ return [
1181
+ "You are Cline, an autonomous coding agent operating inside a sandboxed workspace.",
1182
+ `## Workspace\n\nThe workspace root is \`${input.sessionWorkDir}\`. All relative file paths and shell commands resolve against it. Use the provided tools (read, write, edit, bash, grep, glob, ls) to inspect and modify the workspace.`,
1183
+ `## Sandbox\n\n${input.sandboxDescription}`
1184
+ ].join("\n\n");
1361
1185
  }
1362
1186
  function toClineBackendProviderBaseUrl(baseUrl) {
1363
- return `${baseUrl.replace(/\/+$/, "")}/api/v1`;
1187
+ return `${baseUrl.replace(/\/+$/, "")}/api/v1`;
1364
1188
  }
1365
1189
  function toAiGatewayProviderBaseUrl(baseUrl) {
1366
- const trimmed = baseUrl.replace(/\/+$/, "");
1367
- return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
1190
+ const trimmed = baseUrl.replace(/\/+$/, "");
1191
+ return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
1368
1192
  }
1369
- function createClineAgentModel({
1370
- settings,
1371
- clientApp,
1372
- modelId
1373
- }) {
1374
- const gatewayBaseUrl = settings.authEnv.AI_GATEWAY_BASE_URL;
1375
- const isAiGateway = gatewayBaseUrl != null;
1376
- const providerId = isAiGateway ? "cline" : settings.providerId ?? "cline";
1377
- const apiKey = isAiGateway ? settings.authEnv.AI_GATEWAY_API_KEY : settings.apiKey ?? (providerId === "cline" ? settings.authEnv.CLINE_API_KEY : void 0);
1378
- const baseUrl = isAiGateway ? toAiGatewayProviderBaseUrl(gatewayBaseUrl) : settings.baseUrl ?? (providerId === "cline" && settings.authEnv.CLINE_API_BASE_URL ? toClineBackendProviderBaseUrl(settings.authEnv.CLINE_API_BASE_URL) : void 0);
1379
- const headers = isAiGateway ? {
1380
- ...settings.headers,
1381
- ...settings.agentHeaders,
1382
- "User-Agent": clientApp,
1383
- "x-client-app": clientApp
1384
- } : settings.headers != null || settings.agentHeaders != null ? {
1385
- ...settings.headers,
1386
- ...settings.agentHeaders
1387
- } : void 0;
1388
- const gateway = Llms2.createGateway({
1389
- providerConfigs: [
1390
- {
1391
- providerId,
1392
- ...apiKey ? { apiKey } : {},
1393
- ...baseUrl ? { baseUrl } : {},
1394
- ...headers ? { headers } : {},
1395
- ...isAiGateway || settings.isAuthenticationEnvironmentOverride ? { apiKeyEnv: [] } : {}
1396
- }
1397
- ]
1398
- });
1399
- const modelSelection = {
1400
- providerId,
1401
- ...modelId ? { modelId } : {}
1402
- };
1403
- const modelOptions = settings.reasoningEffort === "none" ? { reasoning: { enabled: false } } : {
1404
- reasoning: {
1405
- enabled: true,
1406
- effort: settings.reasoningEffort
1407
- }
1408
- };
1409
- const model = settings.reasoningEffort === void 0 ? gateway.createAgentModel(modelSelection) : gateway.createAgentModel(modelSelection, modelOptions);
1410
- return {
1411
- model,
1412
- providerId
1413
- };
1193
+ function createClineAgentModel({ settings, clientApp, modelId }) {
1194
+ const gatewayBaseUrl = settings.authEnv.AI_GATEWAY_BASE_URL;
1195
+ const isAiGateway = gatewayBaseUrl != null;
1196
+ const providerId = isAiGateway ? "cline" : settings.providerId ?? "cline";
1197
+ const apiKey = isAiGateway ? settings.authEnv.AI_GATEWAY_API_KEY : settings.apiKey ?? (providerId === "cline" ? settings.authEnv.CLINE_API_KEY : void 0);
1198
+ const baseUrl = isAiGateway ? toAiGatewayProviderBaseUrl(gatewayBaseUrl) : settings.baseUrl ?? (providerId === "cline" && settings.authEnv.CLINE_API_BASE_URL ? toClineBackendProviderBaseUrl(settings.authEnv.CLINE_API_BASE_URL) : void 0);
1199
+ const headers = isAiGateway ? {
1200
+ ...settings.headers,
1201
+ ...settings.agentHeaders,
1202
+ "User-Agent": clientApp,
1203
+ "x-client-app": clientApp
1204
+ } : settings.headers != null || settings.agentHeaders != null ? {
1205
+ ...settings.headers,
1206
+ ...settings.agentHeaders
1207
+ } : void 0;
1208
+ const gateway = Llms.createGateway({ providerConfigs: [{
1209
+ providerId,
1210
+ ...apiKey ? { apiKey } : {},
1211
+ ...baseUrl ? { baseUrl } : {},
1212
+ ...headers ? { headers } : {},
1213
+ ...isAiGateway || settings.isAuthenticationEnvironmentOverride ? { apiKeyEnv: [] } : {}
1214
+ }] });
1215
+ const modelSelection = {
1216
+ providerId,
1217
+ ...modelId ? { modelId } : {}
1218
+ };
1219
+ const modelOptions = settings.reasoningEffort === "none" ? { reasoning: { enabled: false } } : { reasoning: {
1220
+ enabled: true,
1221
+ effort: settings.reasoningEffort
1222
+ } };
1223
+ return {
1224
+ model: settings.reasoningEffort === void 0 ? gateway.createAgentModel(modelSelection) : gateway.createAgentModel(modelSelection, modelOptions),
1225
+ providerId
1226
+ };
1414
1227
  }
1415
1228
  async function createClineSession(input) {
1416
- if (input.isResume) {
1417
- const parkedSession = parkedClineSessions.get(input.sessionId);
1418
- if (parkedSession) {
1419
- parkedClineSessions.delete(input.sessionId);
1420
- return {
1421
- ...parkedSession,
1422
- isResume: true
1423
- };
1424
- }
1425
- }
1426
- const toolSafeSandboxSession = getRestrictedSandboxSession(
1427
- input.sandboxSession
1428
- );
1429
- const sandboxHomeDir = await resolveSandboxHomeDir({
1430
- sandbox: toolSafeSandboxSession,
1431
- ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
1432
- });
1433
- const privateSessionDir = resolveClinePrivateSessionDirectory({
1434
- sandboxHomeDir,
1435
- sessionWorkDir: input.sessionWorkDir,
1436
- sessionId: input.sessionId
1437
- });
1438
- const permissionMode = input.permissionMode ?? "allow-all";
1439
- const activeBuiltinNames = resolveActiveClineBuiltinNames(
1440
- input.builtinToolFiltering
1441
- );
1442
- const ops = createClineRemoteOps({
1443
- sandbox: toolSafeSandboxSession,
1444
- workDir: input.sessionWorkDir
1445
- });
1446
- let currentMessages = [];
1447
- if (input.isResume && input.resumeHistoryFileName) {
1448
- const restored = await pullHistoryFromSandbox({
1449
- sandbox: toolSafeSandboxSession,
1450
- privateSessionDir,
1451
- historyFileName: safeClineHistoryFileName(input.resumeHistoryFileName),
1452
- ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
1453
- });
1454
- if (restored) {
1455
- currentMessages = restored;
1456
- }
1457
- }
1458
- const mcpRuntime = await createClineMcpRuntime({
1459
- mcpServers: input.settings.mcpServers
1460
- });
1461
- let activeModelId;
1462
- let agentModel = createClineAgentModel({
1463
- settings: input.settings,
1464
- clientApp: input.clientApp,
1465
- modelId: activeModelId
1466
- });
1467
- let agent;
1468
- let unsubscribe;
1469
- let lastAgentConfigurationSignature;
1470
- let agentHasRun = false;
1471
- let stopped = false;
1472
- let suspending = false;
1473
- const pendingToolResults = /* @__PURE__ */ new Map();
1474
- const pendingToolApprovals = /* @__PURE__ */ new Map();
1475
- const pendingUserMessages = [];
1476
- let acceptingUserMessages = false;
1477
- let currentEmit;
1478
- let translatorState;
1479
- const pendingQuestions = /* @__PURE__ */ new Map();
1480
- const pendingQuestionResults = /* @__PURE__ */ new Map();
1481
- let activeTurn;
1482
- function settlePendingToolResults(reason) {
1483
- for (const pending of pendingToolResults.values()) {
1484
- pending.resolve(
1485
- createClineToolResult({
1486
- output: { error: reason },
1487
- isError: true
1488
- })
1489
- );
1490
- }
1491
- pendingToolResults.clear();
1492
- }
1493
- function settlePendingToolApprovals(reason) {
1494
- for (const pending of pendingToolApprovals.values()) {
1495
- pending.resolve({ approved: false, reason });
1496
- }
1497
- pendingToolApprovals.clear();
1498
- }
1499
- function consumePendingUserMessage() {
1500
- const pending = pendingUserMessages.shift();
1501
- if (pending == null) return void 0;
1502
- pending.resolve();
1503
- return pending.text;
1504
- }
1505
- function rejectPendingUserMessages(error) {
1506
- for (const pending of pendingUserMessages) {
1507
- pending.reject(error);
1508
- }
1509
- pendingUserMessages.length = 0;
1510
- }
1511
- function settlePendingQuestions(reason) {
1512
- for (const pending of pendingQuestions.values()) {
1513
- pending.resolve(reason);
1514
- }
1515
- pendingQuestions.clear();
1516
- pendingQuestionResults.clear();
1517
- }
1518
- async function persistHistory() {
1519
- const messages = agent?.snapshot().messages ?? currentMessages;
1520
- await persistHistoryToSandbox({
1521
- sandbox: toolSafeSandboxSession,
1522
- privateSessionDir,
1523
- historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME,
1524
- messages
1525
- });
1526
- }
1527
- function requestBuiltinToolApproval(toolCall) {
1528
- if (translatorState && currentEmit) {
1529
- for (const part of toolApprovalParts(translatorState, toolCall)) {
1530
- currentEmit(part);
1531
- }
1532
- }
1533
- return new Promise((resolve2) => {
1534
- pendingToolApprovals.set(toolCall.toolCallId, { resolve: resolve2 });
1535
- });
1536
- }
1537
- function rebuildAgent(rebuildInput) {
1538
- unsubscribe?.();
1539
- unsubscribe = void 0;
1540
- agent = new Agent({
1541
- model: agentModel.model,
1542
- ...activeModelId ? {
1543
- messageModelInfo: {
1544
- id: activeModelId,
1545
- provider: agentModel.providerId
1546
- }
1547
- } : {},
1548
- sessionId: input.sessionId,
1549
- systemPrompt: (() => {
1550
- const baseSystemPrompt = buildSystemPrompt({
1551
- sessionWorkDir: input.sessionWorkDir,
1552
- sandboxDescription: toolSafeSandboxSession.description
1553
- });
1554
- return rebuildInput.instructions ? `${baseSystemPrompt}
1555
-
1556
- ${rebuildInput.instructions}` : baseSystemPrompt;
1557
- })(),
1558
- initialMessages: currentMessages,
1559
- toolExecution: "parallel",
1560
- ...input.settings.maxIterations !== void 0 ? { maxIterations: input.settings.maxIterations } : {},
1561
- tools: [
1562
- ...buildBuiltinAgentTools({
1563
- ops,
1564
- activeNames: activeBuiltinNames,
1565
- pendingQuestions,
1566
- pendingQuestionResults
1567
- }),
1568
- ...rebuildInput.skillsRuntime.tool && activeBuiltinNames.includes("skills") ? [rebuildInput.skillsRuntime.tool] : [],
1569
- ...mcpRuntime.tools,
1570
- ...buildUserAgentTools({
1571
- specs: rebuildInput.userTools,
1572
- pendingToolResults
1573
- }),
1574
- ...rebuildInput.responseFormat?.type === "json" && rebuildInput.responseFormat.schema != null ? [
1575
- createTool2({
1576
- name: "structured_output",
1577
- description: rebuildInput.responseFormat.description ?? "Return the final structured output.",
1578
- inputSchema: {
1579
- type: "object",
1580
- properties: {
1581
- output: rebuildInput.responseFormat.schema
1582
- },
1583
- required: ["output"],
1584
- additionalProperties: false
1585
- },
1586
- lifecycle: { completesRun: true },
1587
- execute: async ({ output }) => JSON.stringify(output)
1588
- })
1589
- ] : []
1590
- ],
1591
- ...rebuildInput.responseFormat?.type === "json" ? { completionPolicy: { requireCompletionTool: true } } : {},
1592
- hooks: {
1593
- afterTool: ({ result }) => {
1594
- const toolResult = unwrapClineToolResult(result.output);
1595
- return toolResult == null ? void 0 : { result: toolResult };
1596
- }
1597
- },
1598
- // Built-in tools whose kind requires approval under the session's
1599
- // permission mode are marked `autoApprove: false`; the runtime then
1600
- // routes them through `requestToolApproval` below. User tools are
1601
- // approved by the harness framework, never by the adapter.
1602
- toolPolicies: Object.fromEntries(
1603
- activeBuiltinNames.filter(
1604
- (name) => clineBuiltinToolRequiresApproval({
1605
- permissionMode,
1606
- kind: CLINE_NATIVE_TOOL_KINDS[name]
1607
- })
1608
- ).map((name) => [name, { autoApprove: false }])
1609
- ),
1610
- requestToolApproval: (request) => {
1611
- if (!isClineBuiltinToolName(request.toolName)) {
1612
- return { approved: true };
1613
- }
1614
- return requestBuiltinToolApproval({
1615
- toolCallId: request.toolCallId,
1616
- toolName: request.toolName,
1617
- input: request.input
1618
- });
1619
- },
1620
- // Mid-turn user messages injected via `submitUserMessage` are consumed
1621
- // by the runtime between loop iterations, before the next model call.
1622
- consumePendingUserMessage
1623
- });
1624
- agentHasRun = false;
1625
- unsubscribe = agent.subscribe((event) => {
1626
- if (!translatorState || !currentEmit) return;
1627
- for (const part of translateClineEvent(event, translatorState)) {
1628
- currentEmit(part);
1629
- }
1630
- });
1631
- }
1632
- function createPromptControl(controlInput) {
1633
- const abortHandler = () => {
1634
- settlePendingToolResults("Turn aborted");
1635
- settlePendingToolApprovals("Turn aborted");
1636
- settlePendingQuestions("The question was cancelled.");
1637
- agent?.abort("Turn aborted by caller");
1638
- };
1639
- if (controlInput.abortSignal) {
1640
- controlInput.abortSignal.addEventListener("abort", abortHandler, {
1641
- once: true
1642
- });
1643
- void controlInput.done.then(
1644
- () => {
1645
- controlInput.abortSignal?.removeEventListener("abort", abortHandler);
1646
- },
1647
- () => {
1648
- controlInput.abortSignal?.removeEventListener("abort", abortHandler);
1649
- }
1650
- );
1651
- }
1652
- return {
1653
- async submitToolResult(args) {
1654
- const pendingQuestion = pendingQuestions.get(args.toolCallId);
1655
- if (pendingQuestion != null) {
1656
- pendingQuestions.delete(args.toolCallId);
1657
- pendingQuestion.resolve(
1658
- toClineQuestionResult({
1659
- nativeInput: pendingQuestion.input,
1660
- output: args.output
1661
- })
1662
- );
1663
- return;
1664
- }
1665
- if (args.toolResult?.toolName === "askUserQuestions") {
1666
- const nativeRequest = args.toolResult.providerOptions?.cline?.nativeRequest;
1667
- if (nativeRequest == null) return;
1668
- pendingQuestionResults.set(clineQuestionKey(nativeRequest), {
1669
- output: args.output
1670
- });
1671
- return;
1672
- }
1673
- const pending = pendingToolResults.get(args.toolCallId);
1674
- if (!pending) return;
1675
- pendingToolResults.delete(args.toolCallId);
1676
- pending.resolve(
1677
- createClineToolResult({
1678
- output: args.output,
1679
- isError: args.isError
1680
- })
1681
- );
1682
- },
1683
- async submitToolApproval(args) {
1684
- const pending = pendingToolApprovals.get(args.approvalId);
1685
- if (!pending) return;
1686
- pendingToolApprovals.delete(args.approvalId);
1687
- pending.resolve({
1688
- approved: args.approved,
1689
- ...args.reason !== void 0 ? { reason: args.reason } : {}
1690
- });
1691
- },
1692
- submitUserMessage(text) {
1693
- if (!acceptingUserMessages) {
1694
- return Promise.reject(
1695
- new Error("Cline has no running turn to steer.")
1696
- );
1697
- }
1698
- return new Promise((resolve2, reject) => {
1699
- pendingUserMessages.push({ text, resolve: resolve2, reject });
1700
- });
1701
- },
1702
- done: controlInput.done
1703
- };
1704
- }
1705
- async function runTurn(turnOpts) {
1706
- if (stopped) {
1707
- throw new Error("Cline session has been stopped.");
1708
- }
1709
- const userTools = turnOpts.tools;
1710
- const skillsRuntime = createClineSkillsRuntime({
1711
- skills: turnOpts.skills
1712
- });
1713
- if (turnOpts.responseFormat?.type === "json" && turnOpts.responseFormat.schema == null) {
1714
- throw new HarnessCapabilityUnsupportedError2({
1715
- message: "Harness 'cline' requires a JSON schema for structured output.",
1716
- harnessId: HARNESS_ID2
1717
- });
1718
- }
1719
- if (turnOpts.responseFormat?.type === "json" && agentModel.providerId === "openai-codex-cli") {
1720
- throw new HarnessCapabilityUnsupportedError2({
1721
- message: "Harness 'cline' cannot require structured output with the openai-codex-cli provider because that provider does not expose external tools.",
1722
- harnessId: HARNESS_ID2
1723
- });
1724
- }
1725
- if (turnOpts.model != null && turnOpts.model !== activeModelId) {
1726
- activeModelId = turnOpts.model;
1727
- agentModel = createClineAgentModel({
1728
- settings: input.settings,
1729
- clientApp: input.clientApp,
1730
- modelId: activeModelId
1731
- });
1732
- }
1733
- const signature = JSON.stringify({
1734
- modelId: activeModelId,
1735
- tools: userTools,
1736
- responseFormat: turnOpts.responseFormat,
1737
- instructions: turnOpts.instructions,
1738
- skills: skillsRuntime.signature
1739
- });
1740
- if (agent == null || signature !== lastAgentConfigurationSignature) {
1741
- currentMessages = agent?.snapshot().messages ?? currentMessages;
1742
- rebuildAgent({
1743
- userTools,
1744
- skillsRuntime,
1745
- responseFormat: turnOpts.responseFormat,
1746
- ...turnOpts.instructions ? { instructions: turnOpts.instructions } : {}
1747
- });
1748
- lastAgentConfigurationSignature = signature;
1749
- }
1750
- currentEmit = turnOpts.emit;
1751
- const userToolNames = new Set(userTools.map((tool2) => tool2.name));
1752
- translatorState = createClineTranslatorState({
1753
- builtinToolNames: activeBuiltinNames.filter(
1754
- (name) => !userToolNames.has(name)
1755
- ),
1756
- hostToolNames: userToolNames,
1757
- ignoredToolNames: turnOpts.responseFormat?.type === "json" ? ["structured_output"] : [],
1758
- mcpToolNames: mcpRuntime.toolNames
1759
- });
1760
- turnOpts.emit({
1761
- type: "stream-start",
1762
- ...activeModelId ? { modelId: activeModelId } : {}
1763
- });
1764
- acceptingUserMessages = true;
1765
- const turnPromise = (async () => {
1766
- const runtime = agent;
1767
- try {
1768
- let nextText = turnOpts.text;
1769
- let result;
1770
- for (; ; ) {
1771
- result = nextText === void 0 ? await runtime.continue() : agentHasRun ? await runtime.continue(nextText) : await runtime.run(nextText);
1772
- agentHasRun = true;
1773
- currentMessages = result.messages;
1774
- if (translatorState) {
1775
- for (const part of finishClineTranslation(translatorState)) {
1776
- currentEmit?.(part);
1777
- }
1778
- }
1779
- if (result.status !== "completed") break;
1780
- const pendingUserMessage = consumePendingUserMessage();
1781
- if (pendingUserMessage == null) break;
1782
- nextText = pendingUserMessage;
1783
- }
1784
- if (turnOpts.responseFormat?.type === "json" && result.status === "completed") {
1785
- const id = `structured-output-${input.sessionId}`;
1786
- currentEmit?.({ type: "text-start", id });
1787
- currentEmit?.({
1788
- type: "text-delta",
1789
- id,
1790
- delta: result.outputText
1791
- });
1792
- currentEmit?.({ type: "text-end", id });
1793
- currentEmit?.({
1794
- type: "finish-step",
1795
- finishReason: { unified: "stop", raw: "structured-output" },
1796
- usage: {
1797
- inputTokens: {
1798
- total: 0,
1799
- noCache: void 0,
1800
- cacheRead: void 0,
1801
- cacheWrite: void 0
1802
- },
1803
- outputTokens: {
1804
- total: 0,
1805
- text: void 0,
1806
- reasoning: void 0
1807
- }
1808
- }
1809
- });
1810
- }
1811
- if (result.status === "aborted") {
1812
- if (suspending) return;
1813
- currentEmit?.({
1814
- type: "finish",
1815
- finishReason: mapRunFinishReason(result.status),
1816
- totalUsage: usageFromAgentUsage(result.usage)
1817
- });
1818
- return;
1819
- }
1820
- if (result.status === "failed") {
1821
- currentEmit?.({
1822
- type: "error",
1823
- error: result.error ?? new Error("Cline agent run failed")
1824
- });
1825
- return;
1826
- }
1827
- currentEmit?.({
1828
- type: "finish",
1829
- finishReason: mapRunFinishReason(result.status),
1830
- totalUsage: usageFromAgentUsage(result.usage)
1831
- });
1832
- } catch (error) {
1833
- if (suspending && isAbortError(error)) return;
1834
- currentEmit?.({ type: "error", error });
1835
- } finally {
1836
- acceptingUserMessages = false;
1837
- rejectPendingUserMessages(
1838
- new Error("Cline turn ended before accepting the user message.")
1839
- );
1840
- }
1841
- })();
1842
- const activeTurnToken = {};
1843
- const done = turnPromise.finally(() => {
1844
- if (activeTurn?.token === activeTurnToken) {
1845
- activeTurn = void 0;
1846
- }
1847
- currentEmit = void 0;
1848
- });
1849
- activeTurn = {
1850
- token: activeTurnToken,
1851
- done
1852
- };
1853
- return createPromptControl({
1854
- done,
1855
- ...turnOpts.abortSignal ? { abortSignal: turnOpts.abortSignal } : {}
1856
- });
1857
- }
1858
- async function teardown() {
1859
- unsubscribe?.();
1860
- unsubscribe = void 0;
1861
- agent = void 0;
1862
- await mcpRuntime.dispose();
1863
- }
1864
- const doStop = async () => {
1865
- if (stopped) {
1866
- throw new Error("Cline session has been stopped.");
1867
- }
1868
- stopped = true;
1869
- parkedClineSessions.delete(input.sessionId);
1870
- settlePendingToolResults("Cline session stopped");
1871
- settlePendingToolApprovals("Cline session stopped");
1872
- settlePendingQuestions("The question was cancelled.");
1873
- try {
1874
- await persistHistory();
1875
- } catch {
1876
- }
1877
- agent?.abort("Cline session stopped");
1878
- await teardown();
1879
- return {
1880
- type: "resume-session",
1881
- harnessId: HARNESS_ID2,
1882
- specificationVersion: "harness-v1",
1883
- data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1884
- };
1885
- };
1886
- const sessionImpl = {
1887
- sessionId: input.sessionId,
1888
- isResume: input.isResume,
1889
- // The Cline runtime has no bridge to attach to and no in-sandbox event
1890
- // log to replay; its only cross-process resume path is restoring the
1891
- // persisted history on a fresh runtime, i.e. `rerun`.
1892
- doPromptTurn: async (promptOpts) => {
1893
- return runTurn({
1894
- text: extractUserText(promptOpts.prompt),
1895
- ...promptOpts.model ? { model: promptOpts.model } : {},
1896
- skills: promptOpts.skills,
1897
- tools: promptOpts.tools ?? [],
1898
- ...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
1899
- emit: promptOpts.emit,
1900
- ...promptOpts.abortSignal ? { abortSignal: promptOpts.abortSignal } : {},
1901
- responseFormat: promptOpts.responseFormat
1902
- });
1903
- },
1904
- doContinueTurn: async (continueOpts) => {
1905
- if (activeTurn != null) {
1906
- currentEmit = continueOpts.emit;
1907
- return createPromptControl({
1908
- done: activeTurn.done,
1909
- ...continueOpts.abortSignal ? { abortSignal: continueOpts.abortSignal } : {}
1910
- });
1911
- }
1912
- return runTurn({
1913
- text: void 0,
1914
- ...continueOpts.model ? { model: continueOpts.model } : {},
1915
- skills: continueOpts.skills,
1916
- tools: continueOpts.tools ?? [],
1917
- ...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
1918
- emit: continueOpts.emit,
1919
- ...continueOpts.abortSignal ? { abortSignal: continueOpts.abortSignal } : {},
1920
- responseFormat: continueOpts.responseFormat
1921
- });
1922
- },
1923
- doCompact: async () => {
1924
- throw new HarnessCapabilityUnsupportedError2({
1925
- message: "cline: manual compaction is not supported by the standalone Cline runtime.",
1926
- harnessId: HARNESS_ID2
1927
- });
1928
- },
1929
- doDestroy: async () => {
1930
- if (stopped) return;
1931
- stopped = true;
1932
- parkedClineSessions.delete(input.sessionId);
1933
- settlePendingToolResults("Cline session stopped");
1934
- settlePendingToolApprovals("Cline session stopped");
1935
- agent?.abort("Cline session destroyed");
1936
- await teardown();
1937
- },
1938
- doStop,
1939
- doDetach: async () => {
1940
- if (activeTurn != null || pendingToolResults.size > 0 || pendingQuestions.size > 0) {
1941
- parkedClineSessions.set(input.sessionId, sessionImpl);
1942
- try {
1943
- await persistHistory();
1944
- } catch {
1945
- }
1946
- return {
1947
- type: "resume-session",
1948
- harnessId: HARNESS_ID2,
1949
- specificationVersion: "harness-v1",
1950
- data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1951
- };
1952
- }
1953
- return doStop();
1954
- },
1955
- doSuspendTurn: async () => {
1956
- if (stopped) {
1957
- throw new Error("Cline session has been stopped.");
1958
- }
1959
- if (activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0 || pendingQuestions.size > 0)) {
1960
- parkedClineSessions.set(input.sessionId, sessionImpl);
1961
- try {
1962
- await persistHistory();
1963
- } catch {
1964
- }
1965
- return {
1966
- type: "continue-turn",
1967
- harnessId: HARNESS_ID2,
1968
- specificationVersion: "harness-v1",
1969
- data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1970
- };
1971
- }
1972
- suspending = true;
1973
- agent?.abort("Cline session suspended");
1974
- if (activeTurn) {
1975
- await activeTurn.done.catch(() => {
1976
- });
1977
- }
1978
- try {
1979
- await persistHistory();
1980
- } catch {
1981
- }
1982
- stopped = true;
1983
- parkedClineSessions.delete(input.sessionId);
1984
- settlePendingToolResults("Cline session suspended");
1985
- settlePendingToolApprovals("Cline session suspended");
1986
- await teardown();
1987
- return {
1988
- type: "continue-turn",
1989
- harnessId: HARNESS_ID2,
1990
- specificationVersion: "harness-v1",
1991
- data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1992
- };
1993
- }
1994
- };
1995
- return sessionImpl;
1229
+ if (input.isResume) {
1230
+ const parkedSession = parkedClineSessions.get(input.sessionId);
1231
+ if (parkedSession) {
1232
+ parkedClineSessions.delete(input.sessionId);
1233
+ return {
1234
+ ...parkedSession,
1235
+ isResume: true
1236
+ };
1237
+ }
1238
+ }
1239
+ const toolSafeSandboxSession = getRestrictedSandboxSession(input.sandboxSession);
1240
+ const privateSessionDir = resolveClinePrivateSessionDirectory({
1241
+ sandboxHomeDir: await resolveSandboxHomeDir({
1242
+ sandbox: toolSafeSandboxSession,
1243
+ ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
1244
+ }),
1245
+ sessionWorkDir: input.sessionWorkDir,
1246
+ sessionId: input.sessionId
1247
+ });
1248
+ const permissionMode = input.permissionMode ?? "allow-all";
1249
+ const activeBuiltinNames = resolveActiveClineBuiltinNames(input.builtinToolFiltering);
1250
+ const ops = createClineRemoteOps({
1251
+ sandbox: toolSafeSandboxSession,
1252
+ workDir: input.sessionWorkDir
1253
+ });
1254
+ let currentMessages = [];
1255
+ if (input.isResume && input.resumeHistoryFileName) {
1256
+ const restored = await pullHistoryFromSandbox({
1257
+ sandbox: toolSafeSandboxSession,
1258
+ privateSessionDir,
1259
+ historyFileName: safeClineHistoryFileName(input.resumeHistoryFileName),
1260
+ ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
1261
+ });
1262
+ if (restored) currentMessages = restored;
1263
+ }
1264
+ const mcpRuntime = await createClineMcpRuntime({ mcpServers: input.settings.mcpServers });
1265
+ let activeModelId;
1266
+ let agentModel = createClineAgentModel({
1267
+ settings: input.settings,
1268
+ clientApp: input.clientApp,
1269
+ modelId: activeModelId
1270
+ });
1271
+ let agent;
1272
+ let unsubscribe;
1273
+ let lastAgentConfigurationSignature;
1274
+ let agentHasRun = false;
1275
+ let stopped = false;
1276
+ let suspending = false;
1277
+ const pendingToolResults = /* @__PURE__ */ new Map();
1278
+ const pendingToolApprovals = /* @__PURE__ */ new Map();
1279
+ const pendingUserMessages = [];
1280
+ let acceptingUserMessages = false;
1281
+ let currentEmit;
1282
+ let translatorState;
1283
+ const pendingQuestions = /* @__PURE__ */ new Map();
1284
+ const pendingQuestionResults = /* @__PURE__ */ new Map();
1285
+ let activeTurn;
1286
+ function settlePendingToolResults(reason) {
1287
+ for (const pending of pendingToolResults.values()) pending.resolve(createClineToolResult({
1288
+ output: { error: reason },
1289
+ isError: true
1290
+ }));
1291
+ pendingToolResults.clear();
1292
+ }
1293
+ function settlePendingToolApprovals(reason) {
1294
+ for (const pending of pendingToolApprovals.values()) pending.resolve({
1295
+ approved: false,
1296
+ reason
1297
+ });
1298
+ pendingToolApprovals.clear();
1299
+ }
1300
+ function consumePendingUserMessage() {
1301
+ const pending = pendingUserMessages.shift();
1302
+ if (pending == null) return void 0;
1303
+ pending.resolve();
1304
+ return pending.text;
1305
+ }
1306
+ function rejectPendingUserMessages(error) {
1307
+ for (const pending of pendingUserMessages) pending.reject(error);
1308
+ pendingUserMessages.length = 0;
1309
+ }
1310
+ function settlePendingQuestions(reason) {
1311
+ for (const pending of pendingQuestions.values()) pending.resolve(reason);
1312
+ pendingQuestions.clear();
1313
+ pendingQuestionResults.clear();
1314
+ }
1315
+ async function persistHistory() {
1316
+ const messages = agent?.snapshot().messages ?? currentMessages;
1317
+ await persistHistoryToSandbox({
1318
+ sandbox: toolSafeSandboxSession,
1319
+ privateSessionDir,
1320
+ historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME,
1321
+ messages
1322
+ });
1323
+ }
1324
+ function requestBuiltinToolApproval(toolCall) {
1325
+ if (translatorState && currentEmit) for (const part of toolApprovalParts(translatorState, toolCall)) currentEmit(part);
1326
+ return new Promise((resolve) => {
1327
+ pendingToolApprovals.set(toolCall.toolCallId, { resolve });
1328
+ });
1329
+ }
1330
+ function rebuildAgent(rebuildInput) {
1331
+ unsubscribe?.();
1332
+ unsubscribe = void 0;
1333
+ agent = new Agent({
1334
+ model: agentModel.model,
1335
+ ...activeModelId ? { messageModelInfo: {
1336
+ id: activeModelId,
1337
+ provider: agentModel.providerId
1338
+ } } : {},
1339
+ sessionId: input.sessionId,
1340
+ systemPrompt: (() => {
1341
+ const baseSystemPrompt = buildSystemPrompt({
1342
+ sessionWorkDir: input.sessionWorkDir,
1343
+ sandboxDescription: toolSafeSandboxSession.description
1344
+ });
1345
+ return rebuildInput.instructions ? `${baseSystemPrompt}\n\n${rebuildInput.instructions}` : baseSystemPrompt;
1346
+ })(),
1347
+ initialMessages: currentMessages,
1348
+ toolExecution: "parallel",
1349
+ ...input.settings.maxIterations !== void 0 ? { maxIterations: input.settings.maxIterations } : {},
1350
+ tools: [
1351
+ ...buildBuiltinAgentTools({
1352
+ ops,
1353
+ activeNames: activeBuiltinNames,
1354
+ pendingQuestions,
1355
+ pendingQuestionResults
1356
+ }),
1357
+ ...rebuildInput.skillsRuntime.tool && activeBuiltinNames.includes("skills") ? [rebuildInput.skillsRuntime.tool] : [],
1358
+ ...mcpRuntime.tools,
1359
+ ...buildUserAgentTools({
1360
+ specs: rebuildInput.userTools,
1361
+ pendingToolResults
1362
+ }),
1363
+ ...rebuildInput.responseFormat?.type === "json" && rebuildInput.responseFormat.schema != null ? [createTool({
1364
+ name: "structured_output",
1365
+ description: rebuildInput.responseFormat.description ?? "Return the final structured output.",
1366
+ inputSchema: {
1367
+ type: "object",
1368
+ properties: { output: rebuildInput.responseFormat.schema },
1369
+ required: ["output"],
1370
+ additionalProperties: false
1371
+ },
1372
+ lifecycle: { completesRun: true },
1373
+ execute: async ({ output }) => JSON.stringify(output)
1374
+ })] : []
1375
+ ],
1376
+ ...rebuildInput.responseFormat?.type === "json" ? { completionPolicy: { requireCompletionTool: true } } : {},
1377
+ hooks: { afterTool: ({ result }) => {
1378
+ const toolResult = unwrapClineToolResult(result.output);
1379
+ return toolResult == null ? void 0 : { result: toolResult };
1380
+ } },
1381
+ toolPolicies: Object.fromEntries(activeBuiltinNames.filter((name) => clineBuiltinToolRequiresApproval({
1382
+ permissionMode,
1383
+ kind: CLINE_NATIVE_TOOL_KINDS[name]
1384
+ })).map((name) => [name, { autoApprove: false }])),
1385
+ requestToolApproval: (request) => {
1386
+ if (!isClineBuiltinToolName(request.toolName)) return { approved: true };
1387
+ return requestBuiltinToolApproval({
1388
+ toolCallId: request.toolCallId,
1389
+ toolName: request.toolName,
1390
+ input: request.input
1391
+ });
1392
+ },
1393
+ consumePendingUserMessage
1394
+ });
1395
+ agentHasRun = false;
1396
+ unsubscribe = agent.subscribe((event) => {
1397
+ if (!translatorState || !currentEmit) return;
1398
+ for (const part of translateClineEvent(event, translatorState)) currentEmit(part);
1399
+ });
1400
+ }
1401
+ function createPromptControl(controlInput) {
1402
+ const abortHandler = () => {
1403
+ settlePendingToolResults("Turn aborted");
1404
+ settlePendingToolApprovals("Turn aborted");
1405
+ settlePendingQuestions("The question was cancelled.");
1406
+ agent?.abort("Turn aborted by caller");
1407
+ };
1408
+ if (controlInput.abortSignal) {
1409
+ controlInput.abortSignal.addEventListener("abort", abortHandler, { once: true });
1410
+ controlInput.done.then(() => {
1411
+ controlInput.abortSignal?.removeEventListener("abort", abortHandler);
1412
+ }, () => {
1413
+ controlInput.abortSignal?.removeEventListener("abort", abortHandler);
1414
+ });
1415
+ }
1416
+ return {
1417
+ async submitToolResult(args) {
1418
+ const pendingQuestion = pendingQuestions.get(args.toolCallId);
1419
+ if (pendingQuestion != null) {
1420
+ pendingQuestions.delete(args.toolCallId);
1421
+ pendingQuestion.resolve(toClineQuestionResult({
1422
+ nativeInput: pendingQuestion.input,
1423
+ output: args.output
1424
+ }));
1425
+ return;
1426
+ }
1427
+ if (args.toolResult?.toolName === "askUserQuestions") {
1428
+ const nativeRequest = args.toolResult.providerOptions?.cline?.nativeRequest;
1429
+ if (nativeRequest == null) return;
1430
+ pendingQuestionResults.set(clineQuestionKey(nativeRequest), { output: args.output });
1431
+ return;
1432
+ }
1433
+ const pending = pendingToolResults.get(args.toolCallId);
1434
+ if (!pending) return;
1435
+ pendingToolResults.delete(args.toolCallId);
1436
+ pending.resolve(createClineToolResult({
1437
+ output: args.output,
1438
+ isError: args.isError
1439
+ }));
1440
+ },
1441
+ async submitToolApproval(args) {
1442
+ const pending = pendingToolApprovals.get(args.approvalId);
1443
+ if (!pending) return;
1444
+ pendingToolApprovals.delete(args.approvalId);
1445
+ pending.resolve({
1446
+ approved: args.approved,
1447
+ ...args.reason !== void 0 ? { reason: args.reason } : {}
1448
+ });
1449
+ },
1450
+ submitUserMessage(text) {
1451
+ if (!acceptingUserMessages) return Promise.reject(/* @__PURE__ */ new Error("Cline has no running turn to steer."));
1452
+ return new Promise((resolve, reject) => {
1453
+ pendingUserMessages.push({
1454
+ text,
1455
+ resolve,
1456
+ reject
1457
+ });
1458
+ });
1459
+ },
1460
+ done: controlInput.done
1461
+ };
1462
+ }
1463
+ async function runTurn(turnOpts) {
1464
+ if (stopped) throw new Error("Cline session has been stopped.");
1465
+ const userTools = turnOpts.tools;
1466
+ const skillsRuntime = createClineSkillsRuntime({ skills: turnOpts.skills });
1467
+ if (turnOpts.responseFormat?.type === "json" && turnOpts.responseFormat.schema == null) throw new HarnessCapabilityUnsupportedError({
1468
+ message: "Harness 'cline' requires a JSON schema for structured output.",
1469
+ harnessId: HARNESS_ID
1470
+ });
1471
+ if (turnOpts.responseFormat?.type === "json" && agentModel.providerId === "openai-codex-cli") throw new HarnessCapabilityUnsupportedError({
1472
+ message: "Harness 'cline' cannot require structured output with the openai-codex-cli provider because that provider does not expose external tools.",
1473
+ harnessId: HARNESS_ID
1474
+ });
1475
+ if (turnOpts.model != null && turnOpts.model !== activeModelId) {
1476
+ activeModelId = turnOpts.model;
1477
+ agentModel = createClineAgentModel({
1478
+ settings: input.settings,
1479
+ clientApp: input.clientApp,
1480
+ modelId: activeModelId
1481
+ });
1482
+ }
1483
+ const signature = JSON.stringify({
1484
+ modelId: activeModelId,
1485
+ tools: userTools,
1486
+ responseFormat: turnOpts.responseFormat,
1487
+ instructions: turnOpts.instructions,
1488
+ skills: skillsRuntime.signature
1489
+ });
1490
+ if (agent == null || signature !== lastAgentConfigurationSignature) {
1491
+ currentMessages = agent?.snapshot().messages ?? currentMessages;
1492
+ rebuildAgent({
1493
+ userTools,
1494
+ skillsRuntime,
1495
+ responseFormat: turnOpts.responseFormat,
1496
+ ...turnOpts.instructions ? { instructions: turnOpts.instructions } : {}
1497
+ });
1498
+ lastAgentConfigurationSignature = signature;
1499
+ }
1500
+ currentEmit = turnOpts.emit;
1501
+ const userToolNames = new Set(userTools.map((tool) => tool.name));
1502
+ translatorState = createClineTranslatorState({
1503
+ builtinToolNames: activeBuiltinNames.filter((name) => !userToolNames.has(name)),
1504
+ hostToolNames: userToolNames,
1505
+ ignoredToolNames: turnOpts.responseFormat?.type === "json" ? ["structured_output"] : [],
1506
+ mcpToolNames: mcpRuntime.toolNames
1507
+ });
1508
+ turnOpts.emit({
1509
+ type: "stream-start",
1510
+ ...activeModelId ? { modelId: activeModelId } : {}
1511
+ });
1512
+ acceptingUserMessages = true;
1513
+ const turnPromise = (async () => {
1514
+ const runtime = agent;
1515
+ try {
1516
+ let nextText = turnOpts.text;
1517
+ let result;
1518
+ for (;;) {
1519
+ result = nextText === void 0 ? await runtime.continue() : agentHasRun ? await runtime.continue(nextText) : await runtime.run(nextText);
1520
+ agentHasRun = true;
1521
+ currentMessages = result.messages;
1522
+ if (translatorState) for (const part of finishClineTranslation(translatorState)) currentEmit?.(part);
1523
+ if (result.status !== "completed") break;
1524
+ const pendingUserMessage = consumePendingUserMessage();
1525
+ if (pendingUserMessage == null) break;
1526
+ nextText = pendingUserMessage;
1527
+ }
1528
+ if (turnOpts.responseFormat?.type === "json" && result.status === "completed") {
1529
+ const id = `structured-output-${input.sessionId}`;
1530
+ currentEmit?.({
1531
+ type: "text-start",
1532
+ id
1533
+ });
1534
+ currentEmit?.({
1535
+ type: "text-delta",
1536
+ id,
1537
+ delta: result.outputText
1538
+ });
1539
+ currentEmit?.({
1540
+ type: "text-end",
1541
+ id
1542
+ });
1543
+ currentEmit?.({
1544
+ type: "finish-step",
1545
+ finishReason: {
1546
+ unified: "stop",
1547
+ raw: "structured-output"
1548
+ },
1549
+ usage: {
1550
+ inputTokens: {
1551
+ total: 0,
1552
+ noCache: void 0,
1553
+ cacheRead: void 0,
1554
+ cacheWrite: void 0
1555
+ },
1556
+ outputTokens: {
1557
+ total: 0,
1558
+ text: void 0,
1559
+ reasoning: void 0
1560
+ }
1561
+ }
1562
+ });
1563
+ }
1564
+ if (result.status === "aborted") {
1565
+ if (suspending) return;
1566
+ currentEmit?.({
1567
+ type: "finish",
1568
+ finishReason: mapRunFinishReason(result.status),
1569
+ totalUsage: usageFromAgentUsage(result.usage)
1570
+ });
1571
+ return;
1572
+ }
1573
+ if (result.status === "failed") {
1574
+ currentEmit?.({
1575
+ type: "error",
1576
+ error: result.error ?? /* @__PURE__ */ new Error("Cline agent run failed")
1577
+ });
1578
+ return;
1579
+ }
1580
+ currentEmit?.({
1581
+ type: "finish",
1582
+ finishReason: mapRunFinishReason(result.status),
1583
+ totalUsage: usageFromAgentUsage(result.usage)
1584
+ });
1585
+ } catch (error) {
1586
+ if (suspending && isAbortError(error)) return;
1587
+ currentEmit?.({
1588
+ type: "error",
1589
+ error
1590
+ });
1591
+ } finally {
1592
+ acceptingUserMessages = false;
1593
+ rejectPendingUserMessages(/* @__PURE__ */ new Error("Cline turn ended before accepting the user message."));
1594
+ }
1595
+ })();
1596
+ const activeTurnToken = {};
1597
+ const done = turnPromise.finally(() => {
1598
+ if (activeTurn?.token === activeTurnToken) activeTurn = void 0;
1599
+ currentEmit = void 0;
1600
+ });
1601
+ activeTurn = {
1602
+ token: activeTurnToken,
1603
+ done
1604
+ };
1605
+ return createPromptControl({
1606
+ done,
1607
+ ...turnOpts.abortSignal ? { abortSignal: turnOpts.abortSignal } : {}
1608
+ });
1609
+ }
1610
+ async function teardown() {
1611
+ unsubscribe?.();
1612
+ unsubscribe = void 0;
1613
+ agent = void 0;
1614
+ await mcpRuntime.dispose();
1615
+ }
1616
+ const doStop = async () => {
1617
+ if (stopped) throw new Error("Cline session has been stopped.");
1618
+ stopped = true;
1619
+ parkedClineSessions.delete(input.sessionId);
1620
+ settlePendingToolResults("Cline session stopped");
1621
+ settlePendingToolApprovals("Cline session stopped");
1622
+ settlePendingQuestions("The question was cancelled.");
1623
+ try {
1624
+ await persistHistory();
1625
+ } catch {}
1626
+ agent?.abort("Cline session stopped");
1627
+ await teardown();
1628
+ return {
1629
+ type: "resume-session",
1630
+ harnessId: HARNESS_ID,
1631
+ specificationVersion: "harness-v1",
1632
+ data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1633
+ };
1634
+ };
1635
+ const sessionImpl = {
1636
+ sessionId: input.sessionId,
1637
+ isResume: input.isResume,
1638
+ doPromptTurn: async (promptOpts) => {
1639
+ return runTurn({
1640
+ text: extractUserText(promptOpts.prompt),
1641
+ ...promptOpts.model ? { model: promptOpts.model } : {},
1642
+ skills: promptOpts.skills,
1643
+ tools: promptOpts.tools ?? [],
1644
+ ...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
1645
+ emit: promptOpts.emit,
1646
+ ...promptOpts.abortSignal ? { abortSignal: promptOpts.abortSignal } : {},
1647
+ responseFormat: promptOpts.responseFormat
1648
+ });
1649
+ },
1650
+ doContinueTurn: async (continueOpts) => {
1651
+ if (activeTurn != null) {
1652
+ currentEmit = continueOpts.emit;
1653
+ return createPromptControl({
1654
+ done: activeTurn.done,
1655
+ ...continueOpts.abortSignal ? { abortSignal: continueOpts.abortSignal } : {}
1656
+ });
1657
+ }
1658
+ return runTurn({
1659
+ text: void 0,
1660
+ ...continueOpts.model ? { model: continueOpts.model } : {},
1661
+ skills: continueOpts.skills,
1662
+ tools: continueOpts.tools ?? [],
1663
+ ...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
1664
+ emit: continueOpts.emit,
1665
+ ...continueOpts.abortSignal ? { abortSignal: continueOpts.abortSignal } : {},
1666
+ responseFormat: continueOpts.responseFormat
1667
+ });
1668
+ },
1669
+ doCompact: async () => {
1670
+ throw new HarnessCapabilityUnsupportedError({
1671
+ message: "cline: manual compaction is not supported by the standalone Cline runtime.",
1672
+ harnessId: HARNESS_ID
1673
+ });
1674
+ },
1675
+ doDestroy: async () => {
1676
+ if (stopped) return;
1677
+ stopped = true;
1678
+ parkedClineSessions.delete(input.sessionId);
1679
+ settlePendingToolResults("Cline session stopped");
1680
+ settlePendingToolApprovals("Cline session stopped");
1681
+ agent?.abort("Cline session destroyed");
1682
+ await teardown();
1683
+ },
1684
+ doStop,
1685
+ doDetach: async () => {
1686
+ if (activeTurn != null || pendingToolResults.size > 0 || pendingQuestions.size > 0) {
1687
+ parkedClineSessions.set(input.sessionId, sessionImpl);
1688
+ try {
1689
+ await persistHistory();
1690
+ } catch {}
1691
+ return {
1692
+ type: "resume-session",
1693
+ harnessId: HARNESS_ID,
1694
+ specificationVersion: "harness-v1",
1695
+ data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1696
+ };
1697
+ }
1698
+ return doStop();
1699
+ },
1700
+ doSuspendTurn: async () => {
1701
+ if (stopped) throw new Error("Cline session has been stopped.");
1702
+ if (activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0 || pendingQuestions.size > 0)) {
1703
+ parkedClineSessions.set(input.sessionId, sessionImpl);
1704
+ try {
1705
+ await persistHistory();
1706
+ } catch {}
1707
+ return {
1708
+ type: "continue-turn",
1709
+ harnessId: HARNESS_ID,
1710
+ specificationVersion: "harness-v1",
1711
+ data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1712
+ };
1713
+ }
1714
+ suspending = true;
1715
+ agent?.abort("Cline session suspended");
1716
+ if (activeTurn) await activeTurn.done.catch(() => {});
1717
+ try {
1718
+ await persistHistory();
1719
+ } catch {}
1720
+ stopped = true;
1721
+ parkedClineSessions.delete(input.sessionId);
1722
+ settlePendingToolResults("Cline session suspended");
1723
+ settlePendingToolApprovals("Cline session suspended");
1724
+ await teardown();
1725
+ return {
1726
+ type: "continue-turn",
1727
+ harnessId: HARNESS_ID,
1728
+ specificationVersion: "harness-v1",
1729
+ data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
1730
+ };
1731
+ }
1732
+ };
1733
+ return sessionImpl;
1996
1734
  }
1997
-
1998
- // src/version.ts
1999
- var VERSION = true ? "1.0.60" : "0.0.0-test";
2000
-
2001
- // src/cline-harness.ts
2002
- var CLINE_CLIENT_APP = `ai-sdk-harness-cline/${VERSION}`;
2003
- var CLINE_BUILTIN_TOOLS = {
2004
- askUserQuestions: {
2005
- ...HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
2006
- nativeName: "ask_question",
2007
- toolUseKind: "readonly"
2008
- },
2009
- read: commonTool("read", {
2010
- nativeName: "read",
2011
- toolUseKind: "readonly",
2012
- description: "Read file contents.",
2013
- inputSchema: z2.object({
2014
- file_path: z2.string()
2015
- })
2016
- }),
2017
- write: commonTool("write", {
2018
- nativeName: "write",
2019
- toolUseKind: "edit",
2020
- description: "Overwrite or create a file.",
2021
- inputSchema: z2.object({
2022
- file_path: z2.string(),
2023
- content: z2.string()
2024
- })
2025
- }),
2026
- edit: commonTool("edit", {
2027
- nativeName: "edit",
2028
- toolUseKind: "edit",
2029
- description: "Edit a file by exact string replacement.",
2030
- inputSchema: z2.object({
2031
- file_path: z2.string(),
2032
- old_string: z2.string(),
2033
- new_string: z2.string()
2034
- })
2035
- }),
2036
- bash: commonTool("bash", {
2037
- nativeName: "bash",
2038
- toolUseKind: "bash",
2039
- description: "Execute a shell command in the sandbox.",
2040
- inputSchema: z2.object({
2041
- command: z2.string(),
2042
- timeout: z2.number().optional()
2043
- })
2044
- }),
2045
- grep: commonTool("grep", {
2046
- nativeName: "grep",
2047
- toolUseKind: "readonly",
2048
- description: "Search file contents with regex.",
2049
- inputSchema: z2.object({
2050
- pattern: z2.string(),
2051
- path: z2.string().optional(),
2052
- glob: z2.string().optional(),
2053
- ignoreCase: z2.boolean().optional(),
2054
- literal: z2.boolean().optional(),
2055
- context: z2.number().optional(),
2056
- limit: z2.number().optional()
2057
- })
2058
- }),
2059
- glob: commonTool("glob", {
2060
- nativeName: "glob",
2061
- toolUseKind: "readonly",
2062
- description: "Find files matching a glob pattern.",
2063
- inputSchema: z2.object({
2064
- pattern: z2.string(),
2065
- path: z2.string().optional(),
2066
- limit: z2.number().optional()
2067
- })
2068
- }),
2069
- ls: {
2070
- ...tool({
2071
- description: "List directory entries.",
2072
- inputSchema: z2.object({
2073
- path: z2.string().optional(),
2074
- limit: z2.number().optional()
2075
- }),
2076
- outputSchema: z2.unknown()
2077
- }),
2078
- nativeName: "ls",
2079
- toolUseKind: "readonly"
2080
- },
2081
- skills: {
2082
- ...tool({
2083
- description: "Execute a configured skill by name.",
2084
- inputSchema: z2.object({
2085
- skill: z2.string(),
2086
- args: z2.string().optional()
2087
- }),
2088
- outputSchema: z2.string()
2089
- }),
2090
- nativeName: "skills",
2091
- toolUseKind: "readonly"
2092
- }
1735
+ //#endregion
1736
+ //#region src/version.ts
1737
+ const VERSION = "1.0.61";
1738
+ //#endregion
1739
+ //#region src/cline-harness.ts
1740
+ /**
1741
+ * Value used to identify the harness in provider request headers.
1742
+ */
1743
+ const CLINE_CLIENT_APP = `ai-sdk-harness-cline/${VERSION}`;
1744
+ const CLINE_BUILTIN_TOOLS = {
1745
+ askUserQuestions: {
1746
+ ...HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
1747
+ nativeName: "ask_question",
1748
+ toolUseKind: "readonly"
1749
+ },
1750
+ read: commonTool("read", {
1751
+ nativeName: "read",
1752
+ toolUseKind: "readonly",
1753
+ description: "Read file contents.",
1754
+ inputSchema: z.object({ file_path: z.string() })
1755
+ }),
1756
+ write: commonTool("write", {
1757
+ nativeName: "write",
1758
+ toolUseKind: "edit",
1759
+ description: "Overwrite or create a file.",
1760
+ inputSchema: z.object({
1761
+ file_path: z.string(),
1762
+ content: z.string()
1763
+ })
1764
+ }),
1765
+ edit: commonTool("edit", {
1766
+ nativeName: "edit",
1767
+ toolUseKind: "edit",
1768
+ description: "Edit a file by exact string replacement.",
1769
+ inputSchema: z.object({
1770
+ file_path: z.string(),
1771
+ old_string: z.string(),
1772
+ new_string: z.string()
1773
+ })
1774
+ }),
1775
+ bash: commonTool("bash", {
1776
+ nativeName: "bash",
1777
+ toolUseKind: "bash",
1778
+ description: "Execute a shell command in the sandbox.",
1779
+ inputSchema: z.object({
1780
+ command: z.string(),
1781
+ timeout: z.number().optional()
1782
+ })
1783
+ }),
1784
+ grep: commonTool("grep", {
1785
+ nativeName: "grep",
1786
+ toolUseKind: "readonly",
1787
+ description: "Search file contents with regex.",
1788
+ inputSchema: z.object({
1789
+ pattern: z.string(),
1790
+ path: z.string().optional(),
1791
+ glob: z.string().optional(),
1792
+ ignoreCase: z.boolean().optional(),
1793
+ literal: z.boolean().optional(),
1794
+ context: z.number().optional(),
1795
+ limit: z.number().optional()
1796
+ })
1797
+ }),
1798
+ glob: commonTool("glob", {
1799
+ nativeName: "glob",
1800
+ toolUseKind: "readonly",
1801
+ description: "Find files matching a glob pattern.",
1802
+ inputSchema: z.object({
1803
+ pattern: z.string(),
1804
+ path: z.string().optional(),
1805
+ limit: z.number().optional()
1806
+ })
1807
+ }),
1808
+ ls: {
1809
+ ...tool({
1810
+ description: "List directory entries.",
1811
+ inputSchema: z.object({
1812
+ path: z.string().optional(),
1813
+ limit: z.number().optional()
1814
+ }),
1815
+ outputSchema: z.unknown()
1816
+ }),
1817
+ nativeName: "ls",
1818
+ toolUseKind: "readonly"
1819
+ },
1820
+ skills: {
1821
+ ...tool({
1822
+ description: "Execute a configured skill by name.",
1823
+ inputSchema: z.object({
1824
+ skill: z.string(),
1825
+ args: z.string().optional()
1826
+ }),
1827
+ outputSchema: z.string()
1828
+ }),
1829
+ nativeName: "skills",
1830
+ toolUseKind: "readonly"
1831
+ }
2093
1832
  };
2094
1833
  function createCline(settings = {}) {
2095
- return {
2096
- specificationVersion: "harness-v1",
2097
- harnessId: "cline",
2098
- builtinTools: CLINE_BUILTIN_TOOLS,
2099
- supportsBuiltinToolApprovals: true,
2100
- supportsBuiltinToolFiltering: true,
2101
- lifecycleStateSchema: clineResumeStateSchema,
2102
- doStart: async (startOpts) => {
2103
- const authentication = await resolveClineAuthentication({
2104
- auth: settings.auth,
2105
- providerId: settings.providerId,
2106
- apiKey: settings.apiKey
2107
- });
2108
- const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
2109
- const resumeData = lifecycleState?.data;
2110
- return createClineSession({
2111
- sessionId: startOpts.sessionId,
2112
- sandboxSession: startOpts.sandboxSession,
2113
- sessionWorkDir: startOpts.sessionWorkDir,
2114
- settings: {
2115
- authEnv: authentication.environment,
2116
- isAuthenticationEnvironmentOverride: isHarnessAuthenticationEnvironment3(settings.auth),
2117
- ...settings.mcpServers ? { mcpServers: settings.mcpServers } : {},
2118
- ...settings.providerId ? { providerId: settings.providerId } : {},
2119
- ...settings.apiKey ?? authentication.subscriptionApiKey ? { apiKey: settings.apiKey ?? authentication.subscriptionApiKey } : {},
2120
- ...settings.baseUrl ? { baseUrl: settings.baseUrl } : {},
2121
- ...settings.headers ? { headers: settings.headers } : {},
2122
- ...startOpts.headers ? { agentHeaders: startOpts.headers } : {},
2123
- ...settings.reasoningEffort !== void 0 ? { reasoningEffort: settings.reasoningEffort } : {},
2124
- ...settings.maxIterations !== void 0 ? { maxIterations: settings.maxIterations } : {}
2125
- },
2126
- clientApp: CLINE_CLIENT_APP,
2127
- isResume: lifecycleState != null,
2128
- ...startOpts.permissionMode ? { permissionMode: startOpts.permissionMode } : {},
2129
- ...startOpts.builtinToolFiltering ? { builtinToolFiltering: startOpts.builtinToolFiltering } : {},
2130
- ...resumeData?.historyFileName ? { resumeHistoryFileName: resumeData.historyFileName } : {},
2131
- ...startOpts.abortSignal ? { abortSignal: startOpts.abortSignal } : {}
2132
- });
2133
- }
2134
- };
1834
+ return {
1835
+ specificationVersion: "harness-v1",
1836
+ harnessId: "cline",
1837
+ builtinTools: CLINE_BUILTIN_TOOLS,
1838
+ supportsBuiltinToolApprovals: true,
1839
+ supportsBuiltinToolFiltering: true,
1840
+ lifecycleStateSchema: clineResumeStateSchema,
1841
+ doStart: async (startOpts) => {
1842
+ const authentication = await resolveClineAuthentication({
1843
+ auth: settings.auth,
1844
+ providerId: settings.providerId,
1845
+ apiKey: settings.apiKey
1846
+ });
1847
+ const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
1848
+ const resumeData = lifecycleState?.data;
1849
+ return createClineSession({
1850
+ sessionId: startOpts.sessionId,
1851
+ sandboxSession: startOpts.sandboxSession,
1852
+ sessionWorkDir: startOpts.sessionWorkDir,
1853
+ settings: {
1854
+ authEnv: authentication.environment,
1855
+ isAuthenticationEnvironmentOverride: isHarnessAuthenticationEnvironment(settings.auth),
1856
+ ...settings.mcpServers ? { mcpServers: settings.mcpServers } : {},
1857
+ ...settings.providerId ? { providerId: settings.providerId } : {},
1858
+ ...settings.apiKey ?? authentication.subscriptionApiKey ? { apiKey: settings.apiKey ?? authentication.subscriptionApiKey } : {},
1859
+ ...settings.baseUrl ? { baseUrl: settings.baseUrl } : {},
1860
+ ...settings.headers ? { headers: settings.headers } : {},
1861
+ ...startOpts.headers ? { agentHeaders: startOpts.headers } : {},
1862
+ ...settings.reasoningEffort !== void 0 ? { reasoningEffort: settings.reasoningEffort } : {},
1863
+ ...settings.maxIterations !== void 0 ? { maxIterations: settings.maxIterations } : {}
1864
+ },
1865
+ clientApp: CLINE_CLIENT_APP,
1866
+ isResume: lifecycleState != null,
1867
+ ...startOpts.permissionMode ? { permissionMode: startOpts.permissionMode } : {},
1868
+ ...startOpts.builtinToolFiltering ? { builtinToolFiltering: startOpts.builtinToolFiltering } : {},
1869
+ ...resumeData?.historyFileName ? { resumeHistoryFileName: resumeData.historyFileName } : {},
1870
+ ...startOpts.abortSignal ? { abortSignal: startOpts.abortSignal } : {}
1871
+ });
1872
+ }
1873
+ };
2135
1874
  }
1875
+ //#endregion
1876
+ //#region src/index.ts
1877
+ /**
1878
+ * Default `cline` harness instance with no overrides — suitable for the
1879
+ * common case where the Cline SDK's defaults are fine. Equivalent to
1880
+ * `createCline()`.
1881
+ */
1882
+ const cline = createCline();
1883
+ //#endregion
1884
+ export { VERSION, cline, createCline };
2136
1885
 
2137
- // src/index.ts
2138
- var cline = createCline();
2139
- export {
2140
- VERSION,
2141
- cline,
2142
- createCline
2143
- };
2144
1886
  //# sourceMappingURL=index.js.map