@hasna/knowledge 0.2.11 → 0.2.12

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/src/cli.ts CHANGED
@@ -60,7 +60,7 @@ interface ParseResult {
60
60
  flags: Flags;
61
61
  }
62
62
 
63
- const COMMANDS = ['add', 'list', 'get', 'delete', 'update', 'archive', 'restore', 'upsert', 'untag', 'export', 'prune', 'dedupe', 'stats', 'paths', 'db', 'wiki', 'source', 'ingest', 'reindex', 'providers', 'safety', 'help'];
63
+ const COMMANDS = ['add', 'list', 'get', 'delete', 'update', 'archive', 'restore', 'upsert', 'untag', 'export', 'prune', 'dedupe', 'stats', 'paths', 'storage', 'db', 'wiki', 'source', 'ingest', 'reindex', 'providers', 'safety', 'help'];
64
64
  const COMMAND_ALIASES: Record<string, string> = {
65
65
  ls: 'list',
66
66
  rm: 'delete',
@@ -162,6 +162,7 @@ Commands:
162
162
  dedupe Remove duplicate items by title+content (requires --yes)
163
163
  stats Show knowledge base statistics
164
164
  paths Show resolved workspace/store paths
165
+ storage status|validate Inspect local/S3 artifact storage contract
165
166
  db init|stats Initialize or inspect local knowledge.db
166
167
  wiki init Initialize scalable wiki/schema/index/log artifacts
167
168
  source resolve <source-ref> Resolve read-only source content and citation evidence
@@ -230,6 +231,7 @@ function printCommandHelp(command: string): void {
230
231
  if (command === 'dedupe') { console.log('Usage: open-knowledge dedupe --yes [--json]'); return; }
231
232
  if (command === 'stats') { console.log('Usage: open-knowledge stats [--json]'); return; }
232
233
  if (command === 'paths') { console.log('Usage: open-knowledge paths [--scope local|global|project] [--json]'); return; }
234
+ if (command === 'storage') { console.log('Usage: open-knowledge storage status|validate [--scope local|global|project] [--json]'); return; }
233
235
  if (command === 'db') { console.log('Usage: open-knowledge db init|stats [--scope local|global|project] [--json]'); return; }
234
236
  if (command === 'wiki') { console.log('Usage: open-knowledge wiki init [--scope local|global|project] [--json]'); return; }
235
237
  if (command === 'source') { console.log('Usage: open-knowledge source resolve <source-ref> [--purpose knowledge_answer|knowledge_index] [--limit <n>] [--scope local|global|project] [--json]'); return; }
@@ -281,11 +283,11 @@ async function run(argv: string[]): Promise<void> {
281
283
  if (flags.completions) {
282
284
  const shell = flags.completions;
283
285
  if (shell === 'bash') {
284
- console.log(`_open_knowledge() { local cur; cur="${"$"}{COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update archive restore upsert untag delete export prune dedupe stats paths db wiki source ingest reindex providers safety help ls rm edit unarchive --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --purpose --no-color --scope --archived --include-archived" -- "$cur")); }; complete -F _open_knowledge open-knowledge`);
286
+ console.log(`_open_knowledge() { local cur; cur="${"$"}{COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update archive restore upsert untag delete export prune dedupe stats paths storage db wiki source ingest reindex providers safety help ls rm edit unarchive --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --purpose --no-color --scope --archived --include-archived" -- "$cur")); }; complete -F _open_knowledge open-knowledge`);
285
287
  } else if (shell === 'zsh') {
286
- console.log(`#compdef open-knowledge\n_open_knowledge() { _arguments -C "1: :(add list get update archive restore upsert untag delete export prune dedupe stats paths db wiki source ingest reindex providers safety help ls rm edit unarchive)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(--archived)--archived" "(--include-archived)--include-archived" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"\{created,title\}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--purpose)--purpose[purpose]:" "(--no-color)--no-color[disable color]" "(--scope)--scope"\{local,global,project\}:" }; _open_knowledge`);
288
+ console.log(`#compdef open-knowledge\n_open_knowledge() { _arguments -C "1: :(add list get update archive restore upsert untag delete export prune dedupe stats paths storage db wiki source ingest reindex providers safety help ls rm edit unarchive)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(--archived)--archived" "(--include-archived)--include-archived" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"\{created,title\}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--purpose)--purpose[purpose]:" "(--no-color)--no-color[disable color]" "(--scope)--scope"\{local,global,project\}:" }; _open_knowledge`);
287
289
  } else if (shell === 'fish') {
288
- console.log(`complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update archive restore upsert untag delete export prune dedupe stats paths db wiki source ingest reindex providers safety help ls rm edit unarchive"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -l archived; complete -c open-knowledge -l include-archived; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l purpose; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"`);
290
+ console.log(`complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update archive restore upsert untag delete export prune dedupe stats paths storage db wiki source ingest reindex providers safety help ls rm edit unarchive"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -l archived; complete -c open-knowledge -l include-archived; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l purpose; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"`);
289
291
  } else {
290
292
  throw new Error("Invalid --completions value. Use 'bash', 'zsh', or 'fish'.");
291
293
  }
@@ -311,6 +313,31 @@ async function run(argv: string[]): Promise<void> {
311
313
  return;
312
314
  }
313
315
 
316
+ if (command === 'storage') {
317
+ const action = positional[1] ?? 'status';
318
+ if (action === 'status') {
319
+ const contract = service.storageContract();
320
+ const validation = service.validateStorage();
321
+ output({
322
+ ok: validation.ok,
323
+ ...contract,
324
+ validation,
325
+ message: `${contract.storage_type} artifact storage at ${contract.artifact_store.uri_prefix}`,
326
+ }, flags.json);
327
+ return;
328
+ }
329
+ if (action === 'validate') {
330
+ const validation = service.validateStorage();
331
+ output({
332
+ ok: validation.ok,
333
+ validation,
334
+ message: validation.ok ? 'Storage contract valid' : `Storage contract invalid: ${validation.errors.join('; ')}`,
335
+ }, flags.json);
336
+ return;
337
+ }
338
+ throw new Error("Invalid storage action. Use 'status' or 'validate'.");
339
+ }
340
+
314
341
  if (command === 'db') {
315
342
  const action = positional[1] ?? 'init';
316
343
  if (action !== 'init' && action !== 'stats') {
@@ -16,6 +16,7 @@ export interface KnowledgeDbStats {
16
16
  redaction_findings: number;
17
17
  audit_events: number;
18
18
  approval_gates: number;
19
+ storage_objects: number;
19
20
  }
20
21
 
21
22
  const MIGRATION_1 = `
@@ -280,6 +281,7 @@ export function getKnowledgeDbStats(path: string): KnowledgeDbStats {
280
281
  redaction_findings: count(db, 'redaction_findings'),
281
282
  audit_events: count(db, 'audit_events'),
282
283
  approval_gates: count(db, 'approval_gates'),
284
+ storage_objects: count(db, 'storage_objects'),
283
285
  };
284
286
  } finally {
285
287
  db.close();
package/src/mcp.js CHANGED
@@ -77,6 +77,18 @@ export function buildServer() {
77
77
  return jsonText(createKnowledgeService({ scope }).paths());
78
78
  });
79
79
 
80
+ registerTool(server, 'ok_storage_status', 'Knowledge storage status', 'Inspect local/S3 artifact storage, source ownership, and scalability contract', {
81
+ scope: scopeField,
82
+ }, async ({ scope }) => {
83
+ const service = createKnowledgeService({ scope });
84
+ const validation = service.validateStorage();
85
+ return jsonText({
86
+ ok: validation.ok,
87
+ ...service.storageContract(),
88
+ validation,
89
+ });
90
+ });
91
+
80
92
  registerTool(server, 'ok_parse_source_ref', 'Parse source reference', 'Parse and validate an open-files, S3, file, or web source ref', {
81
93
  uri: z.string().describe('Source reference URI'),
82
94
  }, async ({ uri }) => {
package/src/service.ts CHANGED
@@ -1,11 +1,18 @@
1
1
  import { createArtifactStore } from './artifact-store';
2
2
  import { consumeOpenFilesOutbox } from './outbox-consume';
3
- import { getKnowledgeDbStats, migrateKnowledgeDb } from './knowledge-db';
3
+ import { getKnowledgeDbStats, migrateKnowledgeDb, openKnowledgeDb } from './knowledge-db';
4
4
  import { ingestOpenFilesManifest } from './manifest-ingest';
5
5
  import { ingestSourceRef } from './source-ingest';
6
6
  import { resolveOpenFilesSource } from './source-resolver';
7
7
  import { providerStatus, listModelRegistry, type ProviderStatusResult, type ModelRegistryEntry } from './providers';
8
8
  import { resolveSafetyPolicy } from './safety';
9
+ import {
10
+ recordStorageObjects,
11
+ resolveStorageContract,
12
+ validateStorageConfig,
13
+ type StorageContract,
14
+ type StorageValidationResult,
15
+ } from './storage-contract';
9
16
  import { initializeWikiLayout } from './wiki-layout';
10
17
  import {
11
18
  ensureKnowledgeWorkspace,
@@ -76,6 +83,14 @@ export class KnowledgeService {
76
83
  return createArtifactStore(this.config(), this.ensureWorkspace());
77
84
  }
78
85
 
86
+ storageContract(): StorageContract {
87
+ return resolveStorageContract(this.config(), this.ensureWorkspace(), this.scope);
88
+ }
89
+
90
+ validateStorage(): StorageValidationResult {
91
+ return validateStorageConfig(this.config(), this.ensureWorkspace());
92
+ }
93
+
79
94
  paths(): KnowledgePathsResult {
80
95
  const workspace = this.ensureWorkspace();
81
96
  return {
@@ -107,7 +122,16 @@ export class KnowledgeService {
107
122
  }
108
123
 
109
124
  async initWiki() {
110
- return initializeWikiLayout(this.artifactStore());
125
+ const workspace = this.ensureWorkspace();
126
+ migrateKnowledgeDb(workspace.knowledgeDbPath);
127
+ const result = await initializeWikiLayout(this.artifactStore());
128
+ const db = openKnowledgeDb(workspace.knowledgeDbPath);
129
+ try {
130
+ recordStorageObjects(db, result.artifacts);
131
+ } finally {
132
+ db.close();
133
+ }
134
+ return result;
111
135
  }
112
136
 
113
137
  async ingestManifest(input: string) {
@@ -0,0 +1,265 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import type { Database } from 'bun:sqlite';
3
+ import type { KnowledgeConfig, KnowledgeWorkspace } from './workspace';
4
+ import { HASNA_KNOWLEDGE_APP_PATH } from './workspace';
5
+
6
+ export interface StorageArtifactClass {
7
+ kind: string;
8
+ prefix: string;
9
+ description: string;
10
+ }
11
+
12
+ export interface StorageContract {
13
+ scope: string;
14
+ mode: KnowledgeConfig['mode'];
15
+ storage_type: KnowledgeConfig['storage']['type'];
16
+ workspace_home: string;
17
+ local_layout: {
18
+ app_path: string;
19
+ config_path: string;
20
+ json_store_path: string;
21
+ knowledge_db_path: string;
22
+ directories: Record<string, string>;
23
+ };
24
+ artifact_store: {
25
+ type: KnowledgeConfig['storage']['type'];
26
+ artifacts_root: string;
27
+ uri_prefix: string;
28
+ s3: {
29
+ bucket: string;
30
+ prefix: string;
31
+ region: string | null;
32
+ profile: string | null;
33
+ server_side_encryption: string | null;
34
+ kms_key_configured: boolean;
35
+ } | null;
36
+ };
37
+ source_ownership: {
38
+ owner: 'open-files';
39
+ preferred_ref: string;
40
+ allowed_schemes: string[];
41
+ raw_source_bytes_stored_in_open_knowledge: false;
42
+ stores: string[];
43
+ does_not_store: string[];
44
+ };
45
+ generated_artifacts: StorageArtifactClass[];
46
+ scalability: {
47
+ catalog: string;
48
+ indexes: string;
49
+ logs: string;
50
+ markdown: string;
51
+ };
52
+ warnings: string[];
53
+ }
54
+
55
+ export interface StorageValidationResult {
56
+ ok: boolean;
57
+ errors: string[];
58
+ warnings: string[];
59
+ }
60
+
61
+ export interface GeneratedStorageObject {
62
+ uri: string;
63
+ key: string;
64
+ kind: string;
65
+ content_type?: string;
66
+ hash?: string;
67
+ size_bytes?: number;
68
+ metadata?: Record<string, unknown>;
69
+ }
70
+
71
+ const GENERATED_ARTIFACTS: StorageArtifactClass[] = [
72
+ {
73
+ kind: 'schema',
74
+ prefix: 'schemas/',
75
+ description: 'Machine-readable agent schemas and source rules.',
76
+ },
77
+ {
78
+ kind: 'index',
79
+ prefix: 'indexes/',
80
+ description: 'Small orientation indexes and future shard manifests.',
81
+ },
82
+ {
83
+ kind: 'log',
84
+ prefix: 'logs/',
85
+ description: 'Append-only JSONL run and wiki-maintenance log partitions.',
86
+ },
87
+ {
88
+ kind: 'run',
89
+ prefix: 'runs/',
90
+ description: 'Prompt/tool/cost ledgers and generated output records.',
91
+ },
92
+ {
93
+ kind: 'wiki_page',
94
+ prefix: 'wiki/',
95
+ description: 'Generated cited Markdown pages, not raw source files.',
96
+ },
97
+ {
98
+ kind: 'export',
99
+ prefix: 'exports/',
100
+ description: 'Portable exports and snapshots of derived knowledge state.',
101
+ },
102
+ ];
103
+
104
+ export function hashArtifactBody(body: string | Uint8Array): { hash: string; size_bytes: number } {
105
+ const bytes = typeof body === 'string' ? Buffer.from(body) : Buffer.from(body);
106
+ return {
107
+ hash: `sha256:${createHash('sha256').update(bytes).digest('hex')}`,
108
+ size_bytes: bytes.byteLength,
109
+ };
110
+ }
111
+
112
+ export function artifactKindForKey(key: string): string {
113
+ const match = GENERATED_ARTIFACTS.find((entry) => key.startsWith(entry.prefix));
114
+ return match?.kind ?? 'artifact';
115
+ }
116
+
117
+ export function resolveStorageContract(
118
+ config: KnowledgeConfig,
119
+ workspace: KnowledgeWorkspace,
120
+ scope = 'global',
121
+ ): StorageContract {
122
+ const validation = validateStorageConfig(config, workspace);
123
+ const s3 = config.storage.s3 ?? null;
124
+ const prefix = s3?.prefix?.replace(/^\/+|\/+$/g, '') ?? '';
125
+ const s3UriPrefix = s3 ? `s3://${s3.bucket}/${prefix ? `${prefix}/` : ''}` : '';
126
+
127
+ return {
128
+ scope,
129
+ mode: config.mode,
130
+ storage_type: config.storage.type,
131
+ workspace_home: workspace.home,
132
+ local_layout: {
133
+ app_path: HASNA_KNOWLEDGE_APP_PATH,
134
+ config_path: workspace.configPath,
135
+ json_store_path: workspace.jsonStorePath,
136
+ knowledge_db_path: workspace.knowledgeDbPath,
137
+ directories: {
138
+ artifacts: workspace.artifactsDir,
139
+ cache: workspace.cacheDir,
140
+ exports: workspace.exportsDir,
141
+ indexes: workspace.indexesDir,
142
+ logs: workspace.logsDir,
143
+ runs: workspace.runsDir,
144
+ schemas: workspace.schemasDir,
145
+ wiki: workspace.wikiDir,
146
+ },
147
+ },
148
+ artifact_store: {
149
+ type: config.storage.type,
150
+ artifacts_root: config.storage.artifacts_root,
151
+ uri_prefix: config.storage.type === 's3' ? s3UriPrefix : `file://${workspace.artifactsDir}/`,
152
+ s3: s3
153
+ ? {
154
+ bucket: s3.bucket,
155
+ prefix,
156
+ region: s3.region ?? null,
157
+ profile: s3.profile ?? null,
158
+ server_side_encryption: s3.server_side_encryption ?? null,
159
+ kms_key_configured: Boolean(s3.kms_key_id),
160
+ }
161
+ : null,
162
+ },
163
+ source_ownership: {
164
+ owner: 'open-files',
165
+ preferred_ref: config.sources.preferred_ref,
166
+ allowed_schemes: config.sources.allowed_schemes,
167
+ raw_source_bytes_stored_in_open_knowledge: false,
168
+ stores: [
169
+ 'source refs',
170
+ 'source revisions and hashes',
171
+ 'citation spans',
172
+ 'redacted extracted chunks',
173
+ 'embeddings',
174
+ 'generated wiki artifacts',
175
+ 'indexes',
176
+ 'run ledgers',
177
+ ],
178
+ does_not_store: [
179
+ 'raw open-files bytes',
180
+ 'S3 object credentials',
181
+ 'connector secrets',
182
+ 'hosted tenant ownership state',
183
+ ],
184
+ },
185
+ generated_artifacts: GENERATED_ARTIFACTS,
186
+ scalability: {
187
+ catalog: 'knowledge.db tracks sources, revisions, chunks, citations, indexes, runs, and storage_objects.',
188
+ indexes: 'Indexes are cataloged DB rows plus sharded artifacts, not one giant index.md.',
189
+ logs: 'Logs use dated JSONL partitions under logs/yyyy/mm/dd.jsonl.',
190
+ markdown: 'Markdown pages are the readable wiki layer over DB/object-store state.',
191
+ },
192
+ warnings: validation.warnings,
193
+ };
194
+ }
195
+
196
+ export function validateStorageConfig(config: KnowledgeConfig, workspace: KnowledgeWorkspace): StorageValidationResult {
197
+ const errors: string[] = [];
198
+ const warnings: string[] = [];
199
+
200
+ if (!workspace.home.endsWith(HASNA_KNOWLEDGE_APP_PATH)) {
201
+ warnings.push(`Workspace home does not end with ${HASNA_KNOWLEDGE_APP_PATH}: ${workspace.home}`);
202
+ }
203
+
204
+ if (config.storage.type === 's3') {
205
+ if (!config.storage.s3?.bucket) errors.push('storage.s3.bucket is required when storage.type is s3.');
206
+ if (!config.storage.s3?.prefix) warnings.push('storage.s3.prefix is empty; generated knowledge artifacts will be written at the bucket root.');
207
+ if (config.mode === 'local') warnings.push('storage.type is s3 while mode is local; this is valid for BYO S3, but hosted wrappers should set mode to hosted.');
208
+ }
209
+
210
+ if (config.storage.type === 'local' && config.storage.s3) {
211
+ warnings.push('storage.s3 is configured but ignored while storage.type is local.');
212
+ }
213
+
214
+ if (config.sources.preferred_ref !== 'open-files') {
215
+ warnings.push('sources.preferred_ref should stay open-files for durable company knowledge.');
216
+ }
217
+
218
+ if (!config.sources.allowed_schemes.includes('open-files')) {
219
+ errors.push('sources.allowed_schemes must include open-files.');
220
+ }
221
+
222
+ return {
223
+ ok: errors.length === 0,
224
+ errors,
225
+ warnings,
226
+ };
227
+ }
228
+
229
+ export function recordStorageObjects(db: Database, objects: GeneratedStorageObject[], now = new Date()): void {
230
+ const timestamp = now.toISOString();
231
+ const statement = db.prepare(`
232
+ INSERT INTO storage_objects (
233
+ id, artifact_uri, kind, content_type, hash, size_bytes, metadata_json, created_at, updated_at
234
+ )
235
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
236
+ ON CONFLICT(artifact_uri) DO UPDATE SET
237
+ kind = excluded.kind,
238
+ content_type = excluded.content_type,
239
+ hash = excluded.hash,
240
+ size_bytes = excluded.size_bytes,
241
+ metadata_json = excluded.metadata_json,
242
+ updated_at = excluded.updated_at
243
+ `);
244
+
245
+ const insert = db.transaction((entries: GeneratedStorageObject[]) => {
246
+ for (const entry of entries) {
247
+ statement.run(
248
+ randomUUID(),
249
+ entry.uri,
250
+ entry.kind,
251
+ entry.content_type ?? null,
252
+ entry.hash ?? null,
253
+ entry.size_bytes ?? null,
254
+ JSON.stringify({
255
+ key: entry.key,
256
+ ...(entry.metadata ?? {}),
257
+ }),
258
+ timestamp,
259
+ timestamp,
260
+ );
261
+ }
262
+ });
263
+
264
+ insert(objects);
265
+ }
@@ -1,10 +1,16 @@
1
1
  import type { ArtifactStore } from './artifact-store';
2
+ import {
3
+ artifactKindForKey,
4
+ hashArtifactBody,
5
+ type GeneratedStorageObject,
6
+ } from './storage-contract';
2
7
 
3
8
  export interface WikiLayoutInitResult {
4
9
  schema_key: string;
5
10
  root_index_key: string;
6
11
  wiki_readme_key: string;
7
12
  log_key: string;
13
+ artifacts: GeneratedStorageObject[];
8
14
  written: string[];
9
15
  }
10
16
 
@@ -86,19 +92,29 @@ export async function initializeWikiLayout(store: ArtifactStore, now = new Date(
86
92
  wiki_readme_key: wikiReadmeKey,
87
93
  };
88
94
 
89
- const writes = [
90
- store.put({ key: schemaKey, body: agentSchemaTemplate(), content_type: 'text/markdown' }),
91
- store.put({ key: rootIndexKey, body: rootIndexTemplate(), content_type: 'text/markdown' }),
92
- store.put({ key: wikiReadmeKey, body: wikiReadmeTemplate(), content_type: 'text/markdown' }),
93
- store.put({ key: logKey, body: `${JSON.stringify(event)}\n`, content_type: 'application/x-ndjson' }),
95
+ const entries = [
96
+ { key: schemaKey, body: agentSchemaTemplate(), content_type: 'text/markdown' },
97
+ { key: rootIndexKey, body: rootIndexTemplate(), content_type: 'text/markdown' },
98
+ { key: wikiReadmeKey, body: wikiReadmeTemplate(), content_type: 'text/markdown' },
99
+ { key: logKey, body: `${JSON.stringify(event)}\n`, content_type: 'application/x-ndjson' },
94
100
  ];
95
101
 
96
- await Promise.all(writes);
102
+ const artifacts = await Promise.all(entries.map(async (entry) => {
103
+ const result = await store.put(entry);
104
+ return {
105
+ key: result.key,
106
+ uri: result.uri,
107
+ kind: artifactKindForKey(entry.key),
108
+ content_type: entry.content_type,
109
+ ...hashArtifactBody(entry.body),
110
+ };
111
+ }));
97
112
  return {
98
113
  schema_key: schemaKey,
99
114
  root_index_key: rootIndexKey,
100
115
  wiki_readme_key: wikiReadmeKey,
101
116
  log_key: logKey,
117
+ artifacts,
102
118
  written: [schemaKey, rootIndexKey, wikiReadmeKey, logKey],
103
119
  };
104
120
  }