@clinebot/core 0.0.11 → 0.0.13

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.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/agent-config-loader.d.ts +1 -1
  3. package/dist/agents/agent-config-parser.d.ts +5 -2
  4. package/dist/agents/index.d.ts +1 -1
  5. package/dist/agents/plugin-config-loader.d.ts +4 -0
  6. package/dist/agents/plugin-loader.d.ts +1 -0
  7. package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
  8. package/dist/agents/plugin-sandbox.d.ts +4 -0
  9. package/dist/index.node.d.ts +5 -0
  10. package/dist/index.node.js +685 -413
  11. package/dist/runtime/commands.d.ts +11 -0
  12. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
  13. package/dist/runtime/skills.d.ts +13 -0
  14. package/dist/session/default-session-manager.d.ts +5 -0
  15. package/dist/session/session-config-builder.d.ts +4 -1
  16. package/dist/session/session-manager.d.ts +1 -0
  17. package/dist/session/session-service.d.ts +22 -22
  18. package/dist/session/unified-session-persistence-service.d.ts +12 -6
  19. package/dist/session/utils/helpers.d.ts +2 -2
  20. package/dist/session/utils/types.d.ts +9 -0
  21. package/dist/tools/definitions.d.ts +2 -2
  22. package/dist/tools/presets.d.ts +3 -3
  23. package/dist/tools/schemas.d.ts +15 -14
  24. package/dist/types/config.d.ts +5 -0
  25. package/dist/types/events.d.ts +22 -0
  26. package/package.json +5 -4
  27. package/src/agents/agent-config-loader.test.ts +2 -0
  28. package/src/agents/agent-config-loader.ts +1 -0
  29. package/src/agents/agent-config-parser.ts +12 -5
  30. package/src/agents/index.ts +1 -0
  31. package/src/agents/plugin-config-loader.test.ts +49 -0
  32. package/src/agents/plugin-config-loader.ts +10 -73
  33. package/src/agents/plugin-loader.test.ts +127 -1
  34. package/src/agents/plugin-loader.ts +72 -5
  35. package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
  36. package/src/agents/plugin-sandbox.test.ts +198 -1
  37. package/src/agents/plugin-sandbox.ts +223 -353
  38. package/src/index.node.ts +14 -0
  39. package/src/runtime/commands.test.ts +98 -0
  40. package/src/runtime/commands.ts +83 -0
  41. package/src/runtime/hook-file-hooks.test.ts +1 -1
  42. package/src/runtime/hook-file-hooks.ts +16 -6
  43. package/src/runtime/index.ts +10 -0
  44. package/src/runtime/runtime-builder.test.ts +67 -0
  45. package/src/runtime/runtime-builder.ts +70 -16
  46. package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
  47. package/src/runtime/skills.ts +44 -0
  48. package/src/runtime/workflows.ts +20 -29
  49. package/src/session/default-session-manager.e2e.test.ts +52 -33
  50. package/src/session/default-session-manager.test.ts +453 -1
  51. package/src/session/default-session-manager.ts +210 -12
  52. package/src/session/rpc-session-service.ts +14 -96
  53. package/src/session/session-config-builder.ts +2 -0
  54. package/src/session/session-manager.ts +1 -0
  55. package/src/session/session-service.ts +127 -64
  56. package/src/session/session-team-coordination.ts +30 -0
  57. package/src/session/unified-session-persistence-service.test.ts +3 -3
  58. package/src/session/unified-session-persistence-service.ts +159 -141
  59. package/src/session/utils/helpers.ts +22 -41
  60. package/src/session/utils/types.ts +10 -0
  61. package/src/storage/sqlite-team-store.ts +16 -5
  62. package/src/tools/definitions.test.ts +137 -8
  63. package/src/tools/definitions.ts +115 -70
  64. package/src/tools/presets.test.ts +2 -3
  65. package/src/tools/presets.ts +3 -3
  66. package/src/tools/schemas.ts +28 -28
  67. package/src/types/config.ts +5 -0
  68. package/src/types/events.ts +23 -0
@@ -1,19 +1,13 @@
1
- import { existsSync, readdirSync, statSync } from "node:fs";
2
- import { join, resolve } from "node:path";
1
+ import { existsSync } from "node:fs";
3
2
  import type { AgentConfig } from "@clinebot/agents";
4
- import { resolvePluginConfigSearchPaths as resolvePluginConfigSearchPathsFromShared } from "@clinebot/shared/storage";
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
- const root = resolve(directoryPath);
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 = resolveConfiguredPluginPaths(
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 ?? [],
@@ -1,4 +1,4 @@
1
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { describe, expect, it } from "vitest";
@@ -82,6 +82,132 @@ describe("plugin-loader", () => {
82
82
  }
83
83
  });
84
84
 
85
+ it("loads TypeScript plugins from file paths", async () => {
86
+ const dir = await mkdtemp(join(tmpdir(), "core-plugin-loader-"));
87
+ try {
88
+ const pluginPath = join(dir, "plugin-ts.ts");
89
+ await writeFile(
90
+ pluginPath,
91
+ [
92
+ "const name: string = 'plugin-ts';",
93
+ "export default {",
94
+ " name,",
95
+ " manifest: { capabilities: ['tools'] },",
96
+ "};",
97
+ ].join("\n"),
98
+ "utf8",
99
+ );
100
+
101
+ const plugin = await loadAgentPluginFromPath(pluginPath);
102
+ expect(plugin.name).toBe("plugin-ts");
103
+ } finally {
104
+ await rm(dir, { recursive: true, force: true });
105
+ }
106
+ });
107
+
108
+ it("resolves plugin-local dependencies from the plugin path", async () => {
109
+ const dir = await mkdtemp(join(tmpdir(), "core-plugin-loader-"));
110
+ try {
111
+ const depDir = join(dir, "node_modules", "plugin-local-dep");
112
+ await mkdir(depDir, { recursive: true });
113
+ await writeFile(
114
+ join(depDir, "package.json"),
115
+ JSON.stringify({
116
+ name: "plugin-local-dep",
117
+ type: "module",
118
+ exports: "./index.js",
119
+ }),
120
+ "utf8",
121
+ );
122
+ await writeFile(
123
+ join(depDir, "index.js"),
124
+ "export const depName = 'plugin-local-dep';\n",
125
+ "utf8",
126
+ );
127
+ const pluginPath = join(dir, "plugin-with-dep.ts");
128
+ await writeFile(
129
+ pluginPath,
130
+ [
131
+ "import { depName } from 'plugin-local-dep';",
132
+ "export default {",
133
+ " name: depName,",
134
+ " manifest: { capabilities: ['tools'] },",
135
+ "};",
136
+ ].join("\n"),
137
+ "utf8",
138
+ );
139
+
140
+ const plugin = await loadAgentPluginFromPath(pluginPath, { cwd: dir });
141
+ expect(plugin.name).toBe("plugin-local-dep");
142
+ } finally {
143
+ await rm(dir, { recursive: true, force: true });
144
+ }
145
+ });
146
+
147
+ it("prefers plugin-installed SDK packages over workspace aliases", async () => {
148
+ const dir = await mkdtemp(join(tmpdir(), "core-plugin-loader-"));
149
+ try {
150
+ const depDir = join(dir, "node_modules", "@clinebot", "shared");
151
+ await mkdir(depDir, { recursive: true });
152
+ await writeFile(
153
+ join(depDir, "package.json"),
154
+ JSON.stringify({
155
+ name: "@clinebot/shared",
156
+ type: "module",
157
+ exports: "./index.js",
158
+ }),
159
+ "utf8",
160
+ );
161
+ await writeFile(
162
+ join(depDir, "index.js"),
163
+ "export const sdkMarker = 'plugin-installed-sdk';\n",
164
+ "utf8",
165
+ );
166
+ const pluginPath = join(dir, "plugin-with-sdk-dep.ts");
167
+ await writeFile(
168
+ pluginPath,
169
+ [
170
+ "import { sdkMarker } from '@clinebot/shared';",
171
+ "export default {",
172
+ " name: sdkMarker,",
173
+ " manifest: { capabilities: ['tools'] },",
174
+ "};",
175
+ ].join("\n"),
176
+ "utf8",
177
+ );
178
+
179
+ const plugin = await loadAgentPluginFromPath(pluginPath, { cwd: dir });
180
+ expect(plugin.name).toBe("plugin-installed-sdk");
181
+ } finally {
182
+ await rm(dir, { recursive: true, force: true });
183
+ }
184
+ });
185
+
186
+ it("requires copied plugins to provide their own non-SDK dependencies", async () => {
187
+ const dir = await mkdtemp(join(tmpdir(), "core-plugin-loader-copy-"));
188
+ try {
189
+ const pluginPath = join(dir, "portable-subagents.ts");
190
+ await writeFile(
191
+ pluginPath,
192
+ [
193
+ "import { resolveClineDataDir } from '@clinebot/shared/storage';",
194
+ "import YAML from 'yaml';",
195
+ "export default {",
196
+ " name: typeof resolveClineDataDir === 'function' ? YAML.stringify({ ok: true }) : 'invalid',",
197
+ " manifest: { capabilities: ['tools'] },",
198
+ "};",
199
+ ].join("\n"),
200
+ "utf8",
201
+ );
202
+
203
+ await expect(
204
+ loadAgentPluginFromPath(pluginPath, { cwd: dir, useCache: true }),
205
+ ).rejects.toThrow(/Cannot find (package|module) 'yaml'/i);
206
+ } finally {
207
+ await rm(dir, { recursive: true, force: true });
208
+ }
209
+ });
210
+
85
211
  it("rejects invalid plugin export missing manifest", async () => {
86
212
  const dir = await mkdtemp(join(tmpdir(), "core-plugin-loader-"));
87
213
  try {
@@ -1,6 +1,9 @@
1
- import { resolve } from "node:path";
2
- import { pathToFileURL } from "node:url";
1
+ import { existsSync } from "node:fs";
2
+ import { builtinModules, createRequire } from "node:module";
3
+ import { dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
3
5
  import type { AgentConfig } from "@clinebot/agents";
6
+ import createJiti from "jiti";
4
7
 
5
8
  type AgentPlugin = NonNullable<AgentConfig["extensions"]>[number];
6
9
  type PluginLike = {
@@ -14,8 +17,15 @@ type PluginLike = {
14
17
  export interface LoadAgentPluginFromPathOptions {
15
18
  exportName?: string;
16
19
  cwd?: string;
20
+ useCache?: boolean;
17
21
  }
18
22
 
23
+ const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
24
+ const WORKSPACE_ALIASES = collectWorkspaceAliases(MODULE_DIR);
25
+ const BUILTIN_MODULES = new Set(
26
+ builtinModules.flatMap((id) => [id, id.replace(/^node:/, "")]),
27
+ );
28
+
19
29
  function isObject(value: unknown): value is Record<string, unknown> {
20
30
  return typeof value === "object" && value !== null;
21
31
  }
@@ -77,14 +87,71 @@ function validatePluginExport(
77
87
  validatePluginManifest(plugin as PluginLike, absolutePath);
78
88
  }
79
89
 
90
+ function collectWorkspaceAliases(startDir: string): Record<string, string> {
91
+ const root = resolve(startDir, "..", "..", "..", "..");
92
+ const aliases: Record<string, string> = {};
93
+ const candidates: Record<string, string> = {
94
+ "@clinebot/agents": resolve(root, "packages/agents/src/index.ts"),
95
+ "@clinebot/core": resolve(root, "packages/core/src/index.node.ts"),
96
+ "@clinebot/llms": resolve(root, "packages/llms/src/index.ts"),
97
+ "@clinebot/rpc": resolve(root, "packages/rpc/src/index.ts"),
98
+ "@clinebot/scheduler": resolve(root, "packages/scheduler/src/index.ts"),
99
+ "@clinebot/shared": resolve(root, "packages/shared/src/index.ts"),
100
+ "@clinebot/shared/storage": resolve(
101
+ root,
102
+ "packages/shared/src/storage/index.ts",
103
+ ),
104
+ "@clinebot/shared/db": resolve(root, "packages/shared/src/db/index.ts"),
105
+ };
106
+ for (const [key, value] of Object.entries(candidates)) {
107
+ if (existsSync(value)) {
108
+ aliases[key] = value;
109
+ }
110
+ }
111
+ return aliases;
112
+ }
113
+
114
+ function collectPluginImportAliases(
115
+ pluginPath: string,
116
+ ): Record<string, string> {
117
+ const pluginRequire = createRequire(pluginPath);
118
+ const aliases: Record<string, string> = {};
119
+ for (const [specifier, sourcePath] of Object.entries(WORKSPACE_ALIASES)) {
120
+ try {
121
+ pluginRequire.resolve(specifier);
122
+ continue;
123
+ } catch {
124
+ // Use the workspace source only when the plugin package does not provide
125
+ // its own installed SDK dependency.
126
+ }
127
+ aliases[specifier] = sourcePath;
128
+ }
129
+ return aliases;
130
+ }
131
+
132
+ async function importPluginModule(
133
+ absolutePath: string,
134
+ options: LoadAgentPluginFromPathOptions = {},
135
+ ): Promise<Record<string, unknown>> {
136
+ const aliases = collectPluginImportAliases(absolutePath);
137
+ const jiti = createJiti(absolutePath, {
138
+ alias: aliases,
139
+ cache: options.useCache,
140
+ requireCache: options.useCache,
141
+ esmResolve: true,
142
+ interopDefault: false,
143
+ nativeModules: [...BUILTIN_MODULES],
144
+ transformModules: Object.keys(aliases),
145
+ });
146
+ return (await jiti.import(absolutePath, {})) as Record<string, unknown>;
147
+ }
148
+
80
149
  export async function loadAgentPluginFromPath(
81
150
  pluginPath: string,
82
151
  options: LoadAgentPluginFromPathOptions = {},
83
152
  ): Promise<AgentPlugin> {
84
153
  const absolutePath = resolve(options.cwd ?? process.cwd(), pluginPath);
85
- const moduleExports = (await import(
86
- pathToFileURL(absolutePath).href
87
- )) as Record<string, unknown>;
154
+ const moduleExports = await importPluginModule(absolutePath, options);
88
155
  const exportName = options.exportName ?? "plugin";
89
156
  const plugin = (moduleExports.default ??
90
157
  moduleExports[exportName]) as unknown;