@clinebot/shared 0.0.20 → 0.0.22

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.
@@ -0,0 +1,2 @@
1
+ export declare function getDefaultShell(platform: string): string;
2
+ export declare function getShellArgs(shell: string, command: string): string[];
@@ -0,0 +1 @@
1
+ export declare function sanitizeFileName(value: string): string;
@@ -0,0 +1 @@
1
+ export declare function createSessionId(prefix?: string, suffix?: string): string;
@@ -1 +1 @@
1
- var s=Object.defineProperty;var n=(C)=>C;function w(C,D){this[C]=n.bind(null,D)}var EC=(C,D)=>{for(var N in D)s(C,N,{get:D[N],enumerable:!0,configurable:!0,set:w.bind(D,N)})};import{existsSync as g,mkdirSync as L,readdirSync as k,readFileSync as i,statSync as G}from"node:fs";import{dirname as U,join as S,resolve as H}from"node:path";var Y="agents",j="hooks",A="skills",q="rules",z="workflows",u="plugins",B="cline_mcp_settings.json",I=process?.env?.HOME||"~",Q=!1;function c(C){let D=C.trim();if(!D)return;I=D,Q=!0}function P(C){if(Q)return;let D=C.trim();if(!D)return;I=D}var T,V=!1;function d(C){let D=C.trim();if(!D)return;T=D,V=!0}function r(C){if(V)return;let D=C.trim();if(!D)return;T=D}function l(){if(T)return T;let C=process.env.CLINE_DIR?.trim();if(C)return C;return S(I,".cline")}function M(){return S(I,"Documents","Cline")}function Z(){return S(M(),"Agents")}function $(){return S(M(),"Hooks")}function f(){return S(M(),"Rules")}function X(){return S(M(),"Workflows")}function J(){return S(I,"Documents","Plugins")}function R(){let C=process.env.CLINE_DATA_DIR?.trim();if(C)return C;return S(l(),"data")}function h(){let C=process.env.CLINE_SESSION_DATA_DIR?.trim();if(C)return C;return S(R(),"sessions")}function p(){let C=process.env.CLINE_TEAM_DATA_DIR?.trim();if(C)return C;return S(R(),"teams")}function e(){let C=process.env.CLINE_PROVIDER_SETTINGS_PATH?.trim();if(C)return C;return S(R(),"settings","providers.json")}function t(){let C=process.env.CLINE_MCP_SETTINGS_PATH?.trim();if(C)return C;return S(R(),"settings",B)}function E(C){let D=new Set,N=[];for(let _ of C){if(!_||D.has(_))continue;D.add(_),N.push(_)}return N}function a(C){if(!C)return[];return[S(C,".clinerules",A),S(C,".cline",A),S(C,".claude",A),S(C,".agents",A)]}function x(){return S(R(),"settings",Y)}function CC(){return[Z(),x()]}function DC(C){return E([C?S(C,".clinerules",j):"",$()])}function SC(C){return E([...a(C),S(R(),"settings",A),S(l(),A),S(I,".agents",A)])}function vC(C){return E([C?S(C,".clinerules"):"",S(R(),"settings",q),f()])}function NC(C){return E([C?S(C,".clinerules","workflows"):"",S(R(),"settings",z),X()])}function OC(C){return E([C?S(C,".clinerules",u):"",S(l(),u),J()])}var b=new Set([".js",".mjs",".cjs",".ts",".mts",".cts"]),_C="package.json",RC=["index.ts","index.mts","index.cts","index.js","index.mjs","index.cjs"];function K(C){let D=C.lastIndexOf(".");if(D===-1)return!1;return b.has(C.slice(D))}function gC(C){try{let D=JSON.parse(i(C,"utf8"));if(!D.cline||typeof D.cline!=="object")return null;return D.cline}catch{return null}}function AC(C){let D=C?.plugins??C?.extensions;if(!Array.isArray(D))return[];return D.filter((N)=>typeof N==="string")}function o(C){let D=H(C);if(!g(D)||!G(D).isDirectory())return null;let N=S(D,_C);if(g(N)){let _=gC(N),O=AC(_).map((v)=>H(D,v)).filter((v)=>g(v)&&G(v).isFile()&&K(v));if(O.length>0)return O}for(let _ of RC){let O=S(D,_);if(g(O)&&G(O).isFile())return[O]}return null}function m(C){let D=H(C);if(!g(D))return[];let N=[],_=[D];while(_.length>0){let O=_.pop();if(!O)continue;for(let v of k(O,{withFileTypes:!0})){let F=S(O,v.name);if(v.isDirectory()){_.push(F);continue}if(v.name.startsWith("."))continue;if(v.isFile()&&K(F))N.push(F)}}return N.sort((O,v)=>O.localeCompare(v))}function IC(C,D){let N=[];for(let _ of C){let O=_.trim();if(!O)continue;let v=H(D,O);if(!g(v))throw Error(`Plugin path does not exist: ${v}`);if(G(v).isDirectory()){let W=o(v);if(W){N.push(...W);continue}N.push(...m(v));continue}if(!K(v))throw Error(`Plugin file must use a supported extension (${[...b].join(", ")}): ${v}`);N.push(v)}return N}function y(C){let D=U(C);if(!g(D))L(D,{recursive:!0})}function MC(C){if(C?.trim())return y(C),U(C);let D=S(R(),"hooks");if(!g(D))L(D,{recursive:!0});return D}export{P as setHomeDirIfUnset,c as setHomeDir,r as setClineDirIfUnset,d as setClineDir,NC as resolveWorkflowsConfigSearchPaths,p as resolveTeamDataDir,SC as resolveSkillsConfigSearchPaths,h as resolveSessionDataDir,vC as resolveRulesConfigSearchPaths,e as resolveProviderSettingsPath,o as resolvePluginModuleEntries,OC as resolvePluginConfigSearchPaths,t as resolveMcpSettingsPath,DC as resolveHooksConfigSearchPaths,X as resolveDocumentsWorkflowsDirectoryPath,f as resolveDocumentsRulesDirectoryPath,J as resolveDocumentsPluginsDirectoryPath,$ as resolveDocumentsHooksDirectoryPath,M as resolveDocumentsClineDirectoryPath,Z as resolveDocumentsAgentConfigDirectoryPath,IC as resolveConfiguredPluginModulePaths,l as resolveClineDir,R as resolveClineDataDir,x as resolveAgentsConfigDirPath,CC as resolveAgentConfigSearchPaths,K as isPluginModulePath,y as ensureParentDir,MC as ensureHookLogDir,m as discoverPluginModulePaths,z as WORKFLOWS_CONFIG_DIRECTORY_NAME,A as SKILLS_CONFIG_DIRECTORY_NAME,q as RULES_CONFIG_DIRECTORY_NAME,j as HOOKS_CONFIG_DIRECTORY_NAME,B as CLINE_MCP_SETTINGS_FILE_NAME,Y as AGENT_CONFIG_DIRECTORY_NAME};
1
+ var o=Object.defineProperty;var w=(C)=>C;function n(C,S){this[C]=w.bind(null,S)}var TC=(C,S)=>{for(var N in S)o(C,N,{get:S[N],enumerable:!0,configurable:!0,set:n.bind(S,N)})};import{existsSync as A,mkdirSync as v,readdirSync as k,readFileSync as c,statSync as T}from"node:fs";import{homedir as i}from"node:os";import{dirname as j,join as D,resolve as K}from"node:path";var q="agents",z="hooks",I="skills",B="rules",Q="workflows",l="plugins",U="cline_mcp_settings.json";function d(){let C=process?.env?.HOME?.trim();if(C&&C!=="~")return C;let S=process?.env?.USERPROFILE?.trim();if(S)return S;let N=process?.env?.HOMEDRIVE?.trim(),R=process?.env?.HOMEPATH?.trim();if(N&&R)return`${N}${R}`;let _=i().trim();if(_&&_!=="~")return _;return"~"}var M=d(),V=!1;function P(C){let S=C.trim();if(!S)return;M=S,V=!0}function p(C){if(V)return;let S=C.trim();if(!S)return;M=S}var W,Z=!1;function h(C){let S=C.trim();if(!S)return;W=S,Z=!0}function r(C){if(Z)return;let S=C.trim();if(!S)return;W=S}function u(){if(W)return W;let C=process.env.CLINE_DIR?.trim();if(C)return C;return D(M,".cline")}function E(){return D(M,"Documents","Cline")}function $(){return D(E(),"Agents")}function X(){return D(E(),"Hooks")}function f(){return D(E(),"Rules")}function H(){return D(E(),"Workflows")}function J(){return D(M,"Documents","Plugins")}function g(){let C=process.env.CLINE_DATA_DIR?.trim();if(C)return C;return D(u(),"data")}function e(){let C=process.env.CLINE_SESSION_DATA_DIR?.trim();if(C)return C;return D(g(),"sessions")}function t(){let C=process.env.CLINE_TEAM_DATA_DIR?.trim();if(C)return C;return D(g(),"teams")}function a(){let C=process.env.CLINE_PROVIDER_SETTINGS_PATH?.trim();if(C)return C;return D(g(),"settings","providers.json")}function CC(){let C=process.env.CLINE_MCP_SETTINGS_PATH?.trim();if(C)return C;return D(g(),"settings",U)}function F(C){let S=new Set,N=[];for(let R of C){if(!R||S.has(R))continue;S.add(R),N.push(R)}return N}function SC(C){if(!C)return[];return[D(C,".clinerules",I),D(C,".cline",I),D(C,".claude",I),D(C,".agents",I)]}function x(){return D(g(),"settings",q)}function DC(){return[$(),x()]}function NC(C){return F([C?D(C,".clinerules",z):"",X()])}function OC(C){return F([...SC(C),D(g(),"settings",I),D(u(),I),D(M,".agents",I)])}function _C(C){return F([C?D(C,".clinerules"):"",D(g(),"settings",B),f()])}function RC(C){return F([C?D(C,".clinerules","workflows"):"",D(g(),"settings",Q),H()])}function gC(C){return F([C?D(C,".clinerules",l):"",D(u(),l),J()])}var b=new Set([".js",".mjs",".cjs",".ts",".mts",".cts"]),AC="package.json",IC=["index.ts","index.mts","index.cts","index.js","index.mjs","index.cjs"];function L(C){let S=C.lastIndexOf(".");if(S===-1)return!1;return b.has(C.slice(S))}function MC(C){try{let S=JSON.parse(c(C,"utf8"));if(!S.cline||typeof S.cline!=="object")return null;return S.cline}catch{return null}}function EC(C){let S=C?.plugins??C?.extensions;if(!Array.isArray(S))return[];return S.filter((N)=>typeof N==="string")}function y(C){let S=K(C);if(!A(S)||!T(S).isDirectory())return null;let N=D(S,AC);if(A(N)){let R=MC(N),_=EC(R).map((O)=>K(S,O)).filter((O)=>A(O)&&T(O).isFile()&&L(O));if(_.length>0)return _}for(let R of IC){let _=D(S,R);if(A(_)&&T(_).isFile())return[_]}return null}function m(C){let S=K(C);if(!A(S))return[];let N=[],R=[S];while(R.length>0){let _=R.pop();if(!_)continue;for(let O of k(_,{withFileTypes:!0})){let G=D(_,O.name);if(O.isDirectory()){R.push(G);continue}if(O.name.startsWith("."))continue;if(O.isFile()&&L(G))N.push(G)}}return N.sort((_,O)=>_.localeCompare(O))}function FC(C,S){let N=[];for(let R of C){let _=R.trim();if(!_)continue;let O=K(S,_);if(!A(O))throw Error(`Plugin path does not exist: ${O}`);if(T(O).isDirectory()){let Y=y(O);if(Y){N.push(...Y);continue}N.push(...m(O));continue}if(!L(O))throw Error(`Plugin file must use a supported extension (${[...b].join(", ")}): ${O}`);N.push(O)}return N}function s(C){let S=j(C);if(!A(S))v(S,{recursive:!0})}function GC(C){if(C?.trim())return s(C),j(C);let S=D(g(),"hooks");if(!A(S))v(S,{recursive:!0});return S}export{p as setHomeDirIfUnset,P as setHomeDir,r as setClineDirIfUnset,h as setClineDir,RC as resolveWorkflowsConfigSearchPaths,t as resolveTeamDataDir,OC as resolveSkillsConfigSearchPaths,e as resolveSessionDataDir,_C as resolveRulesConfigSearchPaths,a as resolveProviderSettingsPath,y as resolvePluginModuleEntries,gC as resolvePluginConfigSearchPaths,CC as resolveMcpSettingsPath,NC as resolveHooksConfigSearchPaths,H as resolveDocumentsWorkflowsDirectoryPath,f as resolveDocumentsRulesDirectoryPath,J as resolveDocumentsPluginsDirectoryPath,X as resolveDocumentsHooksDirectoryPath,E as resolveDocumentsClineDirectoryPath,$ as resolveDocumentsAgentConfigDirectoryPath,FC as resolveConfiguredPluginModulePaths,u as resolveClineDir,g as resolveClineDataDir,x as resolveAgentsConfigDirPath,DC as resolveAgentConfigSearchPaths,L as isPluginModulePath,s as ensureParentDir,GC as ensureHookLogDir,m as discoverPluginModulePaths,Q as WORKFLOWS_CONFIG_DIRECTORY_NAME,I as SKILLS_CONFIG_DIRECTORY_NAME,B as RULES_CONFIG_DIRECTORY_NAME,z as HOOKS_CONFIG_DIRECTORY_NAME,U as CLINE_MCP_SETTINGS_FILE_NAME,q as AGENT_CONFIG_DIRECTORY_NAME};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clinebot/shared",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "Shared utilities, types, and schemas for Cline packages",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,8 +29,7 @@
29
29
  }
30
30
  },
31
31
  "files": [
32
- "dist",
33
- "src"
32
+ "dist"
34
33
  ],
35
34
  "scripts": {
36
35
  "build": "BUILD_MODE=package bun bun.mts",
@@ -1,41 +0,0 @@
1
- /**
2
- * Canonical list of OAuth provider IDs managed by the platform.
3
- * Derive sets, types, and guards from this single source of truth.
4
- */
5
- export const OAUTH_PROVIDER_IDS = ["cline", "oca", "openai-codex"] as const;
6
-
7
- export type OAuthProviderId = (typeof OAUTH_PROVIDER_IDS)[number];
8
-
9
- /**
10
- * Check whether a provider ID is a managed OAuth provider.
11
- */
12
- export function isOAuthProviderId(
13
- providerId: string,
14
- ): providerId is OAuthProviderId {
15
- return (OAUTH_PROVIDER_IDS as readonly string[]).includes(providerId);
16
- }
17
-
18
- /**
19
- * Error‑message sub-strings that indicate an auth / credential failure.
20
- * Used to decide whether a failed API call should trigger an OAuth refresh.
21
- */
22
- export const AUTH_ERROR_PATTERNS = [
23
- "401",
24
- "403",
25
- "unauthorized",
26
- "forbidden",
27
- "invalid token",
28
- "expired token",
29
- "authentication",
30
- ] as const;
31
-
32
- /**
33
- * Returns `true` when `error` looks like an authentication failure
34
- * *and* the provider is a managed OAuth provider.
35
- */
36
- export function isLikelyAuthError(error: unknown, providerId: string): boolean {
37
- if (!isOAuthProviderId(providerId)) return false;
38
- const message =
39
- error instanceof Error ? error.message.toLowerCase() : String(error);
40
- return AUTH_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
41
- }
@@ -1,152 +0,0 @@
1
- export type ConnectWhatsAppOptions = {
2
- userName: string;
3
- phoneNumberId?: string;
4
- accessToken?: string;
5
- appSecret?: string;
6
- verifyToken?: string;
7
- apiVersion?: string;
8
- cwd: string;
9
- model?: string;
10
- provider?: string;
11
- apiKey?: string;
12
- systemPrompt?: string;
13
- mode: "act" | "plan";
14
- interactive: boolean;
15
- maxIterations?: number;
16
- enableTools: boolean;
17
- rpcAddress: string;
18
- hookCommand?: string;
19
- port: number;
20
- host: string;
21
- baseUrl: string;
22
- };
23
-
24
- export type WhatsAppConnectorState = {
25
- instanceKey: string;
26
- userName: string;
27
- phoneNumberId?: string;
28
- pid: number;
29
- rpcAddress: string;
30
- port: number;
31
- baseUrl: string;
32
- startedAt: string;
33
- };
34
-
35
- export type ConnectTelegramOptions = {
36
- botToken: string;
37
- botUsername: string;
38
- cwd: string;
39
- model?: string;
40
- provider?: string;
41
- apiKey?: string;
42
- systemPrompt?: string;
43
- mode: "act" | "plan";
44
- interactive: boolean;
45
- maxIterations?: number;
46
- enableTools: boolean;
47
- rpcAddress: string;
48
- hookCommand?: string;
49
- };
50
-
51
- export type TelegramConnectorState = {
52
- botUsername: string;
53
- pid: number;
54
- rpcAddress: string;
55
- startedAt: string;
56
- };
57
-
58
- export type ConnectSlackOptions = {
59
- userName: string;
60
- botToken?: string;
61
- signingSecret?: string;
62
- clientId?: string;
63
- clientSecret?: string;
64
- encryptionKey?: string;
65
- installationKeyPrefix?: string;
66
- cwd: string;
67
- model?: string;
68
- provider?: string;
69
- apiKey?: string;
70
- systemPrompt?: string;
71
- mode: "act" | "plan";
72
- interactive: boolean;
73
- maxIterations?: number;
74
- enableTools: boolean;
75
- rpcAddress: string;
76
- hookCommand?: string;
77
- port: number;
78
- host: string;
79
- baseUrl: string;
80
- };
81
-
82
- export type SlackConnectorState = {
83
- userName: string;
84
- pid: number;
85
- rpcAddress: string;
86
- port: number;
87
- baseUrl: string;
88
- startedAt: string;
89
- };
90
-
91
- export type ConnectGoogleChatOptions = {
92
- userName: string;
93
- cwd: string;
94
- model?: string;
95
- provider?: string;
96
- apiKey?: string;
97
- systemPrompt?: string;
98
- mode: "act" | "plan";
99
- interactive: boolean;
100
- maxIterations?: number;
101
- enableTools: boolean;
102
- rpcAddress: string;
103
- hookCommand?: string;
104
- port: number;
105
- host: string;
106
- baseUrl: string;
107
- pubsubTopic?: string;
108
- impersonateUser?: string;
109
- useApplicationDefaultCredentials: boolean;
110
- credentialsJson?: string;
111
- };
112
-
113
- export type GoogleChatConnectorState = {
114
- userName: string;
115
- pid: number;
116
- rpcAddress: string;
117
- port: number;
118
- baseUrl: string;
119
- startedAt: string;
120
- };
121
-
122
- export type ConnectLinearOptions = {
123
- userName: string;
124
- apiKey?: string;
125
- clientId?: string;
126
- clientSecret?: string;
127
- accessToken?: string;
128
- webhookSecret: string;
129
- cwd: string;
130
- model?: string;
131
- provider?: string;
132
- apiProviderKey?: string;
133
- systemPrompt?: string;
134
- mode: "act" | "plan";
135
- interactive: boolean;
136
- maxIterations?: number;
137
- enableTools: boolean;
138
- rpcAddress: string;
139
- hookCommand?: string;
140
- port: number;
141
- host: string;
142
- baseUrl: string;
143
- };
144
-
145
- export type LinearConnectorState = {
146
- userName: string;
147
- pid: number;
148
- rpcAddress: string;
149
- port: number;
150
- baseUrl: string;
151
- startedAt: string;
152
- };
@@ -1,73 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const ConnectorHookEventNameSchema = z.enum([
4
- "connector.started",
5
- "connector.stopping",
6
- "session.authorize",
7
- "message.received",
8
- "message.denied",
9
- "message.completed",
10
- "message.failed",
11
- "session.started",
12
- "session.reused",
13
- "session.reset",
14
- "schedule.delivery.started",
15
- "schedule.delivery.sent",
16
- "schedule.delivery.failed",
17
- ]);
18
-
19
- export type ConnectorHookEventName = z.infer<
20
- typeof ConnectorHookEventNameSchema
21
- >;
22
-
23
- export const ConnectorEventActorSchema = z.object({
24
- id: z.string().optional(),
25
- label: z.string().optional(),
26
- role: z.string().optional(),
27
- participantKey: z.string().optional(),
28
- participantLabel: z.string().optional(),
29
- platformUserId: z.string().optional(),
30
- metadata: z.record(z.string(), z.unknown()).optional(),
31
- });
32
-
33
- export const ConnectorEventContextSchema = z.object({
34
- source: z.string(),
35
- sourceEvent: z.string(),
36
- threadId: z.string(),
37
- channelId: z.string(),
38
- isDM: z.boolean(),
39
- sessionId: z.string().optional(),
40
- workspaceRoot: z.string().optional(),
41
- metadata: z.record(z.string(), z.unknown()).optional(),
42
- });
43
-
44
- export const ConnectorAuthorizationRequestSchema = z.object({
45
- actor: ConnectorEventActorSchema,
46
- context: ConnectorEventContextSchema,
47
- payload: z.record(z.string(), z.unknown()).optional(),
48
- });
49
-
50
- export const ConnectorAuthorizationDecisionSchema = z.object({
51
- action: z.enum(["allow", "deny"]).default("allow"),
52
- message: z.string().optional(),
53
- reason: z.string().optional(),
54
- metadata: z.record(z.string(), z.unknown()).optional(),
55
- });
56
-
57
- export const ConnectorHookEventSchema = z.object({
58
- adapter: z.string(),
59
- botUserName: z.string().optional(),
60
- event: ConnectorHookEventNameSchema,
61
- payload: z.record(z.string(), z.unknown()),
62
- ts: z.string(),
63
- });
64
-
65
- export type ConnectorHookEvent = z.infer<typeof ConnectorHookEventSchema>;
66
- export type ConnectorEventActor = z.infer<typeof ConnectorEventActorSchema>;
67
- export type ConnectorEventContext = z.infer<typeof ConnectorEventContextSchema>;
68
- export type ConnectorAuthorizationRequest = z.infer<
69
- typeof ConnectorAuthorizationRequestSchema
70
- >;
71
- export type ConnectorAuthorizationDecision = z.infer<
72
- typeof ConnectorAuthorizationDecisionSchema
73
- >;
package/src/db/index.ts DELETED
@@ -1,14 +0,0 @@
1
- export type {
2
- SessionSchemaOptions,
3
- SqliteDb,
4
- SqliteStatement,
5
- } from "./sqlite-db";
6
- export {
7
- asBool,
8
- asOptionalString,
9
- asString,
10
- ensureSessionSchema,
11
- loadSqliteDb,
12
- nowIso,
13
- toBoolInt,
14
- } from "./sqlite-db";
@@ -1,329 +0,0 @@
1
- import { mkdirSync } from "node:fs";
2
- import { createRequire } from "node:module";
3
- import { dirname } from "node:path";
4
-
5
- export type SqliteStatement = {
6
- run: (...params: unknown[]) => { changes?: number };
7
- get: (...params: unknown[]) => Record<string, unknown> | null;
8
- all: (...params: unknown[]) => Record<string, unknown>[];
9
- };
10
-
11
- export type SqliteDb = {
12
- prepare: (sql: string) => SqliteStatement;
13
- exec: (sql: string) => void;
14
- close?: () => void;
15
- };
16
-
17
- export function nowIso(): string {
18
- return new Date().toISOString();
19
- }
20
-
21
- export function toBoolInt(value: boolean): number {
22
- return value ? 1 : 0;
23
- }
24
-
25
- export function asString(value: unknown): string {
26
- return typeof value === "string" ? value : "";
27
- }
28
-
29
- export function asOptionalString(value: unknown): string | undefined {
30
- if (typeof value !== "string") return undefined;
31
- const trimmed = value.trim();
32
- return trimmed.length > 0 ? trimmed : undefined;
33
- }
34
-
35
- export function asBool(value: unknown): boolean {
36
- return value === 1 || value === true;
37
- }
38
-
39
- function wrapBunDb(db: {
40
- query: (sql: string) => SqliteStatement;
41
- exec: (sql: string) => void;
42
- close?: () => void;
43
- }): SqliteDb {
44
- return {
45
- prepare: (sql) => db.query(sql),
46
- exec: (sql) => db.exec(sql),
47
- close: () => db.close?.(),
48
- };
49
- }
50
-
51
- function wrapNodeDb(db: {
52
- prepare: (sql: string) => {
53
- run: (...params: unknown[]) => { changes?: number };
54
- get: (...params: unknown[]) => Record<string, unknown> | undefined;
55
- all: (...params: unknown[]) => Record<string, unknown>[];
56
- };
57
- exec: (sql: string) => void;
58
- close?: () => void;
59
- }): SqliteDb {
60
- return {
61
- prepare: (sql) => {
62
- const stmt = db.prepare(sql);
63
- return {
64
- run: (...params) => stmt.run(...params),
65
- get: (...params) => stmt.get(...params) ?? null,
66
- all: (...params) => stmt.all(...params),
67
- };
68
- },
69
- exec: (sql) => db.exec(sql),
70
- close: () => db.close?.(),
71
- };
72
- }
73
-
74
- export function loadSqliteDb(filePath: string): SqliteDb {
75
- mkdirSync(dirname(filePath), { recursive: true });
76
- const require = createRequire(import.meta.url);
77
-
78
- if (typeof (globalThis as { Bun?: unknown }).Bun !== "undefined") {
79
- const { Database } = require("bun:sqlite") as {
80
- Database: new (
81
- path: string,
82
- options?: { create?: boolean },
83
- ) => {
84
- query: (sql: string) => SqliteStatement;
85
- exec: (sql: string) => void;
86
- close?: () => void;
87
- };
88
- };
89
- return wrapBunDb(new Database(filePath, { create: true }));
90
- }
91
-
92
- try {
93
- // Suppress "ExperimentalWarning: SQLite is an experimental feature"
94
- const originalEmit = process.emitWarning;
95
- process.emitWarning = ((warning: string | Error, ...args: unknown[]) => {
96
- const msg =
97
- typeof warning === "string" ? warning : (warning?.message ?? "");
98
- if (msg.includes("SQLite")) return;
99
- return (originalEmit as Function).call(process, warning, ...args);
100
- }) as typeof process.emitWarning;
101
-
102
- const { DatabaseSync } = require(["node", ":sqlite"].join("")) as {
103
- DatabaseSync: new (
104
- path: string,
105
- ) => {
106
- prepare: (sql: string) => {
107
- run: (...params: unknown[]) => { changes?: number };
108
- get: (...params: unknown[]) => Record<string, unknown> | undefined;
109
- all: (...params: unknown[]) => Record<string, unknown>[];
110
- };
111
- exec: (sql: string) => void;
112
- close?: () => void;
113
- };
114
- };
115
- process.emitWarning = originalEmit;
116
- return wrapNodeDb(new DatabaseSync(filePath));
117
- } catch {
118
- // Fall through to better-sqlite3 for older Node runtimes.
119
- }
120
-
121
- try {
122
- const BetterSqlite3 = require(["better", "-sqlite3"].join("")) as new (
123
- path: string,
124
- ) => SqliteDb;
125
- return new BetterSqlite3(filePath);
126
- } catch (error) {
127
- throw new Error(
128
- "SQLite requires Node's built-in sqlite support or the optional dependency better-sqlite3. Install better-sqlite3 when running on older Node versions without node:sqlite.",
129
- { cause: error },
130
- );
131
- }
132
- }
133
-
134
- export interface SessionSchemaOptions {
135
- includeLegacyMigrations?: boolean;
136
- }
137
-
138
- const SCHEMA_STATEMENTS = [
139
- `CREATE TABLE IF NOT EXISTS sessions (
140
- session_id TEXT PRIMARY KEY,
141
- source TEXT NOT NULL,
142
- pid INTEGER NOT NULL,
143
- started_at TEXT NOT NULL,
144
- ended_at TEXT,
145
- exit_code INTEGER,
146
- status TEXT NOT NULL,
147
- status_lock INTEGER NOT NULL DEFAULT 0,
148
- interactive INTEGER NOT NULL,
149
- provider TEXT NOT NULL,
150
- model TEXT NOT NULL,
151
- cwd TEXT NOT NULL,
152
- workspace_root TEXT NOT NULL,
153
- team_name TEXT,
154
- enable_tools INTEGER NOT NULL,
155
- enable_spawn INTEGER NOT NULL,
156
- enable_teams INTEGER NOT NULL,
157
- parent_session_id TEXT,
158
- parent_agent_id TEXT,
159
- agent_id TEXT,
160
- conversation_id TEXT,
161
- is_subagent INTEGER NOT NULL DEFAULT 0,
162
- prompt TEXT,
163
- metadata_json TEXT,
164
- transcript_path TEXT NOT NULL,
165
- hook_path TEXT NOT NULL,
166
- messages_path TEXT,
167
- updated_at TEXT NOT NULL
168
- );`,
169
- `CREATE TABLE IF NOT EXISTS subagent_spawn_queue (
170
- id INTEGER PRIMARY KEY AUTOINCREMENT,
171
- root_session_id TEXT NOT NULL,
172
- parent_agent_id TEXT NOT NULL,
173
- task TEXT,
174
- system_prompt TEXT,
175
- created_at TEXT NOT NULL,
176
- consumed_at TEXT
177
- );`,
178
- `CREATE TABLE IF NOT EXISTS schedules (
179
- schedule_id TEXT PRIMARY KEY,
180
- name TEXT NOT NULL,
181
- cron_pattern TEXT NOT NULL,
182
- prompt TEXT NOT NULL,
183
- provider TEXT NOT NULL,
184
- model TEXT NOT NULL,
185
- mode TEXT NOT NULL DEFAULT 'act',
186
- workspace_root TEXT,
187
- cwd TEXT,
188
- system_prompt TEXT,
189
- max_iterations INTEGER,
190
- timeout_seconds INTEGER,
191
- max_parallel INTEGER NOT NULL DEFAULT 1,
192
- enabled INTEGER NOT NULL DEFAULT 1,
193
- created_at TEXT NOT NULL,
194
- updated_at TEXT NOT NULL,
195
- last_run_at TEXT,
196
- next_run_at TEXT,
197
- claim_token TEXT,
198
- claim_started_at TEXT,
199
- claim_until_at TEXT,
200
- created_by TEXT,
201
- tags TEXT,
202
- metadata_json TEXT
203
- );`,
204
- `CREATE TABLE IF NOT EXISTS schedule_executions (
205
- execution_id TEXT PRIMARY KEY,
206
- schedule_id TEXT NOT NULL,
207
- session_id TEXT,
208
- triggered_at TEXT NOT NULL,
209
- started_at TEXT,
210
- ended_at TEXT,
211
- status TEXT NOT NULL,
212
- exit_code INTEGER,
213
- error_message TEXT,
214
- iterations INTEGER,
215
- tokens_used INTEGER,
216
- cost_usd REAL,
217
- FOREIGN KEY (schedule_id) REFERENCES schedules(schedule_id) ON DELETE CASCADE,
218
- FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE SET NULL
219
- );`,
220
- `CREATE INDEX IF NOT EXISTS idx_schedule_executions_schedule
221
- ON schedule_executions(schedule_id, triggered_at DESC);`,
222
- `CREATE INDEX IF NOT EXISTS idx_schedules_next_run
223
- ON schedules(enabled, next_run_at);`,
224
- ];
225
-
226
- const LEGACY_MIGRATIONS: Array<{
227
- table: string;
228
- column: string;
229
- sql: string;
230
- }> = [
231
- {
232
- table: "sessions",
233
- column: "workspace_root",
234
- sql: "ALTER TABLE sessions ADD COLUMN workspace_root TEXT;",
235
- },
236
- {
237
- table: "sessions",
238
- column: "parent_session_id",
239
- sql: "ALTER TABLE sessions ADD COLUMN parent_session_id TEXT;",
240
- },
241
- {
242
- table: "sessions",
243
- column: "parent_agent_id",
244
- sql: "ALTER TABLE sessions ADD COLUMN parent_agent_id TEXT;",
245
- },
246
- {
247
- table: "sessions",
248
- column: "agent_id",
249
- sql: "ALTER TABLE sessions ADD COLUMN agent_id TEXT;",
250
- },
251
- {
252
- table: "sessions",
253
- column: "conversation_id",
254
- sql: "ALTER TABLE sessions ADD COLUMN conversation_id TEXT;",
255
- },
256
- {
257
- table: "sessions",
258
- column: "is_subagent",
259
- sql: "ALTER TABLE sessions ADD COLUMN is_subagent INTEGER NOT NULL DEFAULT 0;",
260
- },
261
- {
262
- table: "sessions",
263
- column: "messages_path",
264
- sql: "ALTER TABLE sessions ADD COLUMN messages_path TEXT;",
265
- },
266
- {
267
- table: "sessions",
268
- column: "metadata_json",
269
- sql: "ALTER TABLE sessions ADD COLUMN metadata_json TEXT;",
270
- },
271
- {
272
- table: "schedules",
273
- column: "claim_token",
274
- sql: "ALTER TABLE schedules ADD COLUMN claim_token TEXT;",
275
- },
276
- {
277
- table: "schedules",
278
- column: "claim_started_at",
279
- sql: "ALTER TABLE schedules ADD COLUMN claim_started_at TEXT;",
280
- },
281
- {
282
- table: "schedules",
283
- column: "claim_until_at",
284
- sql: "ALTER TABLE schedules ADD COLUMN claim_until_at TEXT;",
285
- },
286
- ];
287
-
288
- function getColumnNames(db: SqliteDb, table: string): Set<string> {
289
- return new Set(
290
- db
291
- .prepare(`PRAGMA table_info(${table});`)
292
- .all()
293
- .map((c) => c.name as string),
294
- );
295
- }
296
-
297
- export function ensureSessionSchema(
298
- db: SqliteDb,
299
- options: SessionSchemaOptions = {},
300
- ): void {
301
- db.exec("PRAGMA journal_mode = WAL;");
302
- db.exec("PRAGMA busy_timeout = 5000;");
303
- for (const stmt of SCHEMA_STATEMENTS) {
304
- db.exec(stmt);
305
- }
306
-
307
- if (!options.includeLegacyMigrations) return;
308
-
309
- const columnCache = new Map<string, Set<string>>();
310
- const getColumns = (table: string) => {
311
- let cols = columnCache.get(table);
312
- if (!cols) {
313
- cols = getColumnNames(db, table);
314
- columnCache.set(table, cols);
315
- }
316
- return cols;
317
- };
318
-
319
- for (const migration of LEGACY_MIGRATIONS) {
320
- if (!getColumns(migration.table).has(migration.column)) {
321
- db.exec(migration.sql);
322
- if (migration.column === "workspace_root") {
323
- db.exec(
324
- "UPDATE sessions SET workspace_root = cwd WHERE workspace_root IS NULL OR workspace_root = '';",
325
- );
326
- }
327
- }
328
- }
329
- }