@pikku/kysely 0.12.2 → 0.12.4

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,33 @@
1
1
  ## 0.12.0
2
2
 
3
+ ## 0.12.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 3e79248: Add setStepChildRunId to workflow service implementations and auto-bootstrap in pikku all
8
+ - Updated dependencies [bb27710]
9
+ - Updated dependencies [a31bc63]
10
+ - Updated dependencies [3e79248]
11
+ - Updated dependencies [b0a81cc]
12
+ - Updated dependencies [6413df7]
13
+ - @pikku/core@0.12.6
14
+
15
+ ## 0.12.3
16
+
17
+ ### Patch Changes
18
+
19
+ - 32ed003: Add envelope encryption utilities and database-backed secret services with KEK rotation support
20
+ - 387b2ee: Add error_message column to agent run storage and queries
21
+ - b2b0af9: Migrate all consumers from @pikku/pg to @pikku/kysely and remove the @pikku/pg package
22
+ - c7ff141: Add WorkflowVersionStatus type with draft→active lifecycle for AI-generated workflows, type all DB status fields with proper unions instead of plain strings
23
+ - Updated dependencies [387b2ee]
24
+ - Updated dependencies [32ed003]
25
+ - Updated dependencies [7d369f3]
26
+ - Updated dependencies [508a796]
27
+ - Updated dependencies [ffe83af]
28
+ - Updated dependencies [c7ff141]
29
+ - @pikku/core@0.12.3
30
+
3
31
  ## 0.12.2
4
32
 
5
33
  ### Patch Changes
File without 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,9 +32,10 @@ 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;
38
+ child_run_id: string | null;
37
39
  branch_taken: string | null;
38
40
  retries: number | null;
39
41
  retry_delay: string | null;
@@ -43,7 +45,7 @@ export interface WorkflowStepTable {
43
45
  export interface WorkflowStepHistoryTable {
44
46
  history_id: Generated<string>;
45
47
  workflow_step_id: string;
46
- status: string;
48
+ status: StepStatus;
47
49
  result: string | null;
48
50
  error: string | null;
49
51
  created_at: Generated<Date>;
@@ -57,6 +59,7 @@ export interface WorkflowVersionsTable {
57
59
  graph_hash: string;
58
60
  graph: string;
59
61
  source: string;
62
+ status: Generated<WorkflowVersionStatus>;
60
63
  created_at: Generated<Date>;
61
64
  }
62
65
  export interface AIThreadsTable {
@@ -70,7 +73,7 @@ export interface AIThreadsTable {
70
73
  export interface AIMessageTable {
71
74
  id: string;
72
75
  thread_id: string;
73
- role: string;
76
+ role: 'system' | 'user' | 'assistant' | 'tool';
74
77
  content: string | null;
75
78
  created_at: Generated<Date>;
76
79
  }
@@ -82,8 +85,8 @@ export interface AIToolCallTable {
82
85
  tool_name: string;
83
86
  args: string;
84
87
  result: string | null;
85
- approval_status: string | null;
86
- approval_type: string | null;
88
+ approval_status: 'approved' | 'denied' | 'pending' | null;
89
+ approval_type: 'agent-call' | 'tool-call' | null;
87
90
  agent_run_id: string | null;
88
91
  display_tool_name: string | null;
89
92
  display_args: string | null;
@@ -100,8 +103,9 @@ export interface AIRunTable {
100
103
  agent_name: string;
101
104
  thread_id: string;
102
105
  resource_id: string;
103
- status: Generated<string>;
104
- suspend_reason: string | null;
106
+ status: Generated<'running' | 'suspended' | 'completed' | 'failed'>;
107
+ error_message: string | null;
108
+ suspend_reason: 'approval' | 'rpc-missing' | null;
105
109
  missing_rpcs: string | null;
106
110
  usage_input_tokens: Generated<number>;
107
111
  usage_output_tokens: Generated<number>;
@@ -119,6 +123,20 @@ export interface PikkuDeploymentFunctionsTable {
119
123
  deployment_id: string;
120
124
  function_name: string;
121
125
  }
126
+ export interface SecretsTable {
127
+ key: string;
128
+ ciphertext: string;
129
+ wrapped_dek: string;
130
+ key_version: number;
131
+ created_at: Generated<Date>;
132
+ updated_at: Generated<Date>;
133
+ }
134
+ export interface SecretsAuditTable {
135
+ id: string;
136
+ secret_key: string;
137
+ action: string;
138
+ performed_at: Generated<Date>;
139
+ }
122
140
  export interface KyselyPikkuDB {
123
141
  channels: ChannelsTable;
124
142
  channel_subscriptions: ChannelSubscriptionsTable;
@@ -133,4 +151,6 @@ export interface KyselyPikkuDB {
133
151
  ai_run: AIRunTable;
134
152
  pikku_deployments: PikkuDeploymentsTable;
135
153
  pikku_deployment_functions: PikkuDeploymentFunctionsTable;
154
+ secrets: SecretsTable;
155
+ secrets_audit: SecretsAuditTable;
136
156
  }
@@ -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>);
@@ -23,11 +23,12 @@ export declare class KyselyWorkflowService extends PikkuWorkflowService {
23
23
  setStepScheduled(stepId: string): Promise<void>;
24
24
  private insertHistoryRecord;
25
25
  private getTimestampFieldForStatus;
26
+ setStepChildRunId(stepId: string, childRunId: string): Promise<void>;
26
27
  setStepResult(stepId: string, result: any): Promise<void>;
27
28
  setStepError(stepId: string, error: Error): Promise<void>;
28
29
  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>;
30
+ withRunLock<T>(_id: string, fn: () => Promise<T>): Promise<T>;
31
+ withStepLock<T>(_runId: string, _stepName: string, fn: () => Promise<T>): Promise<T>;
31
32
  getCompletedGraphState(runId: string): Promise<{
32
33
  completedNodeIds: string[];
33
34
  failedNodeIds: string[];
@@ -38,10 +39,16 @@ export declare class KyselyWorkflowService extends PikkuWorkflowService {
38
39
  setBranchTaken(stepId: string, branchKey: string): Promise<void>;
39
40
  updateRunState(runId: string, name: string, value: unknown): Promise<void>;
40
41
  getRunState(runId: string): Promise<Record<string, unknown>>;
41
- upsertWorkflowVersion(name: string, graphHash: string, graph: any, source: string): Promise<void>;
42
+ upsertWorkflowVersion(name: string, graphHash: string, graph: any, source: string, status?: WorkflowVersionStatus): Promise<void>;
43
+ updateWorkflowVersionStatus(name: string, graphHash: string, status: WorkflowVersionStatus): Promise<void>;
42
44
  getWorkflowVersion(name: string, graphHash: string): Promise<{
43
45
  graph: any;
44
46
  source: string;
45
47
  } | null>;
48
+ getAIGeneratedWorkflows(agentName?: string): Promise<Array<{
49
+ workflowName: string;
50
+ graphHash: string;
51
+ graph: any;
52
+ }>>;
46
53
  close(): Promise<void>;
47
54
  }
@@ -49,6 +49,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
49
49
  .addColumn('status', 'text', (col) => col.notNull().defaultTo('pending'))
50
50
  .addColumn('result', 'text')
51
51
  .addColumn('error', 'text')
52
+ .addColumn('child_run_id', 'text')
52
53
  .addColumn('branch_taken', 'text')
53
54
  .addColumn('retries', 'integer')
54
55
  .addColumn('retry_delay', 'text')
@@ -85,6 +86,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
85
86
  .addColumn('graph_hash', 'text', (col) => col.notNull())
86
87
  .addColumn('graph', 'text', (col) => col.notNull())
87
88
  .addColumn('source', 'text', (col) => col.notNull())
89
+ .addColumn('status', 'text', (col) => col.notNull().defaultTo('active'))
88
90
  .addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
89
91
  .addPrimaryKeyConstraint('workflow_versions_pk', [
90
92
  'workflow_name',
@@ -255,6 +257,16 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
255
257
  return 'created_at';
256
258
  }
257
259
  }
260
+ async setStepChildRunId(stepId, childRunId) {
261
+ await this.db
262
+ .updateTable('workflow_step')
263
+ .set({
264
+ child_run_id: childRunId,
265
+ updated_at: new Date(),
266
+ })
267
+ .where('workflow_step_id', '=', stepId)
268
+ .execute();
269
+ }
258
270
  async setStepResult(stepId, result) {
259
271
  const resultJson = JSON.stringify(result);
260
272
  await this.db
@@ -352,28 +364,11 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
352
364
  updatedAt: new Date(row.updated_at),
353
365
  };
354
366
  }
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
- });
367
+ async withRunLock(_id, fn) {
368
+ return fn();
365
369
  }
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
- });
370
+ async withStepLock(_runId, _stepName, fn) {
371
+ return fn();
377
372
  }
378
373
  async getCompletedGraphState(runId) {
379
374
  const results = await this.db
@@ -466,7 +461,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
466
461
  return {};
467
462
  return parseJson(row.state) ?? {};
468
463
  }
469
- async upsertWorkflowVersion(name, graphHash, graph, source) {
464
+ async upsertWorkflowVersion(name, graphHash, graph, source, status) {
470
465
  await this.db
471
466
  .insertInto('workflow_versions')
472
467
  .values({
@@ -474,12 +469,24 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
474
469
  graph_hash: graphHash,
475
470
  graph: JSON.stringify(graph),
476
471
  source,
472
+ status: status ?? 'active',
477
473
  })
478
474
  .onConflict((oc) => oc.columns(['workflow_name', 'graph_hash']).doNothing())
479
475
  .execute();
480
476
  }
477
+ async updateWorkflowVersionStatus(name, graphHash, status) {
478
+ await this.db
479
+ .updateTable('workflow_versions')
480
+ .set({ status })
481
+ .where('workflow_name', '=', name)
482
+ .where('graph_hash', '=', graphHash)
483
+ .execute();
484
+ }
481
485
  async getWorkflowVersion(name, graphHash) {
482
486
  return this.runService.getWorkflowVersion(name, graphHash);
483
487
  }
488
+ async getAIGeneratedWorkflows(agentName) {
489
+ return this.runService.getAIGeneratedWorkflows(agentName);
490
+ }
484
491
  async close() { }
485
492
  }
@@ -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"}