@inf-minds/jobs 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact-cleanup.d.ts +30 -0
- package/dist/artifact-cleanup.d.ts.map +1 -0
- package/dist/artifact-cleanup.js +44 -0
- package/dist/artifact-cleanup.js.map +1 -0
- package/dist/artifact-manager.d.ts +41 -0
- package/dist/artifact-manager.d.ts.map +1 -0
- package/dist/artifact-manager.js +254 -0
- package/dist/artifact-manager.js.map +1 -0
- package/dist/artifact-materializer.d.ts +21 -0
- package/dist/artifact-materializer.d.ts.map +1 -0
- package/dist/artifact-materializer.js +33 -0
- package/dist/artifact-materializer.js.map +1 -0
- package/dist/artifact-router.d.ts +131 -0
- package/dist/artifact-router.d.ts.map +1 -0
- package/dist/artifact-router.js +4 -0
- package/dist/artifact-router.js.map +1 -0
- package/dist/default-artifact-router.d.ts +26 -0
- package/dist/default-artifact-router.d.ts.map +1 -0
- package/dist/default-artifact-router.js +125 -0
- package/dist/default-artifact-router.js.map +1 -0
- package/dist/event-appender.d.ts +23 -0
- package/dist/event-appender.d.ts.map +1 -0
- package/dist/event-appender.js +120 -0
- package/dist/event-appender.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/job-context.d.ts +58 -0
- package/dist/job-context.d.ts.map +1 -0
- package/dist/job-context.js +133 -0
- package/dist/job-context.js.map +1 -0
- package/dist/job-manager.d.ts +23 -0
- package/dist/job-manager.d.ts.map +1 -0
- package/dist/job-manager.js +145 -0
- package/dist/job-manager.js.map +1 -0
- package/dist/job-runner.d.ts +32 -0
- package/dist/job-runner.d.ts.map +1 -0
- package/dist/job-runner.js +187 -0
- package/dist/job-runner.js.map +1 -0
- package/dist/job-worker.d.ts +79 -0
- package/dist/job-worker.d.ts.map +1 -0
- package/dist/job-worker.js +134 -0
- package/dist/job-worker.js.map +1 -0
- package/dist/mind-worker.d.ts +63 -0
- package/dist/mind-worker.d.ts.map +1 -0
- package/dist/mind-worker.js +99 -0
- package/dist/mind-worker.js.map +1 -0
- package/dist/schema.d.ts +1143 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +225 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +434 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/drizzle/0001_create_jobs_tables.sql +70 -0
- package/drizzle/0002_coordinator_tables.sql +78 -0
- package/drizzle/0003_artifacts.sql +24 -0
- package/drizzle/0004_kernel.sql +28 -0
- package/drizzle/0005_artifact_routing.sql +29 -0
- package/package.json +48 -0
- package/src/artifact-cleanup.ts +85 -0
- package/src/artifact-manager.ts +346 -0
- package/src/artifact-materializer.ts +64 -0
- package/src/artifact-router.ts +151 -0
- package/src/default-artifact-router.ts +186 -0
- package/src/event-appender.ts +158 -0
- package/src/index.ts +136 -0
- package/src/job-context.ts +195 -0
- package/src/job-manager.ts +179 -0
- package/src/job-runner.ts +260 -0
- package/src/job-worker.ts +252 -0
- package/src/mind-worker.ts +152 -0
- package/src/schema.ts +290 -0
- package/src/types.ts +542 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// ABOUTME: ArtifactManager for job artifact CRUD operations
|
|
2
|
+
// ABOUTME: Routes artifacts to appropriate storage backend
|
|
3
|
+
|
|
4
|
+
import type { StorageProvider } from '@inf-minds/storage';
|
|
5
|
+
import { detectMimeType, getStorageType } from '@inf-minds/storage';
|
|
6
|
+
import type { ArtifactRef, ArtifactContent, SaveArtifactOptions } from './types.js';
|
|
7
|
+
import { jobArtifacts, type JobArtifact, type NewJobArtifact } from './schema.js';
|
|
8
|
+
import { and, eq, sql } from 'drizzle-orm';
|
|
9
|
+
|
|
10
|
+
export interface ArtifactManagerDb {
|
|
11
|
+
insert(table: unknown): {
|
|
12
|
+
values(record: unknown): {
|
|
13
|
+
returning(): Promise<unknown[]>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
select(): {
|
|
17
|
+
from(table: unknown): {
|
|
18
|
+
where(condition: unknown): Promise<unknown[]>;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
delete(table: unknown): {
|
|
22
|
+
where(condition: unknown): Promise<unknown>;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ArtifactManagerOptions {
|
|
27
|
+
db: ArtifactManagerDb;
|
|
28
|
+
postgresStorage: StorageProvider;
|
|
29
|
+
externalStorage: StorageProvider;
|
|
30
|
+
table?: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ArtifactManager {
|
|
34
|
+
save(jobId: string, name: string, content: string | Buffer, options: SaveArtifactOptions): Promise<ArtifactRef>;
|
|
35
|
+
load(jobId: string, name: string): Promise<ArtifactContent | null>;
|
|
36
|
+
list(jobId: string): Promise<ArtifactRef[]>;
|
|
37
|
+
getSignedUrl(jobId: string, name: string, expiresIn?: number): Promise<string | null>;
|
|
38
|
+
delete(jobId: string, name: string): Promise<void>;
|
|
39
|
+
deleteAll(jobId: string): Promise<void>;
|
|
40
|
+
|
|
41
|
+
// Session/visibility queries for kernel orchestration
|
|
42
|
+
/** List all artifacts for a kernel session */
|
|
43
|
+
listBySession(sessionId: string): Promise<ArtifactRef[]>;
|
|
44
|
+
/** List artifacts for a specific node in a session */
|
|
45
|
+
listByNode(sessionId: string, nodeId: string): Promise<ArtifactRef[]>;
|
|
46
|
+
/** List artifacts by visibility scope in a session */
|
|
47
|
+
listByVisibility(sessionId: string, visibility: string): Promise<ArtifactRef[]>;
|
|
48
|
+
/** List artifacts published to specific channels in a session */
|
|
49
|
+
listByChannels(sessionId: string, channels: string[]): Promise<ArtifactRef[]>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createArtifactManager(options: ArtifactManagerOptions): ArtifactManager {
|
|
53
|
+
const { db, postgresStorage, externalStorage } = options;
|
|
54
|
+
const table = options.table ?? jobArtifacts;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
async save(jobId: string, name: string, content: string | Buffer, options: SaveArtifactOptions): Promise<ArtifactRef> {
|
|
58
|
+
// Determine MIME type
|
|
59
|
+
const mimeType = options.mimeType ?? detectMimeType(name);
|
|
60
|
+
|
|
61
|
+
// Determine storage type based on MIME type
|
|
62
|
+
const storageType = getStorageType(mimeType);
|
|
63
|
+
|
|
64
|
+
// Select appropriate storage backend
|
|
65
|
+
const storage = storageType === 'postgres' ? postgresStorage : externalStorage;
|
|
66
|
+
|
|
67
|
+
// Build storage key
|
|
68
|
+
const storageKey = storageType === 'postgres' ? name : `${jobId}/${name}`;
|
|
69
|
+
|
|
70
|
+
// Store content in appropriate backend
|
|
71
|
+
const putResult = await storage.put(storageKey, content, {
|
|
72
|
+
mimeType,
|
|
73
|
+
metadata: options.metadata,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Calculate expiry timestamp
|
|
77
|
+
let expiresAt: Date | null = null;
|
|
78
|
+
if (options.expiresIn) {
|
|
79
|
+
expiresAt = new Date(Date.now() + options.expiresIn * 1000);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build artifact record
|
|
83
|
+
const artifactRecord: NewJobArtifact = {
|
|
84
|
+
jobId,
|
|
85
|
+
name,
|
|
86
|
+
mimeType,
|
|
87
|
+
storageType,
|
|
88
|
+
content: storageType === 'postgres' ? (typeof content === 'string' ? { text: content } : content) : null,
|
|
89
|
+
externalKey: storageType === 'external' ? putResult.key : null,
|
|
90
|
+
sizeBytes: putResult.sizeBytes,
|
|
91
|
+
metadata: options.metadata ?? {},
|
|
92
|
+
expiresAt,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Insert into database
|
|
96
|
+
const [inserted] = await db
|
|
97
|
+
.insert(table)
|
|
98
|
+
.values(artifactRecord)
|
|
99
|
+
.returning();
|
|
100
|
+
|
|
101
|
+
if (!inserted) {
|
|
102
|
+
throw new Error('Failed to insert artifact record');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const artifact = inserted as JobArtifact;
|
|
106
|
+
|
|
107
|
+
// Return artifact reference
|
|
108
|
+
return {
|
|
109
|
+
name: artifact.name,
|
|
110
|
+
mimeType: artifact.mimeType,
|
|
111
|
+
sizeBytes: artifact.sizeBytes,
|
|
112
|
+
storageType: artifact.storageType as 'postgres' | 'external',
|
|
113
|
+
metadata: (artifact.metadata as Record<string, unknown>) ?? {},
|
|
114
|
+
createdAt: artifact.createdAt,
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
async load(jobId: string, name: string): Promise<ArtifactContent | null> {
|
|
119
|
+
// Query database for artifact record
|
|
120
|
+
const results = await db
|
|
121
|
+
.select()
|
|
122
|
+
.from(table)
|
|
123
|
+
.where(and(eq(jobArtifacts.jobId, jobId), eq(jobArtifacts.name, name)));
|
|
124
|
+
|
|
125
|
+
if (results.length === 0) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const artifact = results[0] as JobArtifact;
|
|
130
|
+
|
|
131
|
+
// Fetch content from appropriate storage
|
|
132
|
+
let content: string | Buffer;
|
|
133
|
+
|
|
134
|
+
if (artifact.storageType === 'postgres') {
|
|
135
|
+
const storageResult = await postgresStorage.get(artifact.name);
|
|
136
|
+
if (!storageResult) {
|
|
137
|
+
throw new Error(`Artifact content not found in postgres storage: ${artifact.name}`);
|
|
138
|
+
}
|
|
139
|
+
content = storageResult.content;
|
|
140
|
+
} else {
|
|
141
|
+
if (!artifact.externalKey) {
|
|
142
|
+
throw new Error(`External key missing for artifact: ${artifact.name}`);
|
|
143
|
+
}
|
|
144
|
+
const storageResult = await externalStorage.get(artifact.externalKey);
|
|
145
|
+
if (!storageResult) {
|
|
146
|
+
throw new Error(`Artifact content not found in external storage: ${artifact.externalKey}`);
|
|
147
|
+
}
|
|
148
|
+
content = storageResult.content;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Return artifact with content
|
|
152
|
+
return {
|
|
153
|
+
name: artifact.name,
|
|
154
|
+
mimeType: artifact.mimeType,
|
|
155
|
+
sizeBytes: artifact.sizeBytes,
|
|
156
|
+
storageType: artifact.storageType as 'postgres' | 'external',
|
|
157
|
+
metadata: (artifact.metadata as Record<string, unknown>) ?? {},
|
|
158
|
+
createdAt: artifact.createdAt,
|
|
159
|
+
content,
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
async list(jobId: string): Promise<ArtifactRef[]> {
|
|
164
|
+
// Query database for all artifacts for this job
|
|
165
|
+
const results = await db
|
|
166
|
+
.select()
|
|
167
|
+
.from(table)
|
|
168
|
+
.where(eq(jobArtifacts.jobId, jobId));
|
|
169
|
+
|
|
170
|
+
// Map to artifact references
|
|
171
|
+
return results.map((artifact) => {
|
|
172
|
+
const a = artifact as JobArtifact;
|
|
173
|
+
return {
|
|
174
|
+
name: a.name,
|
|
175
|
+
mimeType: a.mimeType,
|
|
176
|
+
sizeBytes: a.sizeBytes,
|
|
177
|
+
storageType: a.storageType as 'postgres' | 'external',
|
|
178
|
+
metadata: (a.metadata as Record<string, unknown>) ?? {},
|
|
179
|
+
createdAt: a.createdAt,
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
async getSignedUrl(jobId: string, name: string, expiresIn?: number): Promise<string | null> {
|
|
185
|
+
// Query database for artifact record
|
|
186
|
+
const results = await db
|
|
187
|
+
.select()
|
|
188
|
+
.from(table)
|
|
189
|
+
.where(and(eq(jobArtifacts.jobId, jobId), eq(jobArtifacts.name, name)));
|
|
190
|
+
|
|
191
|
+
if (results.length === 0) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const artifact = results[0] as JobArtifact;
|
|
196
|
+
|
|
197
|
+
// Only external artifacts can have signed URLs
|
|
198
|
+
if (artifact.storageType !== 'external' || !artifact.externalKey) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Get signed URL from external storage
|
|
203
|
+
return await externalStorage.getSignedUrl(artifact.externalKey, expiresIn);
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
async delete(jobId: string, name: string): Promise<void> {
|
|
207
|
+
// Query database for artifact record
|
|
208
|
+
const results = await db
|
|
209
|
+
.select()
|
|
210
|
+
.from(table)
|
|
211
|
+
.where(and(eq(jobArtifacts.jobId, jobId), eq(jobArtifacts.name, name)));
|
|
212
|
+
|
|
213
|
+
if (results.length === 0) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const artifact = results[0] as JobArtifact;
|
|
218
|
+
|
|
219
|
+
// Delete from appropriate storage
|
|
220
|
+
if (artifact.storageType === 'postgres') {
|
|
221
|
+
await postgresStorage.delete(artifact.name);
|
|
222
|
+
} else if (artifact.externalKey) {
|
|
223
|
+
await externalStorage.delete(artifact.externalKey);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Delete database record
|
|
227
|
+
await db
|
|
228
|
+
.delete(table)
|
|
229
|
+
.where(and(eq(jobArtifacts.jobId, jobId), eq(jobArtifacts.name, name)));
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
async deleteAll(jobId: string): Promise<void> {
|
|
233
|
+
// Get all artifacts for this job
|
|
234
|
+
const artifacts = await db
|
|
235
|
+
.select()
|
|
236
|
+
.from(table)
|
|
237
|
+
.where(eq(jobArtifacts.jobId, jobId));
|
|
238
|
+
|
|
239
|
+
// Delete from storage backends
|
|
240
|
+
for (const artifact of artifacts) {
|
|
241
|
+
const a = artifact as JobArtifact;
|
|
242
|
+
if (a.storageType === 'postgres') {
|
|
243
|
+
await postgresStorage.delete(a.name);
|
|
244
|
+
} else if (a.externalKey) {
|
|
245
|
+
await externalStorage.delete(a.externalKey);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Delete all database records
|
|
250
|
+
await db
|
|
251
|
+
.delete(table)
|
|
252
|
+
.where(eq(jobArtifacts.jobId, jobId));
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// Session/visibility query implementations
|
|
256
|
+
|
|
257
|
+
async listBySession(sessionId: string): Promise<ArtifactRef[]> {
|
|
258
|
+
const results = await db
|
|
259
|
+
.select()
|
|
260
|
+
.from(table)
|
|
261
|
+
.where(eq(jobArtifacts.sessionId, sessionId));
|
|
262
|
+
|
|
263
|
+
return results.map((artifact) => {
|
|
264
|
+
const a = artifact as JobArtifact;
|
|
265
|
+
return {
|
|
266
|
+
name: a.name,
|
|
267
|
+
mimeType: a.mimeType,
|
|
268
|
+
sizeBytes: a.sizeBytes,
|
|
269
|
+
storageType: a.storageType as 'postgres' | 'external',
|
|
270
|
+
metadata: (a.metadata as Record<string, unknown>) ?? {},
|
|
271
|
+
createdAt: a.createdAt,
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
async listByNode(sessionId: string, nodeId: string): Promise<ArtifactRef[]> {
|
|
277
|
+
const results = await db
|
|
278
|
+
.select()
|
|
279
|
+
.from(table)
|
|
280
|
+
.where(and(eq(jobArtifacts.sessionId, sessionId), eq(jobArtifacts.nodeId, nodeId)));
|
|
281
|
+
|
|
282
|
+
return results.map((artifact) => {
|
|
283
|
+
const a = artifact as JobArtifact;
|
|
284
|
+
return {
|
|
285
|
+
name: a.name,
|
|
286
|
+
mimeType: a.mimeType,
|
|
287
|
+
sizeBytes: a.sizeBytes,
|
|
288
|
+
storageType: a.storageType as 'postgres' | 'external',
|
|
289
|
+
metadata: (a.metadata as Record<string, unknown>) ?? {},
|
|
290
|
+
createdAt: a.createdAt,
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
async listByVisibility(sessionId: string, visibility: string): Promise<ArtifactRef[]> {
|
|
296
|
+
const results = await db
|
|
297
|
+
.select()
|
|
298
|
+
.from(table)
|
|
299
|
+
.where(and(eq(jobArtifacts.sessionId, sessionId), eq(jobArtifacts.visibility, visibility)));
|
|
300
|
+
|
|
301
|
+
return results.map((artifact) => {
|
|
302
|
+
const a = artifact as JobArtifact;
|
|
303
|
+
return {
|
|
304
|
+
name: a.name,
|
|
305
|
+
mimeType: a.mimeType,
|
|
306
|
+
sizeBytes: a.sizeBytes,
|
|
307
|
+
storageType: a.storageType as 'postgres' | 'external',
|
|
308
|
+
metadata: (a.metadata as Record<string, unknown>) ?? {},
|
|
309
|
+
createdAt: a.createdAt,
|
|
310
|
+
};
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
async listByChannels(sessionId: string, channels: string[]): Promise<ArtifactRef[]> {
|
|
315
|
+
if (channels.length === 0) {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Query for artifacts where channels array overlaps with requested channels
|
|
320
|
+
const results = await db
|
|
321
|
+
.select()
|
|
322
|
+
.from(table)
|
|
323
|
+
.where(
|
|
324
|
+
and(
|
|
325
|
+
eq(jobArtifacts.sessionId, sessionId),
|
|
326
|
+
sql`${jobArtifacts.channels} && ARRAY[${sql.join(
|
|
327
|
+
channels.map((c) => sql`${c}`),
|
|
328
|
+
sql`, `
|
|
329
|
+
)}]::text[]`
|
|
330
|
+
)
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return results.map((artifact) => {
|
|
334
|
+
const a = artifact as JobArtifact;
|
|
335
|
+
return {
|
|
336
|
+
name: a.name,
|
|
337
|
+
mimeType: a.mimeType,
|
|
338
|
+
sizeBytes: a.sizeBytes,
|
|
339
|
+
storageType: a.storageType as 'postgres' | 'external',
|
|
340
|
+
metadata: (a.metadata as Record<string, unknown>) ?? {},
|
|
341
|
+
createdAt: a.createdAt,
|
|
342
|
+
};
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// ABOUTME: Materializes artifacts from job dependencies
|
|
2
|
+
// ABOUTME: Makes parent job artifacts available via deps/{jobId}/{name} paths
|
|
3
|
+
|
|
4
|
+
import type { ArtifactManager } from './artifact-manager.js';
|
|
5
|
+
import type { ArtifactContent } from './types.js';
|
|
6
|
+
import type { MaterializedArtifacts } from './job-context.js';
|
|
7
|
+
|
|
8
|
+
export interface ArtifactMaterializerDb {
|
|
9
|
+
select(): {
|
|
10
|
+
from(table: unknown): {
|
|
11
|
+
where(condition: unknown): Promise<{ dependsOnJobId: string }[]>;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ArtifactMaterializerOptions {
|
|
17
|
+
db: ArtifactMaterializerDb;
|
|
18
|
+
artifactManager: Pick<ArtifactManager, 'list' | 'load'>;
|
|
19
|
+
dependenciesTable?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ArtifactMaterializer {
|
|
23
|
+
materialize(jobId: string): Promise<MaterializedArtifacts>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createArtifactMaterializer(
|
|
27
|
+
options: ArtifactMaterializerOptions
|
|
28
|
+
): ArtifactMaterializer {
|
|
29
|
+
const { db, artifactManager } = options;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
async materialize(jobId: string): Promise<MaterializedArtifacts> {
|
|
33
|
+
const artifacts = new Map<string, ArtifactContent>();
|
|
34
|
+
|
|
35
|
+
// Get job dependencies
|
|
36
|
+
const dependencies = await (db.select as any)()
|
|
37
|
+
.from(null)
|
|
38
|
+
.where({ jobId });
|
|
39
|
+
|
|
40
|
+
if (dependencies.length === 0) {
|
|
41
|
+
return { artifacts };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Load artifacts from each dependency
|
|
45
|
+
for (const dep of dependencies) {
|
|
46
|
+
const parentJobId = dep.dependsOnJobId;
|
|
47
|
+
|
|
48
|
+
// List artifacts for parent job
|
|
49
|
+
const parentArtifacts = await artifactManager.list(parentJobId);
|
|
50
|
+
|
|
51
|
+
// Load each artifact
|
|
52
|
+
for (const ref of parentArtifacts) {
|
|
53
|
+
const content = await artifactManager.load(parentJobId, ref.name);
|
|
54
|
+
if (content) {
|
|
55
|
+
const path = `deps/${parentJobId}/${ref.name}`;
|
|
56
|
+
artifacts.set(path, content);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { artifacts };
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// ABOUTME: ArtifactRouter interface for kernel artifact visibility and routing
|
|
2
|
+
// ABOUTME: Determines which artifacts nodes can access and how outputs are routed
|
|
3
|
+
|
|
4
|
+
import type { ArtifactRef, GraphState } from './types.js';
|
|
5
|
+
import type { ArtifactVisibility } from './schema.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Graph edge definition (simplified for routing context).
|
|
9
|
+
*/
|
|
10
|
+
export interface GraphEdge {
|
|
11
|
+
/** Source node ID */
|
|
12
|
+
source: string;
|
|
13
|
+
/** Target node ID */
|
|
14
|
+
target: string;
|
|
15
|
+
/** Named channel for artifact routing (optional) */
|
|
16
|
+
channel?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Context provided for artifact routing decisions.
|
|
21
|
+
*/
|
|
22
|
+
export interface RoutingContext {
|
|
23
|
+
/** Kernel session ID (the parent job ID) */
|
|
24
|
+
sessionId: string;
|
|
25
|
+
/** Current node ID in the graph */
|
|
26
|
+
nodeId: string;
|
|
27
|
+
/** Mind ID being executed (e.g., 'researcher', 'coordinator') */
|
|
28
|
+
mindId: string;
|
|
29
|
+
/** The incoming edge that triggered this node (optional for start nodes) */
|
|
30
|
+
edge?: GraphEdge;
|
|
31
|
+
/** Current graph execution state */
|
|
32
|
+
graphState: GraphState;
|
|
33
|
+
/** Account ID for the session */
|
|
34
|
+
accountId: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hints provided by minds when saving artifacts.
|
|
39
|
+
*/
|
|
40
|
+
export interface VisibilityHints {
|
|
41
|
+
/** Explicit visibility scope override */
|
|
42
|
+
scope?: ArtifactVisibility;
|
|
43
|
+
/** Specific recipient node IDs */
|
|
44
|
+
recipients?: string[];
|
|
45
|
+
/** Named channels to publish to */
|
|
46
|
+
channels?: string[];
|
|
47
|
+
/** Whether this artifact should persist beyond session */
|
|
48
|
+
persistent?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Artifact with routing metadata.
|
|
53
|
+
*/
|
|
54
|
+
export interface RoutedArtifact extends ArtifactRef {
|
|
55
|
+
/** Resolved visibility scope */
|
|
56
|
+
visibility: ArtifactVisibility;
|
|
57
|
+
/** Session this artifact belongs to */
|
|
58
|
+
sessionId: string;
|
|
59
|
+
/** Node that created this artifact */
|
|
60
|
+
nodeId: string;
|
|
61
|
+
/** Channels this artifact is published to */
|
|
62
|
+
channels: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Options for resolving input artifacts.
|
|
67
|
+
*/
|
|
68
|
+
export interface ResolveInputOptions {
|
|
69
|
+
/** Filter by visibility scopes */
|
|
70
|
+
visibilities?: ArtifactVisibility[];
|
|
71
|
+
/** Filter by specific channels */
|
|
72
|
+
channels?: string[];
|
|
73
|
+
/** Filter by source node IDs */
|
|
74
|
+
sourceNodes?: string[];
|
|
75
|
+
/** Include artifacts from all completed predecessor nodes */
|
|
76
|
+
includePredecessors?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Options for routing output artifacts.
|
|
81
|
+
*/
|
|
82
|
+
export interface RouteOutputOptions {
|
|
83
|
+
/** Artifact reference to route */
|
|
84
|
+
artifact: ArtifactRef;
|
|
85
|
+
/** Visibility hints from the mind */
|
|
86
|
+
hints: VisibilityHints;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Interface for artifact routing in kernel sessions.
|
|
91
|
+
*
|
|
92
|
+
* The router is responsible for:
|
|
93
|
+
* 1. Determining which artifacts a node can access as inputs
|
|
94
|
+
* 2. Routing output artifacts with appropriate visibility
|
|
95
|
+
*
|
|
96
|
+
* Implementation is decoupled from storage - the router only
|
|
97
|
+
* handles visibility and routing logic, not persistence.
|
|
98
|
+
*/
|
|
99
|
+
export interface ArtifactRouter {
|
|
100
|
+
/**
|
|
101
|
+
* Resolve which artifacts are available as inputs for a node.
|
|
102
|
+
*
|
|
103
|
+
* This considers:
|
|
104
|
+
* - Artifacts from predecessor nodes (via edges)
|
|
105
|
+
* - Session-visible artifacts
|
|
106
|
+
* - Channel subscriptions
|
|
107
|
+
*
|
|
108
|
+
* @param context - Current routing context
|
|
109
|
+
* @param options - Optional filtering options
|
|
110
|
+
* @returns List of accessible artifact references
|
|
111
|
+
*/
|
|
112
|
+
resolveInputArtifacts(
|
|
113
|
+
context: RoutingContext,
|
|
114
|
+
options?: ResolveInputOptions
|
|
115
|
+
): Promise<ArtifactRef[]>;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Route output artifacts with visibility and channel assignments.
|
|
119
|
+
*
|
|
120
|
+
* This determines:
|
|
121
|
+
* - Final visibility scope (from hints or defaults)
|
|
122
|
+
* - Channel assignments
|
|
123
|
+
* - Session and node attribution
|
|
124
|
+
*
|
|
125
|
+
* @param outputs - Artifacts with visibility hints
|
|
126
|
+
* @param context - Current routing context
|
|
127
|
+
* @returns Routed artifacts with resolved metadata
|
|
128
|
+
*/
|
|
129
|
+
routeOutputArtifacts(
|
|
130
|
+
outputs: RouteOutputOptions[],
|
|
131
|
+
context: RoutingContext
|
|
132
|
+
): Promise<RoutedArtifact[]>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get default visibility for a given artifact path.
|
|
136
|
+
*
|
|
137
|
+
* Visibility can be inferred from path patterns:
|
|
138
|
+
* - `/sessions/{s}/nodes/{n}/private/**` → private
|
|
139
|
+
* - `/sessions/{s}/nodes/{n}/outputs/**` → session
|
|
140
|
+
* - `/sessions/{s}/shared/**` → session
|
|
141
|
+
* - `/persistent/**` → persistent
|
|
142
|
+
*
|
|
143
|
+
* @param path - Artifact path
|
|
144
|
+
* @param context - Routing context
|
|
145
|
+
* @returns Default visibility scope
|
|
146
|
+
*/
|
|
147
|
+
getDefaultVisibility(
|
|
148
|
+
path: string,
|
|
149
|
+
context: RoutingContext
|
|
150
|
+
): ArtifactVisibility;
|
|
151
|
+
}
|