@clinebot/core 0.0.15 → 0.0.18
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/account/cline-account-service.d.ts +3 -1
- package/dist/account/index.d.ts +1 -1
- package/dist/account/types.d.ts +16 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.node.d.ts +1 -1
- package/dist/index.node.js +178 -178
- package/dist/storage/sqlite-session-store.d.ts +1 -0
- package/package.json +4 -4
- package/src/account/cline-account-service.ts +13 -0
- package/src/account/index.ts +2 -0
- package/src/account/types.ts +12 -0
- package/src/agents/agent-config-loader.test.ts +3 -3
- package/src/agents/hooks-config-loader.test.ts +20 -0
- package/src/agents/hooks-config-loader.ts +1 -0
- package/src/agents/user-instruction-config-loader.test.ts +6 -6
- package/src/index.node.ts +2 -0
- package/src/index.ts +1 -0
- package/src/runtime/hook-file-hooks.test.ts +34 -0
- package/src/runtime/hook-file-hooks.ts +34 -4
- package/src/session/unified-session-persistence-service.test.ts +7 -3
- package/src/storage/sqlite-session-store.ts +5 -0
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clinebot/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
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.17",
|
|
7
|
+
"@clinebot/llms": "0.0.17",
|
|
8
|
+
"@clinebot/shared": "0.0.17",
|
|
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",
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ClineAccountPaymentTransaction,
|
|
7
7
|
ClineAccountUsageTransaction,
|
|
8
8
|
ClineAccountUser,
|
|
9
|
+
ClineOrganization,
|
|
9
10
|
UserRemoteConfigResponse,
|
|
10
11
|
} from "./types";
|
|
11
12
|
|
|
@@ -102,6 +103,18 @@ export class ClineAccountService {
|
|
|
102
103
|
return me.organizations ?? [];
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
public async fetchOrganization(
|
|
107
|
+
organizationId: string,
|
|
108
|
+
): Promise<ClineOrganization> {
|
|
109
|
+
const orgId = organizationId.trim();
|
|
110
|
+
if (!orgId) {
|
|
111
|
+
throw new Error("organizationId is required");
|
|
112
|
+
}
|
|
113
|
+
return this.request<ClineOrganization>(
|
|
114
|
+
`/api/v1/organizations/${encodeURIComponent(orgId)}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
105
118
|
public async fetchOrganizationBalance(
|
|
106
119
|
organizationId: string,
|
|
107
120
|
): Promise<ClineAccountOrganizationBalance> {
|
package/src/account/index.ts
CHANGED
package/src/account/types.ts
CHANGED
|
@@ -56,6 +56,18 @@ export interface ClineAccountPaymentTransaction {
|
|
|
56
56
|
credits: number;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export interface ClineOrganization {
|
|
60
|
+
createdAt: string;
|
|
61
|
+
defaultRemoteConfig?: string;
|
|
62
|
+
deletedAt?: string;
|
|
63
|
+
externalOrganizationId?: string;
|
|
64
|
+
id: string;
|
|
65
|
+
memberCount?: number;
|
|
66
|
+
name: string;
|
|
67
|
+
remoteConfigEnabled: boolean;
|
|
68
|
+
updatedAt: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
export interface ClineAccountOrganizationBalance {
|
|
60
72
|
balance: number;
|
|
61
73
|
organizationId: string;
|
|
@@ -39,7 +39,7 @@ describe("agent config YAML loader", () => {
|
|
|
39
39
|
it("resolves default agents settings directory from CLINE_DATA_DIR", () => {
|
|
40
40
|
process.env.CLINE_DATA_DIR = "/tmp/cline-data";
|
|
41
41
|
expect(resolveAgentsConfigDirPath()).toBe(
|
|
42
|
-
|
|
42
|
+
join("/tmp/cline-data", "settings", AGENT_CONFIG_DIRECTORY_NAME),
|
|
43
43
|
);
|
|
44
44
|
});
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ describe("agent config YAML loader", () => {
|
|
|
47
47
|
process.env.CLINE_DATA_DIR = "/tmp/cline-data";
|
|
48
48
|
expect(resolveAgentConfigSearchPaths()).toEqual([
|
|
49
49
|
resolveDocumentsAgentConfigDirectoryPath(),
|
|
50
|
-
|
|
50
|
+
join("/tmp/cline-data", "settings", AGENT_CONFIG_DIRECTORY_NAME),
|
|
51
51
|
]);
|
|
52
52
|
});
|
|
53
53
|
|
|
@@ -57,7 +57,7 @@ describe("agent config YAML loader", () => {
|
|
|
57
57
|
expect(definition.type).toBe("agent");
|
|
58
58
|
expect(definition.directories).toEqual([
|
|
59
59
|
resolveDocumentsAgentConfigDirectoryPath(),
|
|
60
|
-
|
|
60
|
+
join("/tmp/cline-data", "settings", AGENT_CONFIG_DIRECTORY_NAME),
|
|
61
61
|
]);
|
|
62
62
|
expect(definition.includeFile?.("agent.yaml", "/tmp/agent.yaml")).toBe(
|
|
63
63
|
true,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
HookConfigFileName,
|
|
4
|
+
toHookConfigFileName,
|
|
5
|
+
} from "./hooks-config-loader";
|
|
6
|
+
|
|
7
|
+
describe("hooks config loader", () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
delete process.env.CLINE_DATA_DIR;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("recognizes PowerShell hook files", () => {
|
|
13
|
+
expect(toHookConfigFileName("PreToolUse.ps1")).toBe(
|
|
14
|
+
HookConfigFileName.PreToolUse,
|
|
15
|
+
);
|
|
16
|
+
expect(toHookConfigFileName("TaskError.ps1")).toBe(
|
|
17
|
+
HookConfigFileName.TaskError,
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -46,17 +46,17 @@ describe("user instruction config loader", () => {
|
|
|
46
46
|
const workspacePath = "/repo/demo";
|
|
47
47
|
expect(resolveSkillsConfigSearchPaths(workspacePath)).toEqual(
|
|
48
48
|
expect.arrayContaining([
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
49
|
+
join(workspacePath, ".clinerules", "skills"),
|
|
50
|
+
join(workspacePath, ".cline", "skills"),
|
|
51
|
+
join(workspacePath, ".claude", "skills"),
|
|
52
|
+
join(workspacePath, ".agents", "skills"),
|
|
53
53
|
]),
|
|
54
54
|
);
|
|
55
55
|
expect(resolveRulesConfigSearchPaths(workspacePath)).toEqual(
|
|
56
|
-
expect.arrayContaining(["
|
|
56
|
+
expect.arrayContaining([join(workspacePath, ".clinerules")]),
|
|
57
57
|
);
|
|
58
58
|
expect(resolveWorkflowsConfigSearchPaths(workspacePath)).toEqual(
|
|
59
|
-
expect.arrayContaining(["
|
|
59
|
+
expect.arrayContaining([join(workspacePath, ".clinerules", "workflows")]),
|
|
60
60
|
);
|
|
61
61
|
});
|
|
62
62
|
|
package/src/index.node.ts
CHANGED
|
@@ -114,10 +114,12 @@ export {
|
|
|
114
114
|
type ClineAccountServiceOptions,
|
|
115
115
|
type ClineAccountUsageTransaction,
|
|
116
116
|
type ClineAccountUser,
|
|
117
|
+
type ClineOrganization,
|
|
117
118
|
executeRpcClineAccountAction,
|
|
118
119
|
isRpcClineAccountActionRequest,
|
|
119
120
|
RpcClineAccountService,
|
|
120
121
|
type RpcProviderActionExecutor,
|
|
122
|
+
type UserRemoteConfigResponse,
|
|
121
123
|
} from "./account";
|
|
122
124
|
export { startLocalOAuthServer } from "./auth/server";
|
|
123
125
|
export type {
|
package/src/index.ts
CHANGED
|
@@ -172,6 +172,40 @@ describe("createHookConfigFileHooks", () => {
|
|
|
172
172
|
}
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
+
it.skipIf(process.platform !== "win32")(
|
|
176
|
+
"executes PowerShell hook files on Windows",
|
|
177
|
+
async () => {
|
|
178
|
+
const { workspace } = await createWorkspaceWithHook(
|
|
179
|
+
"PreToolUse.ps1",
|
|
180
|
+
'Write-Output \'HOOK_CONTROL\t{"cancel": false, "context": "powershell-ok"}\'\n',
|
|
181
|
+
);
|
|
182
|
+
try {
|
|
183
|
+
const hooks = createHookConfigFileHooks({
|
|
184
|
+
cwd: workspace,
|
|
185
|
+
workspacePath: workspace,
|
|
186
|
+
});
|
|
187
|
+
expect(hooks?.onToolCallStart).toBeTypeOf("function");
|
|
188
|
+
const control = await hooks?.onToolCallStart?.({
|
|
189
|
+
agentId: "agent_1",
|
|
190
|
+
conversationId: "conv_1",
|
|
191
|
+
parentAgentId: null,
|
|
192
|
+
iteration: 1,
|
|
193
|
+
call: {
|
|
194
|
+
id: "call_1",
|
|
195
|
+
name: "read_file",
|
|
196
|
+
input: { path: "README.md" },
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
expect(control).toMatchObject({
|
|
200
|
+
cancel: false,
|
|
201
|
+
context: "powershell-ok",
|
|
202
|
+
});
|
|
203
|
+
} finally {
|
|
204
|
+
await rm(workspace, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
175
209
|
it("maps TaskError hook files to agent_error stop events", async () => {
|
|
176
210
|
const outputPath = join(tmpdir(), `hooks-task-error-${Date.now()}.json`);
|
|
177
211
|
const { workspace } = await createWorkspaceWithHook(
|
|
@@ -345,10 +345,33 @@ function parseShebangCommand(path: string): string[] | undefined {
|
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
function normalizeHookInterpreter(tokens: string[]): string[] | undefined {
|
|
349
|
+
if (tokens.length === 0) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
const [rawCommand, ...rest] = tokens;
|
|
353
|
+
const normalizedCommand = rawCommand.replace(/\\/g, "/").toLowerCase();
|
|
354
|
+
const commandName = normalizedCommand.split("/").at(-1) ?? normalizedCommand;
|
|
355
|
+
|
|
356
|
+
if (commandName === "env") {
|
|
357
|
+
return normalizeHookInterpreter(rest);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (commandName === "bash" || commandName === "sh" || commandName === "zsh") {
|
|
361
|
+
return [commandName, ...rest];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (commandName === "python3" || commandName === "python") {
|
|
365
|
+
return [process.platform === "win32" ? "python" : commandName, ...rest];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return tokens;
|
|
369
|
+
}
|
|
370
|
+
|
|
348
371
|
function inferHookCommand(path: string): string[] {
|
|
349
372
|
const shebang = parseShebangCommand(path);
|
|
350
373
|
if (shebang && shebang.length > 0) {
|
|
351
|
-
return [...shebang, path];
|
|
374
|
+
return [...(normalizeHookInterpreter(shebang) ?? shebang), path];
|
|
352
375
|
}
|
|
353
376
|
const lowered = path.toLowerCase();
|
|
354
377
|
if (
|
|
@@ -356,7 +379,7 @@ function inferHookCommand(path: string): string[] {
|
|
|
356
379
|
lowered.endsWith(".bash") ||
|
|
357
380
|
lowered.endsWith(".zsh")
|
|
358
381
|
) {
|
|
359
|
-
return ["
|
|
382
|
+
return ["bash", path];
|
|
360
383
|
}
|
|
361
384
|
if (
|
|
362
385
|
lowered.endsWith(".js") ||
|
|
@@ -373,10 +396,17 @@ function inferHookCommand(path: string): string[] {
|
|
|
373
396
|
return ["bun", "run", path];
|
|
374
397
|
}
|
|
375
398
|
if (lowered.endsWith(".py")) {
|
|
376
|
-
return ["python3", path];
|
|
399
|
+
return [process.platform === "win32" ? "python" : "python3", path];
|
|
400
|
+
}
|
|
401
|
+
if (lowered.endsWith(".ps1")) {
|
|
402
|
+
return [
|
|
403
|
+
process.platform === "win32" ? "powershell" : "pwsh",
|
|
404
|
+
"-File",
|
|
405
|
+
path,
|
|
406
|
+
];
|
|
377
407
|
}
|
|
378
408
|
// Default to bash for legacy hook files with no extension/shebang.
|
|
379
|
-
return ["
|
|
409
|
+
return ["bash", path];
|
|
380
410
|
}
|
|
381
411
|
|
|
382
412
|
function createHookCommandMap(workspacePath: string): HookCommandMap {
|
|
@@ -8,8 +8,12 @@ import { CoreSessionService } from "./session-service";
|
|
|
8
8
|
|
|
9
9
|
describe("UnifiedSessionPersistenceService", () => {
|
|
10
10
|
const tempDirs: string[] = [];
|
|
11
|
+
const stores: Array<SqliteSessionStore> = [];
|
|
11
12
|
|
|
12
13
|
afterEach(() => {
|
|
14
|
+
for (const store of stores.splice(0)) {
|
|
15
|
+
store.close();
|
|
16
|
+
}
|
|
13
17
|
for (const dir of tempDirs.splice(0)) {
|
|
14
18
|
rmSync(dir, { recursive: true, force: true });
|
|
15
19
|
}
|
|
@@ -19,9 +23,9 @@ describe("UnifiedSessionPersistenceService", () => {
|
|
|
19
23
|
const sessionsDir = mkdtempSync(join(tmpdir(), "stale-session-reconcile-"));
|
|
20
24
|
tempDirs.push(sessionsDir);
|
|
21
25
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
);
|
|
26
|
+
const store = new SqliteSessionStore({ sessionsDir });
|
|
27
|
+
stores.push(store);
|
|
28
|
+
const service = new CoreSessionService(store);
|
|
25
29
|
const sessionId = "stale-root-session";
|
|
26
30
|
const artifacts = await service.createRootSessionWithArtifacts({
|
|
27
31
|
sessionId,
|
|
@@ -57,6 +57,11 @@ export class SqliteSessionStore implements SessionStore {
|
|
|
57
57
|
return db;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
close(): void {
|
|
61
|
+
this.db?.close?.();
|
|
62
|
+
this.db = undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
run(sql: string, params: unknown[] = []): { changes?: number } {
|
|
61
66
|
return this.getRawDb()
|
|
62
67
|
.prepare(sql)
|