@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 +16 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/kysely-agent-run-service.js +2 -0
- package/dist/src/kysely-ai-storage-service.js +14 -1
- package/dist/src/kysely-deployment-service.d.ts +2 -1
- package/dist/src/kysely-deployment-service.js +18 -6
- package/dist/src/kysely-secret-service.d.ts +29 -0
- package/dist/src/kysely-secret-service.js +142 -0
- package/dist/src/kysely-tables.d.ts +27 -8
- package/dist/src/kysely-workflow-run-service.d.ts +5 -0
- package/dist/src/kysely-workflow-run-service.js +16 -0
- package/dist/src/kysely-workflow-service.d.ts +11 -5
- package/dist/src/kysely-workflow-service.js +18 -22
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -5
- package/src/index.ts +2 -2
- package/src/kysely-agent-run-service.ts +2 -0
- package/src/kysely-ai-storage-service.ts +12 -1
- package/src/kysely-deployment-service.ts +28 -13
- package/src/kysely-secret-service.ts +197 -0
- package/src/kysely-services.test.ts +89 -698
- package/src/kysely-tables.ts +33 -8
- package/src/kysely-workflow-run-service.ts +20 -1
- package/src/kysely-workflow-service.ts +30 -24
- package/dist/src/pikku-kysely.d.ts +0 -13
- package/dist/src/pikku-kysely.js +0 -58
- package/src/pikku-kysely.ts +0 -67
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
|
package/dist/src/index.d.ts
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,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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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<
|
|
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:
|
|
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:
|
|
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:
|
|
86
|
-
approval_type:
|
|
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<
|
|
104
|
-
|
|
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
|
-
|
|
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>(
|
|
30
|
-
withStepLock<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(
|
|
356
|
-
return
|
|
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(
|
|
367
|
-
return
|
|
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-
|
|
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.
|
|
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.
|
|
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'
|