@pikku/kysely 0.12.2 → 0.12.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## 0.12.0
2
2
 
3
+ ## 0.12.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 32ed003: Add envelope encryption utilities and database-backed secret services with KEK rotation support
8
+ - 387b2ee: Add error_message column to agent run storage and queries
9
+ - b2b0af9: Migrate all consumers from @pikku/pg to @pikku/kysely and remove the @pikku/pg package
10
+ - c7ff141: Add WorkflowVersionStatus type with draft→active lifecycle for AI-generated workflows, type all DB status fields with proper unions instead of plain strings
11
+ - Updated dependencies [387b2ee]
12
+ - Updated dependencies [32ed003]
13
+ - Updated dependencies [7d369f3]
14
+ - Updated dependencies [508a796]
15
+ - Updated dependencies [ffe83af]
16
+ - Updated dependencies [c7ff141]
17
+ - @pikku/core@0.12.3
18
+
3
19
  ## 0.12.2
4
20
 
5
21
  ### Patch Changes
@@ -1,4 +1,3 @@
1
- export { PikkuKysely } from './pikku-kysely.js';
2
1
  export { KyselyChannelStore } from './kysely-channel-store.js';
3
2
  export { KyselyEventHubStore } from './kysely-eventhub-store.js';
4
3
  export { KyselyWorkflowService } from './kysely-workflow-service.js';
@@ -6,6 +5,8 @@ export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
6
5
  export { KyselyDeploymentService } from './kysely-deployment-service.js';
7
6
  export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
8
7
  export { KyselyAgentRunService } from './kysely-agent-run-service.js';
8
+ export { KyselySecretService } from './kysely-secret-service.js';
9
+ export type { KyselySecretServiceConfig } from './kysely-secret-service.js';
9
10
  export type { KyselyPikkuDB } from './kysely-tables.js';
10
11
  export type { WorkflowRunService } from '@pikku/core/workflow';
11
12
  export type { AgentRunService, AgentRunRow } from '@pikku/core/ai-agent';
package/dist/src/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export { PikkuKysely } from './pikku-kysely.js';
2
1
  export { KyselyChannelStore } from './kysely-channel-store.js';
3
2
  export { KyselyEventHubStore } from './kysely-eventhub-store.js';
4
3
  export { KyselyWorkflowService } from './kysely-workflow-service.js';
@@ -6,3 +5,4 @@ export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
6
5
  export { KyselyDeploymentService } from './kysely-deployment-service.js';
7
6
  export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
8
7
  export { KyselyAgentRunService } from './kysely-agent-run-service.js';
8
+ export { KyselySecretService } from './kysely-secret-service.js';
@@ -113,6 +113,7 @@ export class KyselyAgentRunService {
113
113
  'thread_id',
114
114
  'resource_id',
115
115
  'status',
116
+ 'error_message',
116
117
  'suspend_reason',
117
118
  'missing_rpcs',
118
119
  'usage_input_tokens',
@@ -159,6 +160,7 @@ export class KyselyAgentRunService {
159
160
  threadId: row.thread_id,
160
161
  resourceId: row.resource_id,
161
162
  status: row.status,
163
+ errorMessage: row.error_message ?? undefined,
162
164
  suspendReason: row.suspend_reason ?? undefined,
163
165
  missingRpcs: parseJson(row.missing_rpcs),
164
166
  usageInputTokens: Number(row.usage_input_tokens),
@@ -11,9 +11,14 @@ export class KyselyAIStorageService {
11
11
  await builder.execute();
12
12
  }
13
13
  catch (e) {
14
- // Ignore "index already exists" errors (MySQL doesn't support IF NOT EXISTS for indexes)
14
+ // Ignore "index already exists" errors across databases
15
+ // MySQL: ER_DUP_KEYNAME, Postgres: 42P07, SQLite: "already exists"
15
16
  if (e?.code === 'ER_DUP_KEYNAME' || e?.errno === 1061)
16
17
  return;
18
+ if (e?.code === '42P07')
19
+ return;
20
+ if (e?.message?.includes('already exists'))
21
+ return;
17
22
  throw e;
18
23
  }
19
24
  }
@@ -90,6 +95,7 @@ export class KyselyAIStorageService {
90
95
  .addColumn('thread_id', 'varchar(36)', (col) => col.notNull().references('ai_threads.id').onDelete('cascade'))
91
96
  .addColumn('resource_id', 'varchar(255)', (col) => col.notNull())
92
97
  .addColumn('status', 'varchar(50)', (col) => col.notNull().defaultTo('running'))
98
+ .addColumn('error_message', 'text')
93
99
  .addColumn('suspend_reason', 'text')
94
100
  .addColumn('missing_rpcs', 'text')
95
101
  .addColumn('usage_input_tokens', 'integer', (col) => col.notNull().defaultTo(0))
@@ -352,6 +358,7 @@ export class KyselyAIStorageService {
352
358
  thread_id: run.threadId,
353
359
  resource_id: run.resourceId,
354
360
  status: run.status,
361
+ error_message: run.errorMessage ?? null,
355
362
  suspend_reason: run.suspendReason ?? null,
356
363
  missing_rpcs: run.missingRpcs ? JSON.stringify(run.missingRpcs) : null,
357
364
  usage_input_tokens: run.usage.inputTokens,
@@ -371,6 +378,9 @@ export class KyselyAIStorageService {
371
378
  if (updates.status !== undefined) {
372
379
  setValues.status = updates.status;
373
380
  }
381
+ if (updates.errorMessage !== undefined) {
382
+ setValues.error_message = updates.errorMessage;
383
+ }
374
384
  if (updates.suspendReason !== undefined) {
375
385
  setValues.suspend_reason = updates.suspendReason;
376
386
  }
@@ -415,6 +425,7 @@ export class KyselyAIStorageService {
415
425
  'thread_id',
416
426
  'resource_id',
417
427
  'status',
428
+ 'error_message',
418
429
  'suspend_reason',
419
430
  'missing_rpcs',
420
431
  'usage_input_tokens',
@@ -452,6 +463,7 @@ export class KyselyAIStorageService {
452
463
  'thread_id',
453
464
  'resource_id',
454
465
  'status',
466
+ 'error_message',
455
467
  'suspend_reason',
456
468
  'missing_rpcs',
457
469
  'usage_input_tokens',
@@ -539,6 +551,7 @@ export class KyselyAIStorageService {
539
551
  threadId: row.thread_id,
540
552
  resourceId: row.resource_id,
541
553
  status: row.status,
554
+ errorMessage: row.error_message ?? undefined,
542
555
  suspendReason: row.suspend_reason,
543
556
  missingRpcs: parseJson(row.missing_rpcs),
544
557
  pendingApprovals,
@@ -2,7 +2,7 @@ import type { DeploymentService, DeploymentServiceConfig, DeploymentConfig, Depl
2
2
  import type { Kysely } from 'kysely';
3
3
  import type { KyselyPikkuDB } from './kysely-tables.js';
4
4
  export declare class KyselyDeploymentService implements DeploymentService {
5
- private db;
5
+ protected db: Kysely<KyselyPikkuDB>;
6
6
  private initialized;
7
7
  private heartbeatTimer?;
8
8
  private deploymentConfig?;
@@ -13,5 +13,6 @@ export declare class KyselyDeploymentService implements DeploymentService {
13
13
  start(config: DeploymentConfig): Promise<void>;
14
14
  stop(): Promise<void>;
15
15
  findFunction(name: string): Promise<DeploymentInfo[]>;
16
+ private createIndexSafe;
16
17
  private sendHeartbeat;
17
18
  }
@@ -37,18 +37,16 @@ export class KyselyDeploymentService {
37
37
  'function_name',
38
38
  ])
39
39
  .execute();
40
- await this.db.schema
40
+ await this.createIndexSafe(this.db.schema
41
41
  .createIndex('idx_pikku_deployments_heartbeat')
42
42
  .ifNotExists()
43
43
  .on('pikku_deployments')
44
- .column('last_heartbeat')
45
- .execute();
46
- await this.db.schema
44
+ .column('last_heartbeat'));
45
+ await this.createIndexSafe(this.db.schema
47
46
  .createIndex('idx_pikku_deployment_functions_name')
48
47
  .ifNotExists()
49
48
  .on('pikku_deployment_functions')
50
- .column('function_name')
51
- .execute();
49
+ .column('function_name'));
52
50
  this.initialized = true;
53
51
  }
54
52
  async start(config) {
@@ -111,6 +109,20 @@ export class KyselyDeploymentService {
111
109
  endpoint: row.endpoint,
112
110
  }));
113
111
  }
112
+ async createIndexSafe(builder) {
113
+ try {
114
+ await builder.execute();
115
+ }
116
+ catch (e) {
117
+ if (e?.code === 'ER_DUP_KEYNAME' || e?.errno === 1061)
118
+ return;
119
+ if (e?.code === '42P07')
120
+ return;
121
+ if (e?.message?.includes('already exists'))
122
+ return;
123
+ throw e;
124
+ }
125
+ }
114
126
  async sendHeartbeat() {
115
127
  if (!this.deploymentConfig)
116
128
  return;
@@ -0,0 +1,29 @@
1
+ import type { SecretService } from '@pikku/core/services';
2
+ import type { Kysely } from 'kysely';
3
+ import type { KyselyPikkuDB } from './kysely-tables.js';
4
+ export interface KyselySecretServiceConfig {
5
+ key: string;
6
+ keyVersion?: number;
7
+ previousKey?: string;
8
+ audit?: boolean;
9
+ auditReads?: boolean;
10
+ }
11
+ export declare class KyselySecretService implements SecretService {
12
+ private db;
13
+ private initialized;
14
+ private key;
15
+ private keyVersion;
16
+ private previousKey?;
17
+ private audit;
18
+ private auditReads;
19
+ constructor(db: Kysely<KyselyPikkuDB>, config: KyselySecretServiceConfig);
20
+ init(): Promise<void>;
21
+ private logAudit;
22
+ private getKEK;
23
+ getSecret(key: string): Promise<string>;
24
+ getSecretJSON<R = {}>(key: string): Promise<R>;
25
+ hasSecret(key: string): Promise<boolean>;
26
+ setSecretJSON(key: string, value: unknown): Promise<void>;
27
+ deleteSecret(key: string): Promise<void>;
28
+ rotateKEK(): Promise<number>;
29
+ }
@@ -0,0 +1,142 @@
1
+ import { sql } from 'kysely';
2
+ import { envelopeEncrypt, envelopeDecrypt, envelopeRewrap, } from '@pikku/core/crypto-utils';
3
+ export class KyselySecretService {
4
+ db;
5
+ initialized = false;
6
+ key;
7
+ keyVersion;
8
+ previousKey;
9
+ audit;
10
+ auditReads;
11
+ constructor(db, config) {
12
+ this.db = db;
13
+ this.key = config.key;
14
+ this.keyVersion = config.keyVersion ?? 1;
15
+ this.previousKey = config.previousKey;
16
+ this.audit = config.audit ?? false;
17
+ this.auditReads = config.auditReads ?? false;
18
+ }
19
+ async init() {
20
+ if (this.initialized)
21
+ return;
22
+ await this.db.schema
23
+ .createTable('secrets')
24
+ .ifNotExists()
25
+ .addColumn('key', 'varchar(255)', (col) => col.primaryKey())
26
+ .addColumn('ciphertext', 'text', (col) => col.notNull())
27
+ .addColumn('wrapped_dek', 'text', (col) => col.notNull())
28
+ .addColumn('key_version', 'integer', (col) => col.notNull())
29
+ .addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
30
+ .addColumn('updated_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
31
+ .execute();
32
+ if (this.audit) {
33
+ await this.db.schema
34
+ .createTable('secrets_audit')
35
+ .ifNotExists()
36
+ .addColumn('id', 'varchar(36)', (col) => col.primaryKey())
37
+ .addColumn('secret_key', 'varchar(255)', (col) => col.notNull())
38
+ .addColumn('action', 'varchar(20)', (col) => col.notNull())
39
+ .addColumn('performed_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
40
+ .execute();
41
+ }
42
+ this.initialized = true;
43
+ }
44
+ async logAudit(secretKey, action) {
45
+ if (!this.audit)
46
+ return;
47
+ if (action === 'read' && !this.auditReads)
48
+ return;
49
+ await this.db
50
+ .insertInto('secrets_audit')
51
+ .values({
52
+ id: crypto.randomUUID(),
53
+ secret_key: secretKey,
54
+ action,
55
+ performed_at: new Date().toISOString(),
56
+ })
57
+ .execute();
58
+ }
59
+ getKEK(version) {
60
+ if (version === this.keyVersion)
61
+ return this.key;
62
+ if (this.previousKey)
63
+ return this.previousKey;
64
+ throw new Error(`No KEK available for key_version ${version}`);
65
+ }
66
+ async getSecret(key) {
67
+ const row = await this.db
68
+ .selectFrom('secrets')
69
+ .select(['ciphertext', 'wrapped_dek', 'key_version'])
70
+ .where('key', '=', key)
71
+ .executeTakeFirst();
72
+ if (!row)
73
+ throw new Error(`Secret not found: ${key}`);
74
+ const kek = this.getKEK(row.key_version);
75
+ const result = await envelopeDecrypt(kek, row.ciphertext, row.wrapped_dek);
76
+ await this.logAudit(key, 'read');
77
+ return result;
78
+ }
79
+ async getSecretJSON(key) {
80
+ const raw = await this.getSecret(key);
81
+ return JSON.parse(raw);
82
+ }
83
+ async hasSecret(key) {
84
+ const row = await this.db
85
+ .selectFrom('secrets')
86
+ .select('key')
87
+ .where('key', '=', key)
88
+ .executeTakeFirst();
89
+ return !!row;
90
+ }
91
+ async setSecretJSON(key, value) {
92
+ const plaintext = JSON.stringify(value);
93
+ const { ciphertext, wrappedDEK } = await envelopeEncrypt(this.key, plaintext);
94
+ const now = new Date().toISOString();
95
+ await this.db
96
+ .insertInto('secrets')
97
+ .values({
98
+ key,
99
+ ciphertext,
100
+ wrapped_dek: wrappedDEK,
101
+ key_version: this.keyVersion,
102
+ created_at: now,
103
+ updated_at: now,
104
+ })
105
+ .onConflict((oc) => oc.column('key').doUpdateSet({
106
+ ciphertext,
107
+ wrapped_dek: wrappedDEK,
108
+ key_version: this.keyVersion,
109
+ updated_at: now,
110
+ }))
111
+ .execute();
112
+ await this.logAudit(key, 'write');
113
+ }
114
+ async deleteSecret(key) {
115
+ await this.db.deleteFrom('secrets').where('key', '=', key).execute();
116
+ await this.logAudit(key, 'delete');
117
+ }
118
+ async rotateKEK() {
119
+ if (!this.previousKey) {
120
+ throw new Error('No previousKey configured — nothing to rotate from');
121
+ }
122
+ const rows = await this.db
123
+ .selectFrom('secrets')
124
+ .select(['key', 'wrapped_dek'])
125
+ .where('key_version', '<', this.keyVersion)
126
+ .execute();
127
+ for (const row of rows) {
128
+ const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.wrapped_dek);
129
+ await this.db
130
+ .updateTable('secrets')
131
+ .set({
132
+ wrapped_dek: newWrappedDEK,
133
+ key_version: this.keyVersion,
134
+ updated_at: new Date().toISOString(),
135
+ })
136
+ .where('key', '=', row.key)
137
+ .execute();
138
+ await this.logAudit(row.key, 'rotate');
139
+ }
140
+ return rows.length;
141
+ }
142
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Generated } from 'kysely';
2
+ import type { WorkflowStatus, StepStatus, WorkflowVersionStatus } from '@pikku/core/workflow';
2
3
  export interface ChannelsTable {
3
4
  channel_id: string;
4
5
  channel_name: string;
@@ -14,7 +15,7 @@ export interface ChannelSubscriptionsTable {
14
15
  export interface WorkflowRunsTable {
15
16
  workflow_run_id: Generated<string>;
16
17
  workflow: string;
17
- status: string;
18
+ status: WorkflowStatus;
18
19
  input: string;
19
20
  output: string | null;
20
21
  error: string | null;
@@ -31,7 +32,7 @@ export interface WorkflowStepTable {
31
32
  step_name: string;
32
33
  rpc_name: string | null;
33
34
  data: string | null;
34
- status: Generated<string>;
35
+ status: Generated<StepStatus>;
35
36
  result: string | null;
36
37
  error: string | null;
37
38
  branch_taken: string | null;
@@ -43,7 +44,7 @@ export interface WorkflowStepTable {
43
44
  export interface WorkflowStepHistoryTable {
44
45
  history_id: Generated<string>;
45
46
  workflow_step_id: string;
46
- status: string;
47
+ status: StepStatus;
47
48
  result: string | null;
48
49
  error: string | null;
49
50
  created_at: Generated<Date>;
@@ -57,6 +58,7 @@ export interface WorkflowVersionsTable {
57
58
  graph_hash: string;
58
59
  graph: string;
59
60
  source: string;
61
+ status: Generated<WorkflowVersionStatus>;
60
62
  created_at: Generated<Date>;
61
63
  }
62
64
  export interface AIThreadsTable {
@@ -70,7 +72,7 @@ export interface AIThreadsTable {
70
72
  export interface AIMessageTable {
71
73
  id: string;
72
74
  thread_id: string;
73
- role: string;
75
+ role: 'system' | 'user' | 'assistant' | 'tool';
74
76
  content: string | null;
75
77
  created_at: Generated<Date>;
76
78
  }
@@ -82,8 +84,8 @@ export interface AIToolCallTable {
82
84
  tool_name: string;
83
85
  args: string;
84
86
  result: string | null;
85
- approval_status: string | null;
86
- approval_type: string | null;
87
+ approval_status: 'approved' | 'denied' | 'pending' | null;
88
+ approval_type: 'agent-call' | 'tool-call' | null;
87
89
  agent_run_id: string | null;
88
90
  display_tool_name: string | null;
89
91
  display_args: string | null;
@@ -100,8 +102,9 @@ export interface AIRunTable {
100
102
  agent_name: string;
101
103
  thread_id: string;
102
104
  resource_id: string;
103
- status: Generated<string>;
104
- suspend_reason: string | null;
105
+ status: Generated<'running' | 'suspended' | 'completed' | 'failed'>;
106
+ error_message: string | null;
107
+ suspend_reason: 'approval' | 'rpc-missing' | null;
105
108
  missing_rpcs: string | null;
106
109
  usage_input_tokens: Generated<number>;
107
110
  usage_output_tokens: Generated<number>;
@@ -119,6 +122,20 @@ export interface PikkuDeploymentFunctionsTable {
119
122
  deployment_id: string;
120
123
  function_name: string;
121
124
  }
125
+ export interface SecretsTable {
126
+ key: string;
127
+ ciphertext: string;
128
+ wrapped_dek: string;
129
+ key_version: number;
130
+ created_at: Generated<Date>;
131
+ updated_at: Generated<Date>;
132
+ }
133
+ export interface SecretsAuditTable {
134
+ id: string;
135
+ secret_key: string;
136
+ action: string;
137
+ performed_at: Generated<Date>;
138
+ }
122
139
  export interface KyselyPikkuDB {
123
140
  channels: ChannelsTable;
124
141
  channel_subscriptions: ChannelSubscriptionsTable;
@@ -133,4 +150,6 @@ export interface KyselyPikkuDB {
133
150
  ai_run: AIRunTable;
134
151
  pikku_deployments: PikkuDeploymentsTable;
135
152
  pikku_deployment_functions: PikkuDeploymentFunctionsTable;
153
+ secrets: SecretsTable;
154
+ secrets_audit: SecretsAuditTable;
136
155
  }
@@ -24,6 +24,11 @@ export declare class KyselyWorkflowRunService implements WorkflowRunService {
24
24
  graph: any;
25
25
  source: string;
26
26
  } | null>;
27
+ getAIGeneratedWorkflows(agentName?: string): Promise<Array<{
28
+ workflowName: string;
29
+ graphHash: string;
30
+ graph: any;
31
+ }>>;
27
32
  deleteRun(id: string): Promise<boolean>;
28
33
  private mapRunRow;
29
34
  }
@@ -169,6 +169,22 @@ export class KyselyWorkflowRunService {
169
169
  source: row.source,
170
170
  };
171
171
  }
172
+ async getAIGeneratedWorkflows(agentName) {
173
+ let query = this.db
174
+ .selectFrom('workflow_versions')
175
+ .select(['workflow_name', 'graph_hash', 'graph'])
176
+ .where('source', '=', 'ai-agent')
177
+ .where('status', '=', 'active');
178
+ if (agentName) {
179
+ query = query.where('workflow_name', 'like', `ai:${agentName}:%`);
180
+ }
181
+ const rows = await query.execute();
182
+ return rows.map((row) => ({
183
+ workflowName: row.workflow_name,
184
+ graphHash: row.graph_hash,
185
+ graph: parseJson(row.graph),
186
+ }));
187
+ }
172
188
  async deleteRun(id) {
173
189
  const result = await this.db
174
190
  .deleteFrom('workflow_runs')
@@ -1,9 +1,9 @@
1
1
  import type { SerializedError } from '@pikku/core';
2
- import { PikkuWorkflowService, type WorkflowRun, type WorkflowRunWire, type StepState, type WorkflowStatus } from '@pikku/core/workflow';
2
+ import { PikkuWorkflowService, type WorkflowRun, type WorkflowRunWire, type StepState, type WorkflowStatus, type WorkflowVersionStatus } from '@pikku/core/workflow';
3
3
  import type { Kysely } from 'kysely';
4
4
  import type { KyselyPikkuDB } from './kysely-tables.js';
5
5
  export declare class KyselyWorkflowService extends PikkuWorkflowService {
6
- private db;
6
+ protected db: Kysely<KyselyPikkuDB>;
7
7
  private initialized;
8
8
  private runService;
9
9
  constructor(db: Kysely<KyselyPikkuDB>);
@@ -26,8 +26,8 @@ export declare class KyselyWorkflowService extends PikkuWorkflowService {
26
26
  setStepResult(stepId: string, result: any): Promise<void>;
27
27
  setStepError(stepId: string, error: Error): Promise<void>;
28
28
  createRetryAttempt(stepId: string, status: 'pending' | 'running'): Promise<StepState>;
29
- withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>;
30
- withStepLock<T>(runId: string, stepName: string, fn: () => Promise<T>): Promise<T>;
29
+ withRunLock<T>(_id: string, fn: () => Promise<T>): Promise<T>;
30
+ withStepLock<T>(_runId: string, _stepName: string, fn: () => Promise<T>): Promise<T>;
31
31
  getCompletedGraphState(runId: string): Promise<{
32
32
  completedNodeIds: string[];
33
33
  failedNodeIds: string[];
@@ -38,10 +38,16 @@ export declare class KyselyWorkflowService extends PikkuWorkflowService {
38
38
  setBranchTaken(stepId: string, branchKey: string): Promise<void>;
39
39
  updateRunState(runId: string, name: string, value: unknown): Promise<void>;
40
40
  getRunState(runId: string): Promise<Record<string, unknown>>;
41
- upsertWorkflowVersion(name: string, graphHash: string, graph: any, source: string): Promise<void>;
41
+ upsertWorkflowVersion(name: string, graphHash: string, graph: any, source: string, status?: WorkflowVersionStatus): Promise<void>;
42
+ updateWorkflowVersionStatus(name: string, graphHash: string, status: WorkflowVersionStatus): Promise<void>;
42
43
  getWorkflowVersion(name: string, graphHash: string): Promise<{
43
44
  graph: any;
44
45
  source: string;
45
46
  } | null>;
47
+ getAIGeneratedWorkflows(agentName?: string): Promise<Array<{
48
+ workflowName: string;
49
+ graphHash: string;
50
+ graph: any;
51
+ }>>;
46
52
  close(): Promise<void>;
47
53
  }
@@ -85,6 +85,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
85
85
  .addColumn('graph_hash', 'text', (col) => col.notNull())
86
86
  .addColumn('graph', 'text', (col) => col.notNull())
87
87
  .addColumn('source', 'text', (col) => col.notNull())
88
+ .addColumn('status', 'text', (col) => col.notNull().defaultTo('active'))
88
89
  .addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
89
90
  .addPrimaryKeyConstraint('workflow_versions_pk', [
90
91
  'workflow_name',
@@ -352,28 +353,11 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
352
353
  updatedAt: new Date(row.updated_at),
353
354
  };
354
355
  }
355
- async withRunLock(id, fn) {
356
- return this.db.transaction().execute(async (trx) => {
357
- await trx
358
- .selectFrom('workflow_runs')
359
- .select('workflow_run_id')
360
- .where('workflow_run_id', '=', id)
361
- .forUpdate()
362
- .executeTakeFirst();
363
- return fn();
364
- });
356
+ async withRunLock(_id, fn) {
357
+ return fn();
365
358
  }
366
- async withStepLock(runId, stepName, fn) {
367
- return this.db.transaction().execute(async (trx) => {
368
- await trx
369
- .selectFrom('workflow_step')
370
- .select('workflow_step_id')
371
- .where('workflow_run_id', '=', runId)
372
- .where('step_name', '=', stepName)
373
- .forUpdate()
374
- .executeTakeFirst();
375
- return fn();
376
- });
359
+ async withStepLock(_runId, _stepName, fn) {
360
+ return fn();
377
361
  }
378
362
  async getCompletedGraphState(runId) {
379
363
  const results = await this.db
@@ -466,7 +450,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
466
450
  return {};
467
451
  return parseJson(row.state) ?? {};
468
452
  }
469
- async upsertWorkflowVersion(name, graphHash, graph, source) {
453
+ async upsertWorkflowVersion(name, graphHash, graph, source, status) {
470
454
  await this.db
471
455
  .insertInto('workflow_versions')
472
456
  .values({
@@ -474,12 +458,24 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
474
458
  graph_hash: graphHash,
475
459
  graph: JSON.stringify(graph),
476
460
  source,
461
+ status: status ?? 'active',
477
462
  })
478
463
  .onConflict((oc) => oc.columns(['workflow_name', 'graph_hash']).doNothing())
479
464
  .execute();
480
465
  }
466
+ async updateWorkflowVersionStatus(name, graphHash, status) {
467
+ await this.db
468
+ .updateTable('workflow_versions')
469
+ .set({ status })
470
+ .where('workflow_name', '=', name)
471
+ .where('graph_hash', '=', graphHash)
472
+ .execute();
473
+ }
481
474
  async getWorkflowVersion(name, graphHash) {
482
475
  return this.runService.getWorkflowVersion(name, graphHash);
483
476
  }
477
+ async getAIGeneratedWorkflows(agentName) {
478
+ return this.runService.getAIGeneratedWorkflows(agentName);
479
+ }
484
480
  async close() { }
485
481
  }
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/kysely-agent-run-service.ts","../src/kysely-ai-storage-service.ts","../src/kysely-channel-store.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-tables.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/pikku-kysely.ts","../bin/pikku-kysely-pure.ts"],"version":"5.9.3"}
1
+ {"root":["../src/index.ts","../src/kysely-agent-run-service.ts","../src/kysely-ai-storage-service.ts","../src/kysely-channel-store.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-secret-service.ts","../src/kysely-tables.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../bin/pikku-kysely-pure.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/kysely",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/src/index.js",
@@ -19,12 +19,10 @@
19
19
  "release": "npm run build && npm test"
20
20
  },
21
21
  "peerDependencies": {
22
- "@pikku/core": "^0.12.2"
22
+ "@pikku/core": "^0.12.3"
23
23
  },
24
24
  "dependencies": {
25
- "kysely": "^0.28.11",
26
- "kysely-postgres-js": "^3.0.0",
27
- "postgres": "^3.4.8"
25
+ "kysely": "^0.28.11"
28
26
  },
29
27
  "devDependencies": {
30
28
  "@types/better-sqlite3": "^7.6.13",
package/src/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- export { PikkuKysely } from './pikku-kysely.js'
2
-
3
1
  export { KyselyChannelStore } from './kysely-channel-store.js'
4
2
  export { KyselyEventHubStore } from './kysely-eventhub-store.js'
5
3
  export { KyselyWorkflowService } from './kysely-workflow-service.js'
@@ -7,6 +5,8 @@ export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js'
7
5
  export { KyselyDeploymentService } from './kysely-deployment-service.js'
8
6
  export { KyselyAIStorageService } from './kysely-ai-storage-service.js'
9
7
  export { KyselyAgentRunService } from './kysely-agent-run-service.js'
8
+ export { KyselySecretService } from './kysely-secret-service.js'
9
+ export type { KyselySecretServiceConfig } from './kysely-secret-service.js'
10
10
 
11
11
  export type { KyselyPikkuDB } from './kysely-tables.js'
12
12
  export type { WorkflowRunService } from '@pikku/core/workflow'