@agimon-ai/po-artifact 0.0.0-alpha.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.
@@ -0,0 +1,532 @@
1
+ import { composeServerProviderOverrides, defineServerPlugin } from "@agimon-ai/po-core/serverFacet";
2
+ import { composeRootHooks, defineChannel, defineHook, defineRoot, defineRoute } from "@agimon-ai/po-core/extensionFile";
3
+ import { clearTimeout, setTimeout } from "node:timers";
4
+ import { constants } from "node:fs";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { piAgentDirectory } from "@agimon-ai/po-core/piSettings";
8
+ import { Hono } from "hono";
9
+ //#region src/constants/artifacts.ts
10
+ const ARTIFACT_DIRECTORY = "doom-artifacts";
11
+ const ARTIFACT_REFRESH_MS = 2e3;
12
+ const ARTIFACT_CONTENT_TYPES = {
13
+ ".avif": "image/avif",
14
+ ".bmp": "image/bmp",
15
+ ".gif": "image/gif",
16
+ ".jpeg": "image/jpeg",
17
+ ".jpg": "image/jpeg",
18
+ ".png": "image/png",
19
+ ".svg": "image/svg+xml",
20
+ ".webp": "image/webp",
21
+ ".ico": "image/vnd.microsoft.icon",
22
+ ".mp3": "audio/mpeg",
23
+ ".wav": "audio/wav",
24
+ ".m4a": "audio/mp4",
25
+ ".ogg": "audio/ogg",
26
+ ".m4v": "video/mp4",
27
+ ".mov": "video/quicktime",
28
+ ".mp4": "video/mp4",
29
+ ".webm": "video/webm",
30
+ ".pdf": "application/pdf",
31
+ ".md": "text/markdown; charset=utf-8",
32
+ ".markdown": "text/markdown; charset=utf-8",
33
+ ".txt": "text/plain; charset=utf-8",
34
+ ".html": "text/html; charset=utf-8",
35
+ ".htm": "text/html; charset=utf-8"
36
+ };
37
+ //#endregion
38
+ //#region src/types/artifacts.ts
39
+ const artifactsChannelType = "artifacts";
40
+ //#endregion
41
+ //#region src/services/artifactFiles/index.ts
42
+ var ArtifactAccessError = class extends Error {
43
+ status;
44
+ constructor(message, status) {
45
+ super(message);
46
+ this.status = status;
47
+ }
48
+ };
49
+ function artifactRoot(sessionId, environment) {
50
+ if (!/^[a-zA-Z0-9_-]+$/u.test(sessionId)) throw new ArtifactAccessError("Invalid artifact session identity.", 400);
51
+ return path.join(piAgentDirectory(environment, environment.HOME), ARTIFACT_DIRECTORY, sessionId);
52
+ }
53
+ function inside(root, candidate) {
54
+ const relative = path.relative(root, candidate);
55
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
56
+ }
57
+ function codeOf(error) {
58
+ return error instanceof Error && "code" in error ? String(error.code) : void 0;
59
+ }
60
+ /** The owned directories must not redirect artifact access through a symlink. */
61
+ async function checkedRoot(root) {
62
+ const base = path.dirname(root);
63
+ for (const directory of [base, root]) {
64
+ const stat = await fs.lstat(directory);
65
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new ArtifactAccessError("The artifact folder is unavailable.", 403);
66
+ }
67
+ const realBase = await fs.realpath(base);
68
+ const realRoot = await fs.realpath(root);
69
+ if (!inside(realBase, realRoot)) throw new ArtifactAccessError("The artifact folder is outside its storage directory.", 403);
70
+ return realRoot;
71
+ }
72
+ async function ensureArtifactRoot(root) {
73
+ await fs.mkdir(path.dirname(root), {
74
+ recursive: true,
75
+ mode: 448
76
+ });
77
+ await fs.mkdir(root, {
78
+ recursive: true,
79
+ mode: 448
80
+ });
81
+ await checkedRoot(root);
82
+ }
83
+ function artifactMimeType(relativePath) {
84
+ return ARTIFACT_CONTENT_TYPES[path.extname(relativePath).toLowerCase()] ?? "application/octet-stream";
85
+ }
86
+ /** Open without following mutable ancestors. Node exposes no portable openat API. */
87
+ async function openContained(root, segments) {
88
+ if (process.platform === "darwin") return fs.open(path.join(root, ...segments), constants.O_RDONLY | constants.O_NONBLOCK | 536870912);
89
+ if (process.platform !== "linux") throw new ArtifactAccessError("Secure artifact reads require macOS or Linux.", 403);
90
+ const flags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;
91
+ let directory = await fs.open(path.dirname(root), flags);
92
+ try {
93
+ for (const segment of [path.basename(root), ...segments.slice(0, -1)]) {
94
+ const child = await fs.open(`/proc/self/fd/${directory.fd}/${segment}`, flags);
95
+ await directory.close();
96
+ directory = child;
97
+ }
98
+ return await fs.open(`/proc/self/fd/${directory.fd}/${segments.at(-1)}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
99
+ } finally {
100
+ await directory.close();
101
+ }
102
+ }
103
+ /** One guarded open shared by metadata, text and byte reads. The caller closes the handle. */
104
+ async function openArtifact(root, requested) {
105
+ if (!requested || requested.includes("\0") || requested.includes("\\") || path.isAbsolute(requested) || requested.split("/").some((part) => part === ".." || part === "." || part === "")) throw new ArtifactAccessError("Invalid artifact path.", 400);
106
+ try {
107
+ const realRoot = await checkedRoot(root);
108
+ const candidate = path.resolve(realRoot, requested);
109
+ if (!inside(realRoot, candidate)) throw new ArtifactAccessError("The artifact is outside this session folder.", 403);
110
+ let current = realRoot;
111
+ const segments = requested.split("/");
112
+ for (let index = 0; index < segments.length; index += 1) {
113
+ current = path.join(current, segments[index]);
114
+ const stat = await fs.lstat(current);
115
+ if (stat.isSymbolicLink()) throw new ArtifactAccessError("Artifact symlinks cannot be read.", 403);
116
+ if (index < segments.length - 1 ? !stat.isDirectory() : !stat.isFile()) throw new ArtifactAccessError("The artifact is not a regular file.", 404);
117
+ }
118
+ const real = await fs.realpath(candidate);
119
+ if (!inside(realRoot, real)) throw new ArtifactAccessError("The artifact is outside this session folder.", 403);
120
+ const expected = await fs.lstat(real);
121
+ const handle = await openContained(realRoot, segments);
122
+ try {
123
+ const stat = await handle.stat();
124
+ if (!stat.isFile() || stat.dev !== expected.dev || stat.ino !== expected.ino) throw new ArtifactAccessError("The artifact changed while it was opened. Try again.", 403);
125
+ return {
126
+ handle,
127
+ stat,
128
+ path: requested,
129
+ mimeType: artifactMimeType(requested)
130
+ };
131
+ } catch (error) {
132
+ await handle.close();
133
+ throw error;
134
+ }
135
+ } catch (error) {
136
+ if (error instanceof ArtifactAccessError) throw error;
137
+ if (codeOf(error) === "ENOENT" || codeOf(error) === "ENOTDIR") throw new ArtifactAccessError("The artifact no longer exists.", 404);
138
+ if (codeOf(error) === "EACCES" || codeOf(error) === "EPERM" || codeOf(error) === "ELOOP") throw new ArtifactAccessError("The artifact cannot be read.", 403);
139
+ throw error;
140
+ }
141
+ }
142
+ /** Bound allocation and read length even when the file grows after its metadata was read. */
143
+ async function readArtifactBytes(handle, size, offset = 0) {
144
+ const bytes = new Uint8Array(size);
145
+ let read = 0;
146
+ while (read < size) {
147
+ const result = await handle.read(bytes, read, size - read, offset + read);
148
+ if (result.bytesRead === 0) break;
149
+ read += result.bytesRead;
150
+ }
151
+ return bytes.subarray(0, read);
152
+ }
153
+ async function listArtifacts(root) {
154
+ const items = [];
155
+ try {
156
+ const realRoot = await checkedRoot(root);
157
+ const visit = async (directory) => {
158
+ for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
159
+ const location = path.join(directory, entry.name);
160
+ if (entry.isDirectory()) {
161
+ const stat = await fs.lstat(location);
162
+ if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
163
+ const real = await fs.realpath(location);
164
+ if (inside(realRoot, real)) await visit(real);
165
+ } else if (entry.isFile()) {
166
+ const relativePath = path.relative(realRoot, location).split(path.sep).join("/");
167
+ try {
168
+ const file = await openArtifact(root, relativePath);
169
+ try {
170
+ items.push({
171
+ path: relativePath,
172
+ name: entry.name,
173
+ size: file.stat.size,
174
+ modifiedAt: file.stat.mtimeMs
175
+ });
176
+ } finally {
177
+ await file.handle.close();
178
+ }
179
+ } catch (error) {
180
+ if (error instanceof ArtifactAccessError && (error.status === 404 || error.status === 403)) continue;
181
+ throw error;
182
+ }
183
+ }
184
+ }
185
+ };
186
+ await visit(realRoot);
187
+ items.sort((left, right) => right.modifiedAt - left.modifiedAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
188
+ return { items };
189
+ } catch (error) {
190
+ if (codeOf(error) === "ENOENT") return { items: [] };
191
+ return {
192
+ items: [],
193
+ error: error instanceof Error ? error.message : "The artifact folder could not be read."
194
+ };
195
+ }
196
+ }
197
+ //#endregion
198
+ //#region src/services/artifactSession/index.ts
199
+ function createArtifactSession({ host }) {
200
+ const environment = Object.freeze({ ...host.context.environment });
201
+ let active;
202
+ let timer;
203
+ let pending;
204
+ let closed = false;
205
+ const refresh = () => {
206
+ if (pending) return pending;
207
+ const runtime = active;
208
+ if (!runtime || closed) return Promise.resolve();
209
+ pending = (async () => {
210
+ const snapshot = await listArtifacts(runtime.root);
211
+ if (closed || active !== runtime) return;
212
+ const fingerprint = JSON.stringify(snapshot);
213
+ if (fingerprint === runtime.fingerprint) return;
214
+ runtime.fingerprint = fingerprint;
215
+ host.context.directEvents?.publish(artifactsChannelType, runtime.context.sessionId, snapshot);
216
+ })().finally(() => {
217
+ pending = void 0;
218
+ });
219
+ return pending;
220
+ };
221
+ const schedule = () => {
222
+ if (!active || closed) return;
223
+ timer = setTimeout(() => {
224
+ timer = void 0;
225
+ refresh().finally(schedule);
226
+ }, ARTIFACT_REFRESH_MS);
227
+ };
228
+ const stop = async () => {
229
+ active = void 0;
230
+ if (timer !== void 0) clearTimeout(timer);
231
+ timer = void 0;
232
+ await pending;
233
+ };
234
+ return {
235
+ activities: [{
236
+ name: "artifacts",
237
+ async start(context) {
238
+ if (closed) throw new Error("Artifact session is closed.");
239
+ await stop();
240
+ const root = artifactRoot(context.sessionId, environment);
241
+ await ensureArtifactRoot(root);
242
+ if (closed) throw new Error("Artifact session is closed.");
243
+ active = {
244
+ context,
245
+ root
246
+ };
247
+ await refresh();
248
+ schedule();
249
+ return stop;
250
+ }
251
+ }],
252
+ beforeAgentStart: {
253
+ event: "before_agent_start",
254
+ async handle(event, context) {
255
+ const root = artifactRoot(context.sessionId, environment);
256
+ await ensureArtifactRoot(root);
257
+ const systemPrompt = typeof event.systemPrompt === "string" ? event.systemPrompt : "";
258
+ const declaration = `Session artifact folder: ${root}`;
259
+ if (systemPrompt.includes(declaration)) return { systemPrompt };
260
+ return { systemPrompt: `${systemPrompt}\n\n${declaration}\nSave user-designated artifacts and important deliverable documents in this folder, using descriptive filenames. Keep ordinary repository source edits in their existing locations. Do not move existing documents or overwrite artifacts without the user's intent. This folder belongs only to the current session.`.trim() };
261
+ }
262
+ },
263
+ toolExecutionEnd: {
264
+ event: "tool_execution_end",
265
+ handle(_event, context) {
266
+ return active?.context.sessionId === context.sessionId ? refresh() : void 0;
267
+ }
268
+ },
269
+ onDispose: async () => {
270
+ closed = true;
271
+ await stop();
272
+ }
273
+ };
274
+ }
275
+ //#endregion
276
+ //#region src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts
277
+ const createArtifactServerRoot = (context) => {
278
+ const runtime = createArtifactSession(context);
279
+ return {
280
+ value: runtime,
281
+ activities: runtime.activities,
282
+ onDispose: runtime.onDispose
283
+ };
284
+ };
285
+ //#endregion
286
+ //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
287
+ var root_server_default = defineRoot(createArtifactServerRoot);
288
+ //#endregion
289
+ //#region src/services/artifactChannel/index.ts
290
+ function snapshotOf(value) {
291
+ if (typeof value !== "object" || value === null) return false;
292
+ const snapshot = value;
293
+ return Array.isArray(snapshot.items) && (snapshot.error === void 0 || typeof snapshot.error === "string") && snapshot.items.every((item) => typeof item === "object" && item !== null && typeof item.path === "string" && typeof item.name === "string" && typeof item.size === "number" && Number.isFinite(item.size) && typeof item.modifiedAt === "number" && Number.isFinite(item.modifiedAt));
294
+ }
295
+ function createArtifactsChannel() {
296
+ return {
297
+ frameType: artifactsChannelType,
298
+ start(host) {
299
+ const sessions = /* @__PURE__ */ new Map();
300
+ return {
301
+ payloadFor(scope) {
302
+ return sessions.get(scope.sessionId)?.snapshot;
303
+ },
304
+ sessionAdded(scope) {
305
+ sessions.get(scope.sessionId)?.unsubscribe();
306
+ const state = { unsubscribe: () => void 0 };
307
+ sessions.set(scope.sessionId, state);
308
+ state.unsubscribe = host.directEvents.subscribe(artifactsChannelType, scope.sessionId, (snapshot) => {
309
+ if (!snapshotOf(snapshot) || sessions.get(scope.sessionId) !== state) return;
310
+ state.snapshot = snapshot;
311
+ host.publish(scope.sessionId, snapshot);
312
+ }, { replayLatest: true });
313
+ (async () => {
314
+ let snapshot;
315
+ try {
316
+ snapshot = await listArtifacts(artifactRoot(scope.sessionId, scope.environment ?? {}));
317
+ } catch (error) {
318
+ snapshot = {
319
+ items: [],
320
+ error: error instanceof Error ? error.message : "The artifact folder could not be read."
321
+ };
322
+ }
323
+ if (sessions.get(scope.sessionId) !== state || state.snapshot !== void 0) return;
324
+ state.snapshot = snapshot;
325
+ host.publish(scope.sessionId, snapshot);
326
+ })();
327
+ },
328
+ sessionRemoved(sessionId) {
329
+ sessions.get(sessionId)?.unsubscribe();
330
+ sessions.delete(sessionId);
331
+ },
332
+ close() {
333
+ for (const state of sessions.values()) state.unsubscribe();
334
+ sessions.clear();
335
+ }
336
+ };
337
+ }
338
+ };
339
+ }
340
+ //#endregion
341
+ //#region src/extensions/(backend)/channel/artifacts.server.ts
342
+ var artifacts_server_default = defineChannel(createArtifactsChannel);
343
+ //#endregion
344
+ //#region src/types/apiRoutes.ts
345
+ const routes = {
346
+ list: {
347
+ method: "GET",
348
+ path: "/",
349
+ response: void 0
350
+ },
351
+ preview: {
352
+ method: "GET",
353
+ path: "/preview",
354
+ query: ["path"],
355
+ response: void 0
356
+ },
357
+ content: {
358
+ method: "GET",
359
+ path: "/content",
360
+ query: ["path"],
361
+ raw: true
362
+ }
363
+ };
364
+ //#endregion
365
+ //#region src/services/artifactsApi/index.ts
366
+ function createArtifactsApi(root) {
367
+ const app = new Hono();
368
+ app.onError((error, context) => context.json({ error: error instanceof ArtifactAccessError ? error.message : "The artifact could not be read." }, error instanceof ArtifactAccessError ? error.status : 500));
369
+ app.get(routes.list.path, async (context) => {
370
+ const snapshot = await listArtifacts(root);
371
+ return context.json(snapshot, snapshot.error === void 0 ? 200 : 500);
372
+ });
373
+ app.get(routes.preview.path, async (context) => {
374
+ const file = await openArtifact(root, context.req.query("path"));
375
+ try {
376
+ const preview = {
377
+ path: file.path,
378
+ content: "",
379
+ mimeType: file.mimeType
380
+ };
381
+ if (file.stat.size > 1048576) return context.json({
382
+ ...preview,
383
+ unavailable: true,
384
+ reason: "This artifact is too large for a text preview."
385
+ });
386
+ const bytes = await readArtifactBytes(file.handle, 1048577);
387
+ if (bytes.byteLength > 1048576) return context.json({
388
+ ...preview,
389
+ unavailable: true,
390
+ reason: "This artifact is too large for a text preview."
391
+ });
392
+ if (bytes.subarray(0, 8192).includes(0)) return context.json({
393
+ ...preview,
394
+ unavailable: true,
395
+ reason: "This artifact is binary."
396
+ });
397
+ return context.json({
398
+ ...preview,
399
+ content: new TextDecoder().decode(bytes)
400
+ });
401
+ } finally {
402
+ await file.handle.close();
403
+ }
404
+ });
405
+ app.get(routes.content.path, async (context) => {
406
+ const file = await openArtifact(root, context.req.query("path"));
407
+ try {
408
+ if (file.stat.size > 26214400) throw new ArtifactAccessError("This artifact is too large to download.", 413);
409
+ const version = `"${file.stat.dev}-${file.stat.ino}-${file.stat.size}-${file.stat.mtimeMs}-${file.stat.ctimeMs}"`;
410
+ const name = encodeURIComponent(path.basename(file.path)).replace(/[!'()*]/gu, (value) => `%${value.charCodeAt(0).toString(16).toUpperCase()}`);
411
+ const headers = new Headers({
412
+ "content-type": file.mimeType,
413
+ "content-disposition": `attachment; filename*=UTF-8''${name}`,
414
+ "cache-control": "no-store",
415
+ "x-content-type-options": "nosniff",
416
+ "content-security-policy": "default-src 'none'; sandbox",
417
+ "accept-ranges": "bytes",
418
+ etag: version
419
+ });
420
+ const expectedVersion = context.req.header("if-match");
421
+ if (expectedVersion !== void 0 && expectedVersion !== version) return new Response(null, {
422
+ status: 412,
423
+ headers
424
+ });
425
+ let start = 0;
426
+ let end = file.stat.size - 1;
427
+ const range = context.req.header("range");
428
+ if (range !== void 0) {
429
+ const match = /^bytes=(\d*)-(\d*)$/u.exec(range);
430
+ if (!match || !match[1] && !match[2] || file.stat.size === 0 || [match[1], match[2]].some((value) => value !== "" && !Number.isSafeInteger(Number(value)))) {
431
+ headers.set("content-range", `bytes */${file.stat.size}`);
432
+ return new Response(null, {
433
+ status: 416,
434
+ headers
435
+ });
436
+ }
437
+ if (match[1] === "") {
438
+ const suffix = Number(match[2]);
439
+ start = suffix > 0 ? Math.max(0, file.stat.size - suffix) : file.stat.size;
440
+ } else {
441
+ start = Number(match[1]);
442
+ end = match[2] === "" ? end : Math.min(end, Number(match[2]));
443
+ }
444
+ if (start > end || start >= file.stat.size) {
445
+ headers.set("content-range", `bytes */${file.stat.size}`);
446
+ return new Response(null, {
447
+ status: 416,
448
+ headers
449
+ });
450
+ }
451
+ headers.set("content-range", `bytes ${start}-${end}/${file.stat.size}`);
452
+ }
453
+ const bytes = await readArtifactBytes(file.handle, Math.max(0, end - start + 1), start);
454
+ const after = await file.handle.stat();
455
+ if (after.size !== file.stat.size || after.mtimeMs !== file.stat.mtimeMs || after.ctimeMs !== file.stat.ctimeMs) return new Response(null, {
456
+ status: 412,
457
+ headers
458
+ });
459
+ headers.set("content-length", String(bytes.byteLength));
460
+ return new Response(bytes, {
461
+ status: range === void 0 ? 200 : 206,
462
+ headers
463
+ });
464
+ } finally {
465
+ await file.handle.close();
466
+ }
467
+ });
468
+ return app;
469
+ }
470
+ //#endregion
471
+ //#region src/extensions/workspaces/sessions/(backend)/api/artifacts/route.server.ts
472
+ var route_server_default = defineRoute({
473
+ basePath: "artifacts",
474
+ start(context) {
475
+ if (!context.sessionId) throw new Error("Artifacts requires a session identity.");
476
+ const app = createArtifactsApi(artifactRoot(context.sessionId, context.environment ?? {}));
477
+ return {
478
+ fetch: (request) => app.fetch(request),
479
+ close: () => void 0
480
+ };
481
+ }
482
+ });
483
+ //#endregion
484
+ //#region src/extensions/workspaces/sessions/(backend)/hook/before-agent-start.server.ts
485
+ var before_agent_start_server_default = defineHook((context) => context.root.beforeAgentStart);
486
+ //#endregion
487
+ //#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts
488
+ var tool_execution_end_server_default = defineHook((context) => context.root.toolExecutionEnd);
489
+ //#endregion
490
+ //#region generated/server.ts
491
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
492
+ const via = (identity, value) => value === void 0 ? void 0 : {
493
+ ...identity,
494
+ ...value
495
+ };
496
+ const facet = defineServerPlugin({
497
+ name: "@agimon-ai/po-artifact",
498
+ providerOverrides: composeServerProviderOverrides(root_server_default),
499
+ global: () => ({ get channels() {
500
+ return [() => via({ frameType: "artifacts" }, artifacts_server_default())];
501
+ } }),
502
+ workspace: () => ({ get channels() {
503
+ return [() => via({ frameType: "artifacts" }, artifacts_server_default())];
504
+ } }),
505
+ session: async (context) => {
506
+ const scopeSession = await root_server_default(context);
507
+ const contextSession = {
508
+ ...context,
509
+ root: scopeSession.value
510
+ };
511
+ return {
512
+ ...composeRootHooks(scopeSession),
513
+ get api() {
514
+ return [at(route_server_default, contextSession)];
515
+ },
516
+ get channels() {
517
+ return [() => via({ frameType: "artifacts" }, artifacts_server_default())];
518
+ },
519
+ get hooks() {
520
+ return [via({ event: "before_agent_start" }, at(before_agent_start_server_default, contextSession)), via({ event: "tool_execution_end" }, at(tool_execution_end_server_default, contextSession))];
521
+ },
522
+ services: [...scopeSession.services ?? []],
523
+ get activities() {
524
+ return [...scopeSession.activities ?? []];
525
+ }
526
+ };
527
+ }
528
+ });
529
+ //#endregion
530
+ export { facet as default, facet };
531
+
532
+ //# sourceMappingURL=server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.mjs","names":["rootSession","channelArtifacts","apiRoute","hookBeforeAgentStart","hookToolExecutionEnd"],"sources":["../../src/constants/artifacts.ts","../../src/types/artifacts.ts","../../src/services/artifactFiles/index.ts","../../src/services/artifactSession/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/services/artifactChannel/index.ts","../../src/extensions/(backend)/channel/artifacts.server.ts","../../src/types/apiRoutes.ts","../../src/services/artifactsApi/index.ts","../../src/extensions/workspaces/sessions/(backend)/api/artifacts/route.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/before-agent-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts","../../generated/server.ts"],"sourcesContent":["export const ARTIFACT_DIRECTORY = 'doom-artifacts';\nexport const MAX_PREVIEW_BYTES = 1024 * 1024;\nexport const MAX_CONTENT_BYTES = 25 * 1024 * 1024;\nexport const ARTIFACT_REFRESH_MS = 2000;\n\nexport const ARTIFACT_CONTENT_TYPES: Readonly<Record<string, string>> = {\n '.avif': 'image/avif',\n '.bmp': 'image/bmp',\n '.gif': 'image/gif',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.ico': 'image/vnd.microsoft.icon',\n '.mp3': 'audio/mpeg',\n '.wav': 'audio/wav',\n '.m4a': 'audio/mp4',\n '.ogg': 'audio/ogg',\n '.m4v': 'video/mp4',\n '.mov': 'video/quicktime',\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.pdf': 'application/pdf',\n '.md': 'text/markdown; charset=utf-8',\n '.markdown': 'text/markdown; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.htm': 'text/html; charset=utf-8',\n};\n","export interface ArtifactItem {\n /** Relative to the current session's artifact directory. */\n path: string;\n name: string;\n size: number;\n modifiedAt: number;\n}\n\nexport interface ArtifactSnapshot {\n items: ArtifactItem[];\n error?: string;\n}\n\nexport interface ArtifactPreview {\n path: string;\n content: string;\n mimeType: string;\n unavailable?: boolean;\n reason?: string;\n}\n\nexport const artifactsChannelType = 'artifacts';\n","import { constants } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { piAgentDirectory } from '@agimon-ai/po-core/piSettings';\n\nimport { ARTIFACT_CONTENT_TYPES, ARTIFACT_DIRECTORY } from '../../constants/artifacts';\nimport type { ArtifactItem, ArtifactSnapshot } from '../../types/artifacts';\n\nexport class ArtifactAccessError extends Error {\n constructor(\n message: string,\n readonly status: 400 | 403 | 404 | 413,\n ) {\n super(message);\n }\n}\n\nexport function artifactRoot(sessionId: string, environment: Readonly<Record<string, string | undefined>>): string {\n if (!/^[a-zA-Z0-9_-]+$/u.test(sessionId)) throw new ArtifactAccessError('Invalid artifact session identity.', 400);\n return path.join(piAgentDirectory(environment, environment.HOME), ARTIFACT_DIRECTORY, sessionId);\n}\n\nfunction inside(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);\n}\n\nfunction codeOf(error: unknown): string | undefined {\n return error instanceof Error && 'code' in error ? String(error.code) : undefined;\n}\n\n/** The owned directories must not redirect artifact access through a symlink. */\nasync function checkedRoot(root: string): Promise<string> {\n const base = path.dirname(root);\n for (const directory of [base, root]) {\n const stat = await fs.lstat(directory);\n if (!stat.isDirectory() || stat.isSymbolicLink())\n throw new ArtifactAccessError('The artifact folder is unavailable.', 403);\n }\n const realBase = await fs.realpath(base);\n const realRoot = await fs.realpath(root);\n if (!inside(realBase, realRoot))\n throw new ArtifactAccessError('The artifact folder is outside its storage directory.', 403);\n return realRoot;\n}\n\nexport async function ensureArtifactRoot(root: string): Promise<void> {\n await fs.mkdir(path.dirname(root), { recursive: true, mode: 0o700 });\n await fs.mkdir(root, { recursive: true, mode: 0o700 });\n await checkedRoot(root);\n}\n\nexport function artifactMimeType(relativePath: string): string {\n return ARTIFACT_CONTENT_TYPES[path.extname(relativePath).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/** Open without following mutable ancestors. Node exposes no portable openat API. */\nasync function openContained(root: string, segments: string[]): Promise<fs.FileHandle> {\n if (process.platform === 'darwin') {\n // Darwin O_NOFOLLOW_ANY checks every component atomically. Do not combine it with O_NOFOLLOW.\n return fs.open(path.join(root, ...segments), constants.O_RDONLY | constants.O_NONBLOCK | 0x20000000);\n }\n if (process.platform !== 'linux') throw new ArtifactAccessError('Secure artifact reads require macOS or Linux.', 403);\n const flags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;\n let directory = await fs.open(path.dirname(root), flags);\n try {\n for (const segment of [path.basename(root), ...segments.slice(0, -1)]) {\n const child = await fs.open(`/proc/self/fd/${directory.fd}/${segment}`, flags);\n await directory.close();\n directory = child;\n }\n return await fs.open(\n `/proc/self/fd/${directory.fd}/${segments.at(-1)!}`,\n constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,\n );\n } finally {\n await directory.close();\n }\n}\n\n/** One guarded open shared by metadata, text and byte reads. The caller closes the handle. */\nexport async function openArtifact(root: string, requested: string | undefined) {\n if (\n !requested ||\n requested.includes('\\0') ||\n requested.includes('\\\\') ||\n path.isAbsolute(requested) ||\n requested.split('/').some((part) => part === '..' || part === '.' || part === '')\n )\n throw new ArtifactAccessError('Invalid artifact path.', 400);\n try {\n const realRoot = await checkedRoot(root);\n const candidate = path.resolve(realRoot, requested);\n if (!inside(realRoot, candidate))\n throw new ArtifactAccessError('The artifact is outside this session folder.', 403);\n let current = realRoot;\n const segments = requested.split('/');\n for (let index = 0; index < segments.length; index += 1) {\n current = path.join(current, segments[index]!);\n const stat = await fs.lstat(current);\n if (stat.isSymbolicLink()) throw new ArtifactAccessError('Artifact symlinks cannot be read.', 403);\n if (index < segments.length - 1 ? !stat.isDirectory() : !stat.isFile())\n throw new ArtifactAccessError('The artifact is not a regular file.', 404);\n }\n const real = await fs.realpath(candidate);\n if (!inside(realRoot, real)) throw new ArtifactAccessError('The artifact is outside this session folder.', 403);\n const expected = await fs.lstat(real);\n const handle = await openContained(realRoot, segments);\n try {\n const stat = await handle.stat();\n if (!stat.isFile() || stat.dev !== expected.dev || stat.ino !== expected.ino)\n throw new ArtifactAccessError('The artifact changed while it was opened. Try again.', 403);\n return { handle, stat, path: requested, mimeType: artifactMimeType(requested) };\n } catch (error) {\n await handle.close();\n throw error;\n }\n } catch (error) {\n if (error instanceof ArtifactAccessError) throw error;\n if (codeOf(error) === 'ENOENT' || codeOf(error) === 'ENOTDIR')\n throw new ArtifactAccessError('The artifact no longer exists.', 404);\n if (codeOf(error) === 'EACCES' || codeOf(error) === 'EPERM' || codeOf(error) === 'ELOOP')\n throw new ArtifactAccessError('The artifact cannot be read.', 403);\n throw error;\n }\n}\n\n/** Bound allocation and read length even when the file grows after its metadata was read. */\nexport async function readArtifactBytes(handle: fs.FileHandle, size: number, offset = 0): Promise<Uint8Array> {\n const bytes = new Uint8Array(size);\n let read = 0;\n while (read < size) {\n const result = await handle.read(bytes, read, size - read, offset + read);\n if (result.bytesRead === 0) break;\n read += result.bytesRead;\n }\n return bytes.subarray(0, read);\n}\n\nexport async function listArtifacts(root: string): Promise<ArtifactSnapshot> {\n const items: ArtifactItem[] = [];\n try {\n const realRoot = await checkedRoot(root);\n const visit = async (directory: string): Promise<void> => {\n for (const entry of await fs.readdir(directory, { withFileTypes: true })) {\n const location = path.join(directory, entry.name);\n if (entry.isDirectory()) {\n // Recheck each directory rather than trusting a readdir entry across a rename.\n const stat = await fs.lstat(location);\n if (!stat.isDirectory() || stat.isSymbolicLink()) continue;\n const real = await fs.realpath(location);\n if (inside(realRoot, real)) await visit(real);\n } else if (entry.isFile()) {\n const relativePath = path.relative(realRoot, location).split(path.sep).join('/');\n try {\n const file = await openArtifact(root, relativePath);\n try {\n items.push({ path: relativePath, name: entry.name, size: file.stat.size, modifiedAt: file.stat.mtimeMs });\n } finally {\n await file.handle.close();\n }\n } catch (error) {\n if (error instanceof ArtifactAccessError && (error.status === 404 || error.status === 403)) continue;\n throw error;\n }\n }\n }\n };\n await visit(realRoot);\n items.sort(\n (left, right) =>\n right.modifiedAt - left.modifiedAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0),\n );\n return { items };\n } catch (error) {\n if (codeOf(error) === 'ENOENT') return { items: [] };\n return { items: [], error: error instanceof Error ? error.message : 'The artifact folder could not be read.' };\n }\n}\n","import { clearTimeout, setTimeout } from 'node:timers';\n\nimport type { DoomHeadlessActivity, DoomHeadlessExecutionContext, DoomHeadlessHook } from '@agimon-ai/po-core/headless';\nimport type { DoomServerPluginContext } from '@agimon-ai/po-core/serverFacet';\n\nimport { ARTIFACT_REFRESH_MS } from '../../constants/artifacts';\nimport { artifactsChannelType } from '../../types/artifacts';\nimport { artifactRoot, ensureArtifactRoot, listArtifacts } from '../artifactFiles';\n\nexport function createArtifactSession({ host }: DoomServerPluginContext) {\n const environment = Object.freeze({ ...host.context.environment });\n let active: { context: DoomHeadlessExecutionContext; root: string; fingerprint?: string } | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pending: Promise<void> | undefined;\n let closed = false;\n\n const refresh = (): Promise<void> => {\n if (pending) return pending;\n const runtime = active;\n if (!runtime || closed) return Promise.resolve();\n pending = (async () => {\n const snapshot = await listArtifacts(runtime.root);\n if (closed || active !== runtime) return;\n const fingerprint = JSON.stringify(snapshot);\n if (fingerprint === runtime.fingerprint) return;\n runtime.fingerprint = fingerprint;\n host.context.directEvents?.publish(artifactsChannelType, runtime.context.sessionId, snapshot);\n })().finally(() => {\n pending = undefined;\n });\n return pending;\n };\n const schedule = (): void => {\n if (!active || closed) return;\n timer = setTimeout(() => {\n timer = undefined;\n void refresh().finally(schedule);\n }, ARTIFACT_REFRESH_MS);\n };\n const stop = async (): Promise<void> => {\n active = undefined;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n await pending;\n };\n const activity: DoomHeadlessActivity = {\n name: 'artifacts',\n async start(context) {\n if (closed) throw new Error('Artifact session is closed.');\n await stop();\n const root = artifactRoot(context.sessionId, environment);\n await ensureArtifactRoot(root);\n if (closed) throw new Error('Artifact session is closed.');\n active = { context, root };\n await refresh();\n schedule();\n return stop;\n },\n };\n const beforeAgentStart: DoomHeadlessHook = {\n event: 'before_agent_start',\n async handle(event, context) {\n const root = artifactRoot(context.sessionId, environment);\n await ensureArtifactRoot(root);\n const systemPrompt = typeof event.systemPrompt === 'string' ? event.systemPrompt : '';\n const declaration = `Session artifact folder: ${root}`;\n if (systemPrompt.includes(declaration)) return { systemPrompt };\n return {\n systemPrompt:\n `${systemPrompt}\\n\\n${declaration}\\nSave user-designated artifacts and important deliverable documents in this folder, using descriptive filenames. Keep ordinary repository source edits in their existing locations. Do not move existing documents or overwrite artifacts without the user's intent. This folder belongs only to the current session.`.trim(),\n };\n },\n };\n const toolExecutionEnd: DoomHeadlessHook = {\n event: 'tool_execution_end',\n handle(_event, context) {\n return active?.context.sessionId === context.sessionId ? refresh() : undefined;\n },\n };\n return {\n activities: [activity],\n beforeAgentStart,\n toolExecutionEnd,\n onDispose: async () => {\n closed = true;\n await stop();\n },\n };\n}\n","import type { DoomServerPluginContext } from '@agimon-ai/po-core/serverFacet';\n\nimport { createArtifactSession } from '../../../../../services/artifactSession';\n\nexport const createArtifactServerRoot = (context: DoomServerPluginContext) => {\n const runtime = createArtifactSession(context);\n return { value: runtime, activities: runtime.activities, onDispose: runtime.onDispose };\n};\nexport type ArtifactServerScope = ReturnType<typeof createArtifactServerRoot>['value'];\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\n\nimport { createArtifactServerRoot } from './_lib/serverRoot';\n\nexport default defineRoot(createArtifactServerRoot);\n","import type { DoomHubChannel } from '@agimon-ai/po-core/hubChannel';\n\nimport { artifactsChannelType, type ArtifactSnapshot } from '../../types/artifacts';\nimport { artifactRoot, listArtifacts } from '../artifactFiles';\n\nfunction snapshotOf(value: unknown): value is ArtifactSnapshot {\n if (typeof value !== 'object' || value === null) return false;\n const snapshot = value as Partial<ArtifactSnapshot>;\n return (\n Array.isArray(snapshot.items) &&\n (snapshot.error === undefined || typeof snapshot.error === 'string') &&\n snapshot.items.every(\n (item) =>\n typeof item === 'object' &&\n item !== null &&\n typeof item.path === 'string' &&\n typeof item.name === 'string' &&\n typeof item.size === 'number' &&\n Number.isFinite(item.size) &&\n typeof item.modifiedAt === 'number' &&\n Number.isFinite(item.modifiedAt),\n )\n );\n}\n\nexport function createArtifactsChannel(): DoomHubChannel {\n return {\n frameType: artifactsChannelType,\n start(host) {\n const sessions = new Map<string, { snapshot?: ArtifactSnapshot; unsubscribe: () => void }>();\n return {\n payloadFor(scope) {\n return sessions.get(scope.sessionId)?.snapshot;\n },\n sessionAdded(scope) {\n sessions.get(scope.sessionId)?.unsubscribe();\n const state: { snapshot?: ArtifactSnapshot; unsubscribe: () => void } = { unsubscribe: () => undefined };\n sessions.set(scope.sessionId, state);\n state.unsubscribe = host.directEvents.subscribe(\n artifactsChannelType,\n scope.sessionId,\n (snapshot) => {\n if (!snapshotOf(snapshot) || sessions.get(scope.sessionId) !== state) return;\n state.snapshot = snapshot;\n host.publish(scope.sessionId, snapshot);\n },\n { replayLatest: true },\n );\n void (async () => {\n let snapshot: ArtifactSnapshot;\n try {\n snapshot = await listArtifacts(artifactRoot(scope.sessionId, scope.environment ?? {}));\n } catch (error) {\n snapshot = {\n items: [],\n error: error instanceof Error ? error.message : 'The artifact folder could not be read.',\n };\n }\n // A live session event wins over the earlier seed read.\n if (sessions.get(scope.sessionId) !== state || state.snapshot !== undefined) return;\n state.snapshot = snapshot;\n host.publish(scope.sessionId, snapshot);\n })();\n },\n sessionRemoved(sessionId) {\n sessions.get(sessionId)?.unsubscribe();\n sessions.delete(sessionId);\n },\n close() {\n for (const state of sessions.values()) state.unsubscribe();\n sessions.clear();\n },\n };\n },\n };\n}\n","import { defineChannel } from '@agimon-ai/po-core/extensionFile';\n\nimport { createArtifactsChannel } from '../../../services/artifactChannel';\n\nexport default defineChannel(createArtifactsChannel);\n","import type { ApiResponseOf, ApiRoutes } from '@agimon-ai/po-core/web';\n\nimport type { ArtifactPreview, ArtifactSnapshot } from './artifacts';\n\nconst routes = {\n list: {\n method: 'GET',\n path: '/',\n response: undefined as unknown as ApiResponseOf<ArtifactSnapshot>,\n },\n preview: {\n method: 'GET',\n path: '/preview',\n query: ['path'],\n response: undefined as unknown as ApiResponseOf<ArtifactPreview>,\n },\n content: {\n method: 'GET',\n path: '/content',\n query: ['path'],\n raw: true,\n },\n} as const satisfies ApiRoutes;\n\nexport default routes;\n","import path from 'node:path';\n\nimport type { DoomApi, DoomApiContext } from '@agimon-ai/po-core/packageApi';\nimport { Hono } from 'hono';\n\nimport { MAX_CONTENT_BYTES, MAX_PREVIEW_BYTES } from '../../constants/artifacts';\nimport routes from '../../types/apiRoutes';\nimport type { ArtifactPreview } from '../../types/artifacts';\nimport { ArtifactAccessError, artifactRoot, listArtifacts, openArtifact, readArtifactBytes } from '../artifactFiles';\n\nexport function createArtifactsApi(root: string): Hono {\n const app = new Hono();\n app.onError((error, context) =>\n context.json(\n { error: error instanceof ArtifactAccessError ? error.message : 'The artifact could not be read.' },\n error instanceof ArtifactAccessError ? error.status : 500,\n ),\n );\n app.get(routes.list.path, async (context) => {\n const snapshot = await listArtifacts(root);\n return context.json(snapshot, snapshot.error === undefined ? 200 : 500);\n });\n app.get(routes.preview.path, async (context) => {\n const file = await openArtifact(root, context.req.query('path'));\n try {\n const preview: ArtifactPreview = { path: file.path, content: '', mimeType: file.mimeType };\n if (file.stat.size > MAX_PREVIEW_BYTES) {\n return context.json({\n ...preview,\n unavailable: true,\n reason: 'This artifact is too large for a text preview.',\n });\n }\n const bytes = await readArtifactBytes(file.handle, MAX_PREVIEW_BYTES + 1);\n if (bytes.byteLength > MAX_PREVIEW_BYTES)\n return context.json({\n ...preview,\n unavailable: true,\n reason: 'This artifact is too large for a text preview.',\n });\n if (bytes.subarray(0, 8192).includes(0))\n return context.json({ ...preview, unavailable: true, reason: 'This artifact is binary.' });\n return context.json({ ...preview, content: new TextDecoder().decode(bytes) });\n } finally {\n await file.handle.close();\n }\n });\n app.get(routes.content.path, async (context) => {\n const file = await openArtifact(root, context.req.query('path'));\n try {\n if (file.stat.size > MAX_CONTENT_BYTES)\n throw new ArtifactAccessError('This artifact is too large to download.', 413);\n const version = `\"${file.stat.dev}-${file.stat.ino}-${file.stat.size}-${file.stat.mtimeMs}-${file.stat.ctimeMs}\"`;\n const name = encodeURIComponent(path.basename(file.path)).replace(\n /[!'()*]/gu,\n (value) => `%${value.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n const headers = new Headers({\n 'content-type': file.mimeType,\n 'content-disposition': `attachment; filename*=UTF-8''${name}`,\n 'cache-control': 'no-store',\n 'x-content-type-options': 'nosniff',\n 'content-security-policy': \"default-src 'none'; sandbox\",\n 'accept-ranges': 'bytes',\n etag: version,\n });\n const expectedVersion = context.req.header('if-match');\n if (expectedVersion !== undefined && expectedVersion !== version)\n return new Response(null, { status: 412, headers });\n let start = 0;\n let end = file.stat.size - 1;\n const range = context.req.header('range');\n if (range !== undefined) {\n const match = /^bytes=(\\d*)-(\\d*)$/u.exec(range);\n if (\n !match ||\n (!match[1] && !match[2]) ||\n file.stat.size === 0 ||\n [match[1], match[2]].some((value) => value !== '' && !Number.isSafeInteger(Number(value)))\n ) {\n headers.set('content-range', `bytes */${file.stat.size}`);\n return new Response(null, { status: 416, headers });\n }\n if (match[1] === '') {\n const suffix = Number(match[2]);\n start = suffix > 0 ? Math.max(0, file.stat.size - suffix) : file.stat.size;\n } else {\n start = Number(match[1]);\n end = match[2] === '' ? end : Math.min(end, Number(match[2]));\n }\n if (start > end || start >= file.stat.size) {\n headers.set('content-range', `bytes */${file.stat.size}`);\n return new Response(null, { status: 416, headers });\n }\n headers.set('content-range', `bytes ${start}-${end}/${file.stat.size}`);\n }\n const bytes = await readArtifactBytes(file.handle, Math.max(0, end - start + 1), start);\n const after = await file.handle.stat();\n if (after.size !== file.stat.size || after.mtimeMs !== file.stat.mtimeMs || after.ctimeMs !== file.stat.ctimeMs)\n return new Response(null, { status: 412, headers });\n headers.set('content-length', String(bytes.byteLength));\n return new Response(bytes, { status: range === undefined ? 200 : 206, headers });\n } finally {\n await file.handle.close();\n }\n });\n return app;\n}\n\nexport const api: DoomApi = {\n basePath: 'artifacts',\n start(context: DoomApiContext) {\n if (!context.sessionId) throw new Error('Artifacts requires a session identity.');\n const app = createArtifactsApi(artifactRoot(context.sessionId, context.environment ?? {}));\n return { fetch: (request) => app.fetch(request), close: () => undefined };\n },\n};\n","import { defineRoute } from '@agimon-ai/po-core/extensionFile';\n\nimport { api } from '../../../../../../services/artifactsApi';\n\nexport default defineRoute(api);\n","import { defineHook, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/po-core/serverFacet';\n\nimport type { ArtifactServerScope } from '../_lib/serverRoot';\n\nexport default defineHook(\n (context: WithRoot<DoomServerPluginContext, ArtifactServerScope>) => context.root.beforeAgentStart,\n);\n","import { defineHook, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/po-core/serverFacet';\n\nimport type { ArtifactServerScope } from '../_lib/serverRoot';\n\nexport default defineHook(\n (context: WithRoot<DoomServerPluginContext, ArtifactServerScope>) => context.root.toolExecutionEnd,\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport channelArtifacts from '../src/extensions/(backend)/channel/artifacts.server';\nimport apiRoute from '../src/extensions/workspaces/sessions/(backend)/api/artifacts/route.server';\nimport hookBeforeAgentStart from '../src/extensions/workspaces/sessions/(backend)/hook/before-agent-start.server';\nimport hookToolExecutionEnd from '../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n name: '@agimon-ai/po-artifact',\n providerOverrides: composeServerProviderOverrides(rootSession),\n global: () => ({\n get channels(): DoomServerSessionPlugin['channels'] { return [() => via({ frameType: 'artifacts' }, channelArtifacts())]; },\n }),\n workspace: () => ({\n get channels(): DoomServerSessionPlugin['channels'] { return [() => via({ frameType: 'artifacts' }, channelArtifacts())]; },\n }),\n session: async (context) => {\n const scopeSession = await rootSession(context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, contextSession)]; },\n get channels(): DoomServerSessionPlugin['channels'] { return [() => via({ frameType: 'artifacts' }, channelArtifacts())]; },\n get hooks(): DoomServerSessionPlugin['hooks'] { return [via({ event: 'before_agent_start' }, at(hookBeforeAgentStart, contextSession)), via({ event: 'tool_execution_end' }, at(hookToolExecutionEnd, contextSession))]; },\n services: [...(scopeSession.services ?? [])],\n get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default facet;\n"],"mappings":"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"}