@clinebot/core 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-config-loader.d.ts +1 -1
- package/dist/agents/agent-config-parser.d.ts +5 -2
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/plugin-config-loader.d.ts +4 -0
- package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
- package/dist/agents/plugin-sandbox.d.ts +4 -0
- package/dist/index.node.d.ts +1 -0
- package/dist/index.node.js +658 -407
- package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
- package/dist/session/default-session-manager.d.ts +5 -0
- package/dist/session/session-config-builder.d.ts +4 -1
- package/dist/session/session-manager.d.ts +1 -0
- package/dist/session/unified-session-persistence-service.d.ts +6 -0
- package/dist/session/utils/types.d.ts +9 -0
- package/dist/tools/definitions.d.ts +2 -2
- package/dist/tools/presets.d.ts +3 -3
- package/dist/tools/schemas.d.ts +14 -14
- package/dist/types/config.d.ts +5 -0
- package/dist/types/events.d.ts +22 -0
- package/package.json +5 -4
- package/src/agents/agent-config-loader.test.ts +2 -0
- package/src/agents/agent-config-loader.ts +1 -0
- package/src/agents/agent-config-parser.ts +12 -5
- package/src/agents/index.ts +1 -0
- package/src/agents/plugin-config-loader.test.ts +49 -0
- package/src/agents/plugin-config-loader.ts +10 -73
- package/src/agents/plugin-loader.test.ts +128 -2
- package/src/agents/plugin-loader.ts +70 -5
- package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
- package/src/agents/plugin-sandbox.test.ts +198 -1
- package/src/agents/plugin-sandbox.ts +223 -353
- package/src/index.node.ts +4 -0
- package/src/runtime/hook-file-hooks.test.ts +1 -1
- package/src/runtime/hook-file-hooks.ts +16 -6
- package/src/runtime/runtime-builder.test.ts +67 -0
- package/src/runtime/runtime-builder.ts +70 -16
- package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
- package/src/session/default-session-manager.e2e.test.ts +20 -1
- package/src/session/default-session-manager.test.ts +453 -1
- package/src/session/default-session-manager.ts +200 -0
- package/src/session/session-config-builder.ts +2 -0
- package/src/session/session-manager.ts +1 -0
- package/src/session/session-team-coordination.ts +30 -0
- package/src/session/unified-session-persistence-service.ts +45 -0
- package/src/session/utils/types.ts +10 -0
- package/src/storage/sqlite-team-store.ts +16 -5
- package/src/tools/definitions.test.ts +87 -8
- package/src/tools/definitions.ts +89 -70
- package/src/tools/presets.test.ts +2 -3
- package/src/tools/presets.ts +3 -3
- package/src/tools/schemas.ts +23 -22
- package/src/types/config.ts +5 -0
- package/src/types/events.ts +23 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export interface SubprocessSandboxOptions {
|
|
2
|
-
|
|
2
|
+
/** Inline script to execute via `node -e`. Mutually exclusive with {@link bootstrapFile}. */
|
|
3
|
+
bootstrapScript?: string;
|
|
4
|
+
/** Path to a JavaScript file to execute via `node <file>`. Mutually exclusive with {@link bootstrapScript}. */
|
|
5
|
+
bootstrapFile?: string;
|
|
3
6
|
name?: string;
|
|
7
|
+
onEvent?: (event: {
|
|
8
|
+
name: string;
|
|
9
|
+
payload?: unknown;
|
|
10
|
+
}) => void;
|
|
4
11
|
}
|
|
5
12
|
export interface SandboxCallOptions {
|
|
6
13
|
timeoutMs?: number;
|
|
@@ -60,7 +60,12 @@ export declare class DefaultSessionManager implements SessionManager {
|
|
|
60
60
|
private failSession;
|
|
61
61
|
private shutdownSession;
|
|
62
62
|
private updateStatus;
|
|
63
|
+
private handlePluginEvent;
|
|
64
|
+
private enqueuePendingPrompt;
|
|
65
|
+
private drainPendingPrompts;
|
|
63
66
|
private onAgentEvent;
|
|
67
|
+
private emitPendingPrompts;
|
|
68
|
+
private emitPendingPromptSubmitted;
|
|
64
69
|
private createSpawnTool;
|
|
65
70
|
private handleTeamEvent;
|
|
66
71
|
private runWithAuthRetry;
|
|
@@ -5,7 +5,10 @@ import type { CoreSessionConfig } from "../types/config";
|
|
|
5
5
|
import { type ProviderSettings } from "../types/provider-settings";
|
|
6
6
|
import type { StartSessionInput } from "./session-manager";
|
|
7
7
|
export declare function resolveWorkspacePath(config: CoreSessionConfig): string;
|
|
8
|
-
export declare function buildEffectiveConfig(input: StartSessionInput, hookPath: string, sessionId: string, defaultTelemetry: ITelemetryService | undefined
|
|
8
|
+
export declare function buildEffectiveConfig(input: StartSessionInput, hookPath: string, sessionId: string, defaultTelemetry: ITelemetryService | undefined, onPluginEvent?: (event: {
|
|
9
|
+
name: string;
|
|
10
|
+
payload?: unknown;
|
|
11
|
+
}) => void): Promise<{
|
|
9
12
|
config: CoreSessionConfig;
|
|
10
13
|
pluginSandboxShutdown?: () => Promise<void>;
|
|
11
14
|
}>;
|
|
@@ -44,9 +44,12 @@ export interface SessionPersistenceAdapter {
|
|
|
44
44
|
export declare class UnifiedSessionPersistenceService {
|
|
45
45
|
private readonly adapter;
|
|
46
46
|
private readonly teamTaskSessionsByAgent;
|
|
47
|
+
private readonly teamTaskLastHeartbeatBySession;
|
|
48
|
+
private readonly teamTaskLastProgressLineBySession;
|
|
47
49
|
protected readonly artifacts: SessionArtifacts;
|
|
48
50
|
private static readonly STALE_REASON;
|
|
49
51
|
private static readonly STALE_SOURCE;
|
|
52
|
+
private static readonly TEAM_HEARTBEAT_LOG_INTERVAL_MS;
|
|
50
53
|
constructor(adapter: SessionPersistenceAdapter);
|
|
51
54
|
ensureSessionsDir(): string;
|
|
52
55
|
private writeManifestFile;
|
|
@@ -81,6 +84,9 @@ export declare class UnifiedSessionPersistenceService {
|
|
|
81
84
|
applyStatusToRunningChildSessions(parentSessionId: string, status: Exclude<SessionStatus, "running">): Promise<void>;
|
|
82
85
|
onTeamTaskStart(rootSessionId: string, agentId: string, message: string): Promise<void>;
|
|
83
86
|
onTeamTaskEnd(rootSessionId: string, agentId: string, status: SessionStatus, summary?: string, messages?: LlmsProviders.Message[]): Promise<void>;
|
|
87
|
+
onTeamTaskProgress(rootSessionId: string, agentId: string, progress: string, options?: {
|
|
88
|
+
kind?: "heartbeat" | "progress" | "text";
|
|
89
|
+
}): Promise<void>;
|
|
84
90
|
handleSubAgentStart(rootSessionId: string, context: SubAgentStartContext): Promise<void>;
|
|
85
91
|
handleSubAgentEnd(rootSessionId: string, context: SubAgentEndContext): Promise<void>;
|
|
86
92
|
private isPidAlive;
|
|
@@ -21,9 +21,18 @@ export type ActiveSession = {
|
|
|
21
21
|
activeTeamRunIds: Set<string>;
|
|
22
22
|
pendingTeamRunUpdates: TeamRunUpdate[];
|
|
23
23
|
teamRunWaiters: Array<() => void>;
|
|
24
|
+
pendingPrompts: PendingPrompt[];
|
|
25
|
+
drainingPendingPrompts: boolean;
|
|
24
26
|
pluginSandboxShutdown?: () => Promise<void>;
|
|
25
27
|
turnUsageBaseline?: SessionAccumulatedUsage;
|
|
26
28
|
};
|
|
29
|
+
export type PendingPrompt = {
|
|
30
|
+
id: string;
|
|
31
|
+
prompt: string;
|
|
32
|
+
delivery: "queue" | "steer";
|
|
33
|
+
userImages?: string[];
|
|
34
|
+
userFiles?: string[];
|
|
35
|
+
};
|
|
27
36
|
export type TeamRunUpdate = {
|
|
28
37
|
runId: string;
|
|
29
38
|
agentId: string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type Tool } from "@clinebot/agents";
|
|
7
7
|
import { type ApplyPatchInput, type AskQuestionInput, type EditFileInput, type FetchWebContentInput, type ReadFilesInput, type RunCommandsInput, type SearchCodebaseInput, type SkillsInput } from "./schemas.js";
|
|
8
|
-
import type { ApplyPatchExecutor, AskQuestionExecutor, BashExecutor, CreateDefaultToolsOptions, DefaultToolsConfig, EditorExecutor, FileReadExecutor, SearchExecutor,
|
|
8
|
+
import type { ApplyPatchExecutor, AskQuestionExecutor, BashExecutor, CreateDefaultToolsOptions, DefaultToolsConfig, EditorExecutor, FileReadExecutor, SearchExecutor, SkillsExecutorWithMetadata, ToolOperationResult, WebFetchExecutor } from "./types.js";
|
|
9
9
|
/**
|
|
10
10
|
* Create the read_files tool
|
|
11
11
|
*
|
|
@@ -47,7 +47,7 @@ export declare function createEditorTool(executor: EditorExecutor, config?: Pick
|
|
|
47
47
|
*
|
|
48
48
|
* Invokes a configured skill by name and optional arguments.
|
|
49
49
|
*/
|
|
50
|
-
export declare function createSkillsTool(executor:
|
|
50
|
+
export declare function createSkillsTool(executor: SkillsExecutorWithMetadata, config?: Pick<DefaultToolsConfig, "skillsTimeoutMs">): Tool<SkillsInput, string>;
|
|
51
51
|
/**
|
|
52
52
|
* Create the ask_question tool
|
|
53
53
|
*
|
package/dist/tools/presets.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export declare const ToolPresets: {
|
|
|
79
79
|
readonly enableAskQuestion: true;
|
|
80
80
|
};
|
|
81
81
|
/**
|
|
82
|
-
* YOLO mode (
|
|
82
|
+
* YOLO mode (automation-focused tools + no approval required)
|
|
83
83
|
* Good for trusted local automation workflows.
|
|
84
84
|
*/
|
|
85
85
|
readonly yolo: {
|
|
@@ -90,7 +90,7 @@ export declare const ToolPresets: {
|
|
|
90
90
|
readonly enableApplyPatch: false;
|
|
91
91
|
readonly enableEditor: true;
|
|
92
92
|
readonly enableSkills: true;
|
|
93
|
-
readonly enableAskQuestion:
|
|
93
|
+
readonly enableAskQuestion: false;
|
|
94
94
|
};
|
|
95
95
|
};
|
|
96
96
|
/**
|
|
@@ -103,7 +103,7 @@ export type ToolPresetName = keyof typeof ToolPresets;
|
|
|
103
103
|
export type ToolPolicyPresetName = "default" | "yolo";
|
|
104
104
|
/**
|
|
105
105
|
* Build tool policies for a preset.
|
|
106
|
-
* `yolo` guarantees
|
|
106
|
+
* `yolo` guarantees tool policies are enabled and auto-approved.
|
|
107
107
|
*/
|
|
108
108
|
export declare function createToolPoliciesWithPreset(presetName: ToolPolicyPresetName): Record<string, ToolPolicy>;
|
|
109
109
|
/**
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
export declare const ReadFileLineRangeSchema: z.ZodObject<{
|
|
9
|
-
start_line: z.ZodOptional<z.ZodNumber
|
|
10
|
-
end_line: z.ZodOptional<z.ZodNumber
|
|
9
|
+
start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
10
|
+
end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
11
11
|
}, z.core.$strip>;
|
|
12
12
|
export declare const ReadFileRequestSchema: z.ZodObject<{
|
|
13
13
|
path: z.ZodString;
|
|
14
|
-
start_line: z.ZodOptional<z.ZodNumber
|
|
15
|
-
end_line: z.ZodOptional<z.ZodNumber
|
|
14
|
+
start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
15
|
+
end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
16
16
|
}, z.core.$strip>;
|
|
17
17
|
/**
|
|
18
18
|
* Schema for read_files tool input
|
|
@@ -20,8 +20,8 @@ export declare const ReadFileRequestSchema: z.ZodObject<{
|
|
|
20
20
|
export declare const ReadFilesInputSchema: z.ZodObject<{
|
|
21
21
|
files: z.ZodArray<z.ZodObject<{
|
|
22
22
|
path: z.ZodString;
|
|
23
|
-
start_line: z.ZodOptional<z.ZodNumber
|
|
24
|
-
end_line: z.ZodOptional<z.ZodNumber
|
|
23
|
+
start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
24
|
+
end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
25
25
|
}, z.core.$strip>>;
|
|
26
26
|
}, z.core.$strip>;
|
|
27
27
|
/**
|
|
@@ -30,22 +30,22 @@ export declare const ReadFilesInputSchema: z.ZodObject<{
|
|
|
30
30
|
export declare const ReadFilesInputUnionSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
31
31
|
files: z.ZodArray<z.ZodObject<{
|
|
32
32
|
path: z.ZodString;
|
|
33
|
-
start_line: z.ZodOptional<z.ZodNumber
|
|
34
|
-
end_line: z.ZodOptional<z.ZodNumber
|
|
33
|
+
start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
34
|
+
end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
35
35
|
}, z.core.$strip>>;
|
|
36
36
|
}, z.core.$strip>, z.ZodObject<{
|
|
37
37
|
path: z.ZodString;
|
|
38
|
-
start_line: z.ZodOptional<z.ZodNumber
|
|
39
|
-
end_line: z.ZodOptional<z.ZodNumber
|
|
38
|
+
start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
39
|
+
end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
40
40
|
}, z.core.$strip>, z.ZodArray<z.ZodObject<{
|
|
41
41
|
path: z.ZodString;
|
|
42
|
-
start_line: z.ZodOptional<z.ZodNumber
|
|
43
|
-
end_line: z.ZodOptional<z.ZodNumber
|
|
42
|
+
start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
43
|
+
end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
44
44
|
}, z.core.$strip>>, z.ZodArray<z.ZodString>, z.ZodString, z.ZodObject<{
|
|
45
45
|
files: z.ZodObject<{
|
|
46
46
|
path: z.ZodString;
|
|
47
|
-
start_line: z.ZodOptional<z.ZodNumber
|
|
48
|
-
end_line: z.ZodOptional<z.ZodNumber
|
|
47
|
+
start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
48
|
+
end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
49
49
|
}, z.core.$strip>;
|
|
50
50
|
}, z.core.$strip>, z.ZodObject<{
|
|
51
51
|
file_paths: z.ZodArray<z.ZodString>;
|
package/dist/types/config.d.ts
CHANGED
|
@@ -42,4 +42,9 @@ export interface CoreSessionConfig extends CoreModelConfig, CoreRuntimeFeatures,
|
|
|
42
42
|
onTeamEvent?: (event: TeamEvent) => void;
|
|
43
43
|
onConsecutiveMistakeLimitReached?: (context: ConsecutiveMistakeLimitContext) => Promise<ConsecutiveMistakeLimitDecision> | ConsecutiveMistakeLimitDecision;
|
|
44
44
|
toolRoutingRules?: ToolRoutingRule[];
|
|
45
|
+
/**
|
|
46
|
+
* Optional skill allowlist for the `skills` tool. When provided, only these
|
|
47
|
+
* skills are surfaced in tool metadata and invocable by name.
|
|
48
|
+
*/
|
|
49
|
+
skills?: string[];
|
|
45
50
|
}
|
package/dist/types/events.d.ts
CHANGED
|
@@ -27,6 +27,22 @@ export interface SessionTeamProgressEvent {
|
|
|
27
27
|
lifecycle: import("@clinebot/shared").TeamProgressLifecycleEvent;
|
|
28
28
|
summary: import("@clinebot/shared").TeamProgressSummary;
|
|
29
29
|
}
|
|
30
|
+
export interface SessionPendingPromptsEvent {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
prompts: Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
prompt: string;
|
|
35
|
+
delivery: "queue" | "steer";
|
|
36
|
+
attachmentCount: number;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export interface SessionPendingPromptSubmittedEvent {
|
|
40
|
+
sessionId: string;
|
|
41
|
+
id: string;
|
|
42
|
+
prompt: string;
|
|
43
|
+
delivery: "queue" | "steer";
|
|
44
|
+
attachmentCount: number;
|
|
45
|
+
}
|
|
30
46
|
export type CoreSessionEvent = {
|
|
31
47
|
type: "chunk";
|
|
32
48
|
payload: SessionChunkEvent;
|
|
@@ -39,6 +55,12 @@ export type CoreSessionEvent = {
|
|
|
39
55
|
} | {
|
|
40
56
|
type: "team_progress";
|
|
41
57
|
payload: SessionTeamProgressEvent;
|
|
58
|
+
} | {
|
|
59
|
+
type: "pending_prompts";
|
|
60
|
+
payload: SessionPendingPromptsEvent;
|
|
61
|
+
} | {
|
|
62
|
+
type: "pending_prompt_submitted";
|
|
63
|
+
payload: SessionPendingPromptSubmittedEvent;
|
|
42
64
|
} | {
|
|
43
65
|
type: "ended";
|
|
44
66
|
payload: SessionEndedEvent;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clinebot/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"main": "./dist/index.node.js",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@clinebot/agents": "0.0.
|
|
7
|
-
"@clinebot/llms": "0.0.
|
|
8
|
-
"@clinebot/shared": "0.0.
|
|
6
|
+
"@clinebot/agents": "0.0.12",
|
|
7
|
+
"@clinebot/llms": "0.0.12",
|
|
8
|
+
"@clinebot/shared": "0.0.12",
|
|
9
9
|
"@opentelemetry/api": "^1.9.0",
|
|
10
10
|
"@opentelemetry/api-logs": "^0.56.0",
|
|
11
11
|
"@opentelemetry/exporter-logs-otlp-http": "^0.56.0",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"@opentelemetry/sdk-metrics": "^1.30.1",
|
|
16
16
|
"@opentelemetry/semantic-conventions": "^1.37.0",
|
|
17
17
|
"better-sqlite3": "^11.10.0",
|
|
18
|
+
"jiti": "^1.21.7",
|
|
18
19
|
"nanoid": "^5.1.7",
|
|
19
20
|
"simple-git": "^3.32.3",
|
|
20
21
|
"yaml": "^2.8.2",
|
|
@@ -139,6 +139,7 @@ name: Reader
|
|
|
139
139
|
description: Reads files
|
|
140
140
|
modelId: claude-sonnet-4-6
|
|
141
141
|
tools: read_files
|
|
142
|
+
skills: commit, review
|
|
142
143
|
---
|
|
143
144
|
Be precise.`);
|
|
144
145
|
|
|
@@ -149,6 +150,7 @@ Be precise.`);
|
|
|
149
150
|
expect(partial.modelId).toBe("claude-sonnet-4-6");
|
|
150
151
|
expect(partial.systemPrompt).toBe("Be precise.");
|
|
151
152
|
expect(partial.tools).toEqual([readFiles]);
|
|
153
|
+
expect(partial.skills).toEqual(["commit", "review"]);
|
|
152
154
|
});
|
|
153
155
|
|
|
154
156
|
it("throws when tool overrides are configured without available tools", () => {
|
|
@@ -33,6 +33,11 @@ export interface BuildAgentConfigOverridesOptions {
|
|
|
33
33
|
availableTools?: ReadonlyArray<Tool>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export interface PartialAgentConfigOverrides
|
|
37
|
+
extends Partial<Pick<AgentConfig, "modelId" | "systemPrompt" | "tools">> {
|
|
38
|
+
skills?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
export function isAgentConfigYamlFile(fileName: string): boolean {
|
|
37
42
|
return /\.(yaml|yml)$/i.test(fileName);
|
|
38
43
|
}
|
|
@@ -159,10 +164,8 @@ export function resolveAgentTools(
|
|
|
159
164
|
export function toPartialAgentConfig(
|
|
160
165
|
config: AgentYamlConfig,
|
|
161
166
|
options?: BuildAgentConfigOverridesOptions,
|
|
162
|
-
):
|
|
163
|
-
const partial:
|
|
164
|
-
Pick<AgentConfig, "modelId" | "systemPrompt" | "tools">
|
|
165
|
-
> = {
|
|
167
|
+
): PartialAgentConfigOverrides {
|
|
168
|
+
const partial: PartialAgentConfigOverrides = {
|
|
166
169
|
systemPrompt: config.systemPrompt,
|
|
167
170
|
};
|
|
168
171
|
|
|
@@ -179,13 +182,17 @@ export function toPartialAgentConfig(
|
|
|
179
182
|
partial.tools = resolveAgentTools(config.tools, options.availableTools);
|
|
180
183
|
}
|
|
181
184
|
|
|
185
|
+
if (config.skills !== undefined) {
|
|
186
|
+
partial.skills = [...config.skills];
|
|
187
|
+
}
|
|
188
|
+
|
|
182
189
|
return partial;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
export function parsePartialAgentConfigFromYaml(
|
|
186
193
|
content: string,
|
|
187
194
|
options?: BuildAgentConfigOverridesOptions,
|
|
188
|
-
):
|
|
195
|
+
): PartialAgentConfigOverrides {
|
|
189
196
|
const parsed = parseAgentConfigFromYaml(content);
|
|
190
197
|
return toPartialAgentConfig(parsed, options);
|
|
191
198
|
}
|
package/src/agents/index.ts
CHANGED
|
@@ -26,6 +26,11 @@ describe("plugin-config-loader", () => {
|
|
|
26
26
|
await mkdir(nested, { recursive: true });
|
|
27
27
|
await writeFile(join(root, "a.mjs"), "export default {}", "utf8");
|
|
28
28
|
await writeFile(join(nested, "b.ts"), "export default {}", "utf8");
|
|
29
|
+
await writeFile(
|
|
30
|
+
join(root, ".a.mjs.cline-plugin.mjs"),
|
|
31
|
+
"export default {}",
|
|
32
|
+
"utf8",
|
|
33
|
+
);
|
|
29
34
|
await writeFile(join(root, "ignore.txt"), "noop", "utf8");
|
|
30
35
|
|
|
31
36
|
const discovered = discoverPluginModulePaths(root);
|
|
@@ -38,6 +43,8 @@ describe("plugin-config-loader", () => {
|
|
|
38
43
|
it("resolves plugin paths from explicit files/directories", async () => {
|
|
39
44
|
const root = await mkdtemp(join(tmpdir(), "core-plugin-config-loader-"));
|
|
40
45
|
try {
|
|
46
|
+
process.env.HOME = root;
|
|
47
|
+
setHomeDir(root);
|
|
41
48
|
const pluginsDir = join(root, "plugins");
|
|
42
49
|
await mkdir(pluginsDir, { recursive: true });
|
|
43
50
|
const filePath = join(root, "direct.mjs");
|
|
@@ -57,6 +64,41 @@ describe("plugin-config-loader", () => {
|
|
|
57
64
|
}
|
|
58
65
|
});
|
|
59
66
|
|
|
67
|
+
it("prefers package manifest plugin entries for configured directories", async () => {
|
|
68
|
+
const root = await mkdtemp(join(tmpdir(), "core-plugin-config-loader-"));
|
|
69
|
+
try {
|
|
70
|
+
const pluginDir = join(root, "plugin-package");
|
|
71
|
+
const srcDir = join(pluginDir, "src");
|
|
72
|
+
await mkdir(srcDir, { recursive: true });
|
|
73
|
+
const declaredEntry = join(srcDir, "index.ts");
|
|
74
|
+
const ignoredEntry = join(pluginDir, "ignored.mjs");
|
|
75
|
+
await writeFile(
|
|
76
|
+
join(pluginDir, "package.json"),
|
|
77
|
+
JSON.stringify({
|
|
78
|
+
name: "plugin-package",
|
|
79
|
+
private: true,
|
|
80
|
+
cline: {
|
|
81
|
+
plugins: ["./src/index.ts"],
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
"utf8",
|
|
85
|
+
);
|
|
86
|
+
await writeFile(declaredEntry, "export default {}", "utf8");
|
|
87
|
+
await writeFile(ignoredEntry, "export default {}", "utf8");
|
|
88
|
+
|
|
89
|
+
const resolved = resolveAgentPluginPaths({
|
|
90
|
+
pluginPaths: ["./plugin-package"],
|
|
91
|
+
cwd: root,
|
|
92
|
+
workspacePath: join(root, "workspace"),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(resolved).toContain(declaredEntry);
|
|
96
|
+
expect(resolved).not.toContain(ignoredEntry);
|
|
97
|
+
} finally {
|
|
98
|
+
await rm(root, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
60
102
|
it("includes shared search-path plugins", async () => {
|
|
61
103
|
const home = await mkdtemp(
|
|
62
104
|
join(tmpdir(), "core-plugin-config-loader-home-"),
|
|
@@ -69,20 +111,27 @@ describe("plugin-config-loader", () => {
|
|
|
69
111
|
setHomeDir(home);
|
|
70
112
|
const workspacePlugins = join(workspace, ".clinerules", "plugins");
|
|
71
113
|
const userPlugins = join(home, ".cline", "plugins");
|
|
114
|
+
const documentsPlugins = join(home, "Documents", "Plugins");
|
|
72
115
|
await mkdir(workspacePlugins, { recursive: true });
|
|
73
116
|
await mkdir(userPlugins, { recursive: true });
|
|
117
|
+
await mkdir(documentsPlugins, { recursive: true });
|
|
74
118
|
const workspacePlugin = join(workspacePlugins, "workspace.mjs");
|
|
75
119
|
const userPlugin = join(userPlugins, "user.mjs");
|
|
120
|
+
const documentsPlugin = join(documentsPlugins, "documents.mjs");
|
|
76
121
|
await writeFile(workspacePlugin, "export default {}", "utf8");
|
|
77
122
|
await writeFile(userPlugin, "export default {}", "utf8");
|
|
123
|
+
await writeFile(documentsPlugin, "export default {}", "utf8");
|
|
78
124
|
|
|
79
125
|
const searchPaths = resolvePluginConfigSearchPaths(workspace);
|
|
126
|
+
expect(searchPaths).toHaveLength(3);
|
|
80
127
|
expect(searchPaths).toContain(workspacePlugins);
|
|
81
128
|
expect(searchPaths).toContain(userPlugins);
|
|
129
|
+
expect(searchPaths).toContain(documentsPlugins);
|
|
82
130
|
|
|
83
131
|
const resolved = resolveAgentPluginPaths({ workspacePath: workspace });
|
|
84
132
|
expect(resolved).toContain(workspacePlugin);
|
|
85
133
|
expect(resolved).toContain(userPlugin);
|
|
134
|
+
expect(resolved).toContain(documentsPlugin);
|
|
86
135
|
} finally {
|
|
87
136
|
await rm(home, { recursive: true, force: true });
|
|
88
137
|
await rm(workspace, { recursive: true, force: true });
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
import { existsSync
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
3
2
|
import type { AgentConfig } from "@clinebot/agents";
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
discoverPluginModulePaths as discoverPluginModulePathsFromShared,
|
|
5
|
+
resolveConfiguredPluginModulePaths,
|
|
6
|
+
resolvePluginConfigSearchPaths as resolvePluginConfigSearchPathsFromShared,
|
|
7
|
+
} from "@clinebot/shared/storage";
|
|
5
8
|
import { loadAgentPluginsFromPaths } from "./plugin-loader";
|
|
6
9
|
import { loadSandboxedPlugins } from "./plugin-sandbox";
|
|
7
10
|
|
|
8
|
-
const PLUGIN_MODULE_EXTENSIONS = new Set([
|
|
9
|
-
".js",
|
|
10
|
-
".mjs",
|
|
11
|
-
".cjs",
|
|
12
|
-
".ts",
|
|
13
|
-
".mts",
|
|
14
|
-
".cts",
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
11
|
type AgentPlugin = NonNullable<AgentConfig["extensions"]>[number];
|
|
18
12
|
|
|
19
13
|
export function resolvePluginConfigSearchPaths(
|
|
@@ -22,67 +16,8 @@ export function resolvePluginConfigSearchPaths(
|
|
|
22
16
|
return resolvePluginConfigSearchPathsFromShared(workspacePath);
|
|
23
17
|
}
|
|
24
18
|
|
|
25
|
-
function hasPluginModuleExtension(path: string): boolean {
|
|
26
|
-
const dot = path.lastIndexOf(".");
|
|
27
|
-
if (dot === -1) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
return PLUGIN_MODULE_EXTENSIONS.has(path.slice(dot));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
19
|
export function discoverPluginModulePaths(directoryPath: string): string[] {
|
|
34
|
-
|
|
35
|
-
if (!existsSync(root)) {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
const discovered: string[] = [];
|
|
39
|
-
const stack = [root];
|
|
40
|
-
while (stack.length > 0) {
|
|
41
|
-
const current = stack.pop();
|
|
42
|
-
if (!current) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
46
|
-
const candidate = join(current, entry.name);
|
|
47
|
-
if (entry.isDirectory()) {
|
|
48
|
-
stack.push(candidate);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (entry.isFile() && hasPluginModuleExtension(candidate)) {
|
|
52
|
-
discovered.push(candidate);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return discovered.sort((a, b) => a.localeCompare(b));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function resolveConfiguredPluginPaths(
|
|
60
|
-
pluginPaths: ReadonlyArray<string>,
|
|
61
|
-
cwd: string,
|
|
62
|
-
): string[] {
|
|
63
|
-
const resolvedPaths: string[] = [];
|
|
64
|
-
for (const pluginPath of pluginPaths) {
|
|
65
|
-
const trimmed = pluginPath.trim();
|
|
66
|
-
if (!trimmed) {
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
const absolutePath = resolve(cwd, trimmed);
|
|
70
|
-
if (!existsSync(absolutePath)) {
|
|
71
|
-
throw new Error(`Plugin path does not exist: ${absolutePath}`);
|
|
72
|
-
}
|
|
73
|
-
const stats = statSync(absolutePath);
|
|
74
|
-
if (stats.isDirectory()) {
|
|
75
|
-
resolvedPaths.push(...discoverPluginModulePaths(absolutePath));
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (!hasPluginModuleExtension(absolutePath)) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Plugin file must use a supported extension (${[...PLUGIN_MODULE_EXTENSIONS].join(", ")}): ${absolutePath}`,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
resolvedPaths.push(absolutePath);
|
|
84
|
-
}
|
|
85
|
-
return resolvedPaths;
|
|
20
|
+
return discoverPluginModulePathsFromShared(directoryPath);
|
|
86
21
|
}
|
|
87
22
|
|
|
88
23
|
export interface ResolveAgentPluginPathsOptions {
|
|
@@ -100,7 +35,7 @@ export function resolveAgentPluginPaths(
|
|
|
100
35
|
)
|
|
101
36
|
.flatMap((directoryPath) => discoverPluginModulePaths(directoryPath))
|
|
102
37
|
.filter((path) => existsSync(path));
|
|
103
|
-
const configuredPaths =
|
|
38
|
+
const configuredPaths = resolveConfiguredPluginModulePaths(
|
|
104
39
|
options.pluginPaths ?? [],
|
|
105
40
|
cwd,
|
|
106
41
|
);
|
|
@@ -124,6 +59,7 @@ export interface ResolveAndLoadAgentPluginsOptions
|
|
|
124
59
|
importTimeoutMs?: number;
|
|
125
60
|
hookTimeoutMs?: number;
|
|
126
61
|
contributionTimeoutMs?: number;
|
|
62
|
+
onEvent?: (event: { name: string; payload?: unknown }) => void;
|
|
127
63
|
}
|
|
128
64
|
|
|
129
65
|
export async function resolveAndLoadAgentPlugins(
|
|
@@ -152,6 +88,7 @@ export async function resolveAndLoadAgentPlugins(
|
|
|
152
88
|
importTimeoutMs: options.importTimeoutMs,
|
|
153
89
|
hookTimeoutMs: options.hookTimeoutMs,
|
|
154
90
|
contributionTimeoutMs: options.contributionTimeoutMs,
|
|
91
|
+
onEvent: options.onEvent,
|
|
155
92
|
});
|
|
156
93
|
return {
|
|
157
94
|
extensions: sandboxed.extensions ?? [],
|