@agimon-ai/po-author 0.0.0-alpha.0

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/apiContracts.cjs +348 -0
  4. package/dist/apiContracts.cjs.map +1 -0
  5. package/dist/apiContracts.d.mts +53 -0
  6. package/dist/apiContracts.d.mts.map +1 -0
  7. package/dist/apiContracts.mjs +343 -0
  8. package/dist/apiContracts.mjs.map +1 -0
  9. package/dist/authorFacade--Lc599Ft.mjs +84 -0
  10. package/dist/authorFacade--Lc599Ft.mjs.map +1 -0
  11. package/dist/authorFacade-89Eb2-sW.d.mts +132 -0
  12. package/dist/authorFacade-89Eb2-sW.d.mts.map +1 -0
  13. package/dist/authorFacade-BYvlm8Ff.cjs +119 -0
  14. package/dist/authorFacade-BYvlm8Ff.cjs.map +1 -0
  15. package/dist/authorFacade.cjs +27 -0
  16. package/dist/authorFacade.d.mts +3 -0
  17. package/dist/authorFacade.mjs +3 -0
  18. package/dist/extensions/server.cjs +845 -0
  19. package/dist/extensions/server.cjs.map +1 -0
  20. package/dist/extensions/server.d.mts +5 -0
  21. package/dist/extensions/server.d.mts.map +1 -0
  22. package/dist/extensions/server.mjs +838 -0
  23. package/dist/extensions/server.mjs.map +1 -0
  24. package/dist/extensions/web.mjs +4758 -0
  25. package/dist/extensions/web.mjs.map +1 -0
  26. package/dist/index.cjs +101 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.mts +435 -0
  29. package/dist/index.d.mts.map +1 -0
  30. package/dist/index.mjs +70 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/structuredDocuments-B914GLxx.mjs +1064 -0
  33. package/dist/structuredDocuments-B914GLxx.mjs.map +1 -0
  34. package/dist/structuredDocuments-CNac1JmJ.cjs +1316 -0
  35. package/dist/structuredDocuments-CNac1JmJ.cjs.map +1 -0
  36. package/llms.txt +11 -0
  37. package/package.json +140 -0
  38. package/src/prompts/po-use-author/SKILL.md +20 -0
  39. package/src/types/author.ts +61 -0
  40. package/src/types/authorPreview.ts +98 -0
  41. package/src/types/structuredDocuments.ts +63 -0
  42. package/src/types/webAuthor.ts +94 -0
@@ -0,0 +1,838 @@
1
+ import { o as authorBridgeMethod } from "../authorFacade--Lc599Ft.mjs";
2
+ import { A as AUTHOR_ALIAS_PATTERN, C as AuthorBridgeError, D as createAuthorTools, E as AUTHOR_TOOL_WHEN, I as AUTHOR_GUIDANCE, P as parseUseAuthorToolInput, T as createAuthorCatalog, _ as DocumentRequestError, b as authorMinorMode, n as preflightStructuredDocument, r as serializeStructuredDocument, t as parseStructuredDocument, v as documentTooLarge, w as createAuthorBridgeState, y as createAuthorCommand } from "../structuredDocuments-B914GLxx.mjs";
3
+ import { composeServerProviderOverrides, defineServerMethod, defineServerPlugin, readPackageResource } from "@agimon-ai/po-core/serverFacet";
4
+ import { serverMinorModes } from "@agimon-ai/po-minor-mode";
5
+ import { randomUUID } from "node:crypto";
6
+ import path from "node:path";
7
+ import { composeRootHooks, defineChannel, defineCommand as defineCommand$1, defineHook, defineMethod, defineResource, defineRoot, defineRoute, defineTool as defineTool$1 } from "@agimon-ai/po-core/extensionFile";
8
+ import { Hono } from "hono";
9
+ import fs from "node:fs/promises";
10
+ import { apiResponse, defineApiRoutes } from "@agimon-ai/po-core/web";
11
+ //#region src/services/structuredDocuments/document.ts
12
+ const AUTHOR_DOCUMENT_MAX_BYTES = 26214400;
13
+ async function readDocument(cwd, requestedPath) {
14
+ if (typeof requestedPath !== "string" || requestedPath.length === 0 || requestedPath.includes("\0") || path.isAbsolute(requestedPath)) throw new Error("A relative document path is required.");
15
+ const root = await fs.realpath(cwd);
16
+ const candidate = path.resolve(root, requestedPath);
17
+ if (candidate !== root && !candidate.startsWith(`${root}${path.sep}`)) throw new DocumentRequestError("Document path is outside the working directory.", 403);
18
+ const realCandidate = await fs.realpath(candidate);
19
+ if (realCandidate !== root && !realCandidate.startsWith(`${root}${path.sep}`)) throw new DocumentRequestError("Document path is outside the working directory.", 403);
20
+ if ((await fs.stat(realCandidate)).size > 26214400) throw documentTooLarge(`Document exceeds the ${AUTHOR_DOCUMENT_MAX_BYTES} byte preview limit.`);
21
+ return await fs.readFile(realCandidate);
22
+ }
23
+ //#endregion
24
+ //#region src/services/authorCanvasRegistry/index.ts
25
+ const MAX_CANVASES = 32;
26
+ function createAuthorCanvasRegistry(cwd, createBridge) {
27
+ const byAlias = /* @__PURE__ */ new Map();
28
+ const byPath = /* @__PURE__ */ new Map();
29
+ const select = (alias) => {
30
+ if (alias !== void 0) {
31
+ if (!AUTHOR_ALIAS_PATTERN.test(alias)) throw new AuthorBridgeError("Invalid Author canvas alias.", 400);
32
+ const canvas = byAlias.get(alias);
33
+ if (canvas === void 0) throw new AuthorBridgeError(`Unknown Author canvas '${alias}'. Call describe_author_tools({}) for available aliases.`, 404);
34
+ return canvas;
35
+ }
36
+ const canvases = [...byAlias.values()].filter((canvas) => !canvas.closed);
37
+ if (canvases.length !== 1) throw new AuthorBridgeError("Specify a canvas alias. Call describe_author_tools({}) to list canvases.", 409);
38
+ return canvases[0];
39
+ };
40
+ const view = (canvas) => {
41
+ if (canvas.closed) return {
42
+ alias: canvas.alias,
43
+ path: canvas.path,
44
+ status: "unavailable"
45
+ };
46
+ try {
47
+ canvas.bridge.describe();
48
+ canvas.readyOnce = true;
49
+ return {
50
+ alias: canvas.alias,
51
+ path: canvas.path,
52
+ status: "ready"
53
+ };
54
+ } catch {
55
+ return {
56
+ alias: canvas.alias,
57
+ path: canvas.path,
58
+ status: canvas.readyOnce ? "unavailable" : "opening"
59
+ };
60
+ }
61
+ };
62
+ return {
63
+ async open(requestedPath, signal, requestedAlias) {
64
+ signal?.throwIfAborted();
65
+ if (requestedAlias !== void 0 && !AUTHOR_ALIAS_PATTERN.test(requestedAlias)) throw new AuthorBridgeError("Invalid Author canvas alias.", 400);
66
+ const bytes = await readDocument(cwd, requestedPath);
67
+ signal?.throwIfAborted();
68
+ const root = await fs.realpath(cwd);
69
+ const realPath = await fs.realpath(path.resolve(root, requestedPath));
70
+ if (realPath !== root && !realPath.startsWith(`${root}${path.sep}`)) throw new AuthorBridgeError("Document path is outside the working directory.", 400);
71
+ const canonicalPath = path.relative(root, realPath).split(path.sep).join("/");
72
+ const occupied = requestedAlias === void 0 ? void 0 : byAlias.get(requestedAlias);
73
+ if (occupied !== void 0 && occupied.realPath !== realPath) throw new AuthorBridgeError(`Author canvas alias '${requestedAlias}' already names ${occupied.path}.`, 409);
74
+ const existing = byPath.get(realPath);
75
+ if (existing !== void 0) {
76
+ if (existing.closed) existing.readyOnce = false;
77
+ existing.closed = false;
78
+ return {
79
+ path: existing.path,
80
+ byteLength: bytes.byteLength,
81
+ alias: existing.alias,
82
+ status: view(existing).status,
83
+ reused: true
84
+ };
85
+ }
86
+ if (byAlias.size >= MAX_CANVASES) throw new AuthorBridgeError("Too many Author canvases in this conversation.", 409);
87
+ const base = path.basename(canonicalPath).replace(/\.[^.]*$/u, "").toLowerCase().replace(/[^a-z0-9_-]+/gu, "-").replace(/^[^a-z]+/u, "canvas-").slice(0, 48) || "canvas";
88
+ let alias = requestedAlias ?? base;
89
+ for (let suffix = 2; byAlias.has(alias); suffix++) alias = `${base.slice(0, 56)}-${suffix}`;
90
+ const canvas = {
91
+ alias,
92
+ path: canonicalPath,
93
+ realPath,
94
+ bridge: createBridge(),
95
+ closed: false,
96
+ readyOnce: false
97
+ };
98
+ byAlias.set(alias, canvas);
99
+ byPath.set(realPath, canvas);
100
+ return {
101
+ path: canonicalPath,
102
+ byteLength: bytes.byteLength,
103
+ alias,
104
+ status: "opening",
105
+ reused: false
106
+ };
107
+ },
108
+ describe(alias) {
109
+ const canvases = [...byAlias.values()].map(view);
110
+ const target = alias === void 0 && canvases.filter((canvas) => canvas.status !== "unavailable").length !== 1 ? void 0 : select(alias);
111
+ if (target === void 0) return {
112
+ catalogToken: "",
113
+ tools: [],
114
+ canvases,
115
+ nextStep: "Open a file with open_authoring_file, then describe_author_tools({\"alias\":\"canvas-name\"})."
116
+ };
117
+ const status = view(target).status;
118
+ const base = {
119
+ alias: target.alias,
120
+ path: target.path,
121
+ status,
122
+ canvases
123
+ };
124
+ if (status !== "ready") return {
125
+ ...base,
126
+ catalogToken: "",
127
+ tools: [],
128
+ nextStep: status === "opening" ? `Wait for ${target.alias} to be ready, then call describe_author_tools({"alias":"${target.alias}"}).` : `Reopen ${target.path} with open_authoring_file({"path":"${target.path}","alias":"${target.alias}"}).`
129
+ };
130
+ return {
131
+ ...base,
132
+ ...target.bridge.describe()
133
+ };
134
+ },
135
+ invoke(input, signal) {
136
+ const target = select(input.alias);
137
+ if (target.closed) throw new AuthorBridgeError(`Canvas '${target.alias}' is closed. Reopen its file.`, 503);
138
+ return target.bridge.invoke(input, signal).then((result) => ({
139
+ ...result,
140
+ alias: target.alias
141
+ }));
142
+ },
143
+ bridge(alias) {
144
+ const canvas = select(alias);
145
+ if (canvas.closed) throw new AuthorBridgeError(`Canvas '${alias}' is closed. Reopen its file.`, 503);
146
+ return canvas.bridge;
147
+ },
148
+ close(alias) {
149
+ const canvas = select(alias);
150
+ canvas.closed = true;
151
+ canvas.bridge.close();
152
+ canvas.bridge = createBridge();
153
+ },
154
+ closeAll() {
155
+ for (const canvas of byAlias.values()) {
156
+ if (canvas.closed) continue;
157
+ canvas.closed = true;
158
+ canvas.bridge.close();
159
+ canvas.bridge = createBridge();
160
+ }
161
+ },
162
+ dispose() {
163
+ for (const canvas of byAlias.values()) canvas.bridge.close();
164
+ byAlias.clear();
165
+ byPath.clear();
166
+ }
167
+ };
168
+ }
169
+ //#endregion
170
+ //#region src/types/apiRoutes.ts
171
+ /**
172
+ * This package's routes, as data.
173
+ *
174
+ * No scope and no base path. The build reads both off
175
+ * `src/extensions/workspaces/sessions/(backend)/api/author/`, so the mount is
176
+ * stated once, by the folder that creates it. The browser used to restate it:
177
+ * `authorStateUrl` built one URL from `API_BASE_PATH` and the document helper
178
+ * two files away spelled `author` out again as a literal, so the two could
179
+ * disagree and nothing would say so.
180
+ *
181
+ * The order below is the order the three Hono apps register in: `/state` in
182
+ * `authorApi`, the eight bridge routes in `authorBridgeApi`, the three document
183
+ * routes in `authorDocumentApi`. A reviewer can read the two side by side.
184
+ *
185
+ * Response types are named only where a caller reads the body. The bridge
186
+ * routes are called by the hub's author channel over `requestSessionApi`, which
187
+ * has its own message vocabulary and never sees this client.
188
+ */
189
+ var apiRoutes_default = defineApiRoutes({
190
+ state: {
191
+ method: "GET",
192
+ path: "/state",
193
+ response: apiResponse()
194
+ },
195
+ bridgeRegister: {
196
+ method: "POST",
197
+ path: "/bridge/register"
198
+ },
199
+ bridgeCatalog: {
200
+ method: "POST",
201
+ path: "/bridge/catalog"
202
+ },
203
+ /** Long-polls for the next capability request, so it settles when one arrives. */
204
+ bridgeNext: {
205
+ method: "POST",
206
+ path: "/bridge/next"
207
+ },
208
+ bridgeResult: {
209
+ method: "POST",
210
+ path: "/bridge/result"
211
+ },
212
+ bridgeCancelled: {
213
+ method: "POST",
214
+ path: "/bridge/cancelled"
215
+ },
216
+ bridgeDisconnect: {
217
+ method: "POST",
218
+ path: "/bridge/disconnect"
219
+ },
220
+ bridgeClose: {
221
+ method: "POST",
222
+ path: "/bridge/close"
223
+ },
224
+ bridgeDescribe: {
225
+ method: "GET",
226
+ path: "/bridge/describe",
227
+ query: ["alias"]
228
+ },
229
+ bridgeInvoke: {
230
+ method: "POST",
231
+ path: "/bridge/invoke"
232
+ },
233
+ /**
234
+ * Parses one structured document.
235
+ *
236
+ * Answers a byte-length summary instead when the request names no format,
237
+ * which is the agent-side tool path; the page always names one.
238
+ */
239
+ documentsOpen: {
240
+ method: "POST",
241
+ path: "/documents/open",
242
+ query: ["session"],
243
+ response: apiResponse()
244
+ },
245
+ documentsPreflight: {
246
+ method: "POST",
247
+ path: "/documents/preflight",
248
+ query: ["session"],
249
+ response: apiResponse()
250
+ },
251
+ documentsSerialize: {
252
+ method: "POST",
253
+ path: "/documents/serialize",
254
+ query: ["session"],
255
+ response: apiResponse()
256
+ },
257
+ /**
258
+ * The host's own bytes route, not this package's.
259
+ *
260
+ * `host: true` hangs it off the session root rather than under `/plugins`,
261
+ * which is how the cockpit already serves it. Author reads a document's
262
+ * bytes and its digest through it, and writes the edited bytes back to the
263
+ * same path, so both stay raw transfers rather than calls: the body is the
264
+ * file and the digest travels in a header.
265
+ */
266
+ file: {
267
+ method: "GET",
268
+ path: "/file",
269
+ query: ["path"],
270
+ host: true
271
+ }
272
+ });
273
+ //#endregion
274
+ //#region src/types/authorApi.ts
275
+ /**
276
+ * The wire vocabulary this package's API shares with whatever calls it.
277
+ *
278
+ * Paths live in `./apiRoutes`, which the Hono apps register from and the
279
+ * generated client addresses, so neither half can move alone. What stays here
280
+ * is the mount segment the server registers under and the shape the state
281
+ * route answers with.
282
+ */
283
+ /** Where a host mounts this package's API; the segment after /api/plugins/. */
284
+ const API_BASE_PATH = "author";
285
+ //#endregion
286
+ //#region src/extensions/workspaces/sessions/(backend)/api/author/_lib/authorBridgeApi.ts
287
+ function record(value) {
288
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new AuthorBridgeError("Invalid request.", 400);
289
+ return value;
290
+ }
291
+ function text(value, key) {
292
+ const result = record(value)[key];
293
+ if (typeof result !== "string" || result === "") throw new AuthorBridgeError(`Invalid ${key}.`, 400);
294
+ return result;
295
+ }
296
+ function generation(value) {
297
+ const result = record(value).generation;
298
+ if (!Number.isSafeInteger(result) || result < 0) throw new AuthorBridgeError("Invalid generation.", 400);
299
+ return result;
300
+ }
301
+ async function body(request) {
302
+ return record(await request.json());
303
+ }
304
+ function createAuthorBridgeApi(registry) {
305
+ const app = new Hono();
306
+ app.onError((error, context) => context.json({ error: error instanceof Error ? error.message : String(error) }, error instanceof AuthorBridgeError ? error.status : 500));
307
+ app.post(apiRoutes_default.bridgeRegister.path, async (context) => {
308
+ const value = await body(context.req.raw);
309
+ const alias = text(value, "alias");
310
+ return context.json({
311
+ ...registry.bridge(alias).register(text(value, "bindingId"), generation(value)),
312
+ alias
313
+ });
314
+ });
315
+ app.post(apiRoutes_default.bridgeCatalog.path, async (context) => {
316
+ const value = await body(context.req.raw);
317
+ const tools = value.tools;
318
+ if (!Array.isArray(tools)) throw new AuthorBridgeError("Invalid Author catalog.", 400);
319
+ const alias = text(value, "alias");
320
+ return context.json({
321
+ ...registry.bridge(alias).catalog(text(value, "bindingId"), generation(value), text(value, "ownerToken"), tools),
322
+ alias
323
+ });
324
+ });
325
+ app.post(apiRoutes_default.bridgeNext.path, async (context) => {
326
+ const value = await body(context.req.raw);
327
+ const alias = text(value, "alias");
328
+ return context.json({
329
+ ...await registry.bridge(alias).next(text(value, "bindingId"), generation(value), text(value, "ownerToken"), context.req.raw.signal),
330
+ alias
331
+ });
332
+ });
333
+ app.post(apiRoutes_default.bridgeResult.path, async (context) => {
334
+ const value = await body(context.req.raw);
335
+ registry.bridge(text(value, "alias")).result(text(value, "bindingId"), generation(value), text(value, "ownerToken"), text(value, "catalogToken"), text(value, "requestId"), value.result);
336
+ return context.json({ accepted: true });
337
+ });
338
+ app.post(apiRoutes_default.bridgeCancelled.path, async (context) => {
339
+ const value = await body(context.req.raw);
340
+ registry.bridge(text(value, "alias")).cancelled(text(value, "bindingId"), generation(value), text(value, "ownerToken"), text(value, "catalogToken"), text(value, "requestId"));
341
+ return context.json({ accepted: true });
342
+ });
343
+ app.post(apiRoutes_default.bridgeDisconnect.path, async (context) => {
344
+ const value = await body(context.req.raw);
345
+ registry.bridge(text(value, "alias")).disconnect(text(value, "bindingId"), generation(value));
346
+ return context.json({ accepted: true });
347
+ });
348
+ app.post(apiRoutes_default.bridgeClose.path, async (context) => {
349
+ const value = await body(context.req.raw);
350
+ registry.close(text(value, "alias"));
351
+ return context.json({ accepted: true });
352
+ });
353
+ app.get(apiRoutes_default.bridgeDescribe.path, (context) => context.json(registry.describe(context.req.query("alias"))));
354
+ app.post(apiRoutes_default.bridgeInvoke.path, async (context) => {
355
+ const value = parseUseAuthorToolInput(await context.req.raw.json());
356
+ return context.json(await registry.invoke(value, context.req.raw.signal));
357
+ });
358
+ return app;
359
+ }
360
+ //#endregion
361
+ //#region src/extensions/workspaces/sessions/(backend)/api/author/_lib/authorDocumentApi.ts
362
+ const FORMATS = /* @__PURE__ */ new Set([
363
+ "markdown-slides",
364
+ "csv",
365
+ "pptx",
366
+ "xlsx"
367
+ ]);
368
+ function errorResponse(context, error) {
369
+ const message = error instanceof Error ? error.message : "Document operation failed.";
370
+ return context.json({ error: message }, error instanceof DocumentRequestError ? error.status : 422);
371
+ }
372
+ function requestFormat(value) {
373
+ if (typeof value !== "string" || !FORMATS.has(value)) throw new Error("A supported format is required.");
374
+ return value;
375
+ }
376
+ function requestOperations(value) {
377
+ if (value === void 0) return [];
378
+ if (!Array.isArray(value) || value.length > 1e5) throw new Error("A bounded operations array is required.");
379
+ return value.map((operation) => {
380
+ if (typeof operation !== "object" || operation === null || typeof operation.fragmentId !== "string" || typeof operation.replacement !== "string") throw new Error("Each operation requires string fragmentId and replacement fields.");
381
+ return operation;
382
+ });
383
+ }
384
+ async function bodyOf(request) {
385
+ const body = await request.json();
386
+ if (typeof body !== "object" || body === null || Array.isArray(body)) throw new Error("A JSON object is required.");
387
+ return body;
388
+ }
389
+ function createAuthorDocumentApi(options = {}) {
390
+ const app = new Hono();
391
+ const cwd = options.cwd ?? process.cwd();
392
+ app.post(apiRoutes_default.documentsOpen.path, async (context) => {
393
+ try {
394
+ const body = await bodyOf(context.req.raw);
395
+ if (body.format === void 0 && options.open !== void 0) return context.json(await options.open(body.path, context.req.raw.signal, body.alias));
396
+ const bytes = await readDocument(cwd, body.path);
397
+ if (body.format === void 0) return context.json({
398
+ path: body.path,
399
+ byteLength: bytes.byteLength
400
+ });
401
+ return context.json(await parseStructuredDocument(requestFormat(body.format), bytes, body.csvDialect));
402
+ } catch (error) {
403
+ return errorResponse(context, error);
404
+ }
405
+ });
406
+ app.post(apiRoutes_default.documentsPreflight.path, async (context) => {
407
+ try {
408
+ const body = await bodyOf(context.req.raw);
409
+ const bytes = await readDocument(cwd, body.path);
410
+ return context.json(await preflightStructuredDocument(requestFormat(body.format), bytes, requestOperations(body.operations), body.csvDialect));
411
+ } catch (error) {
412
+ return errorResponse(context, error);
413
+ }
414
+ });
415
+ app.post(apiRoutes_default.documentsSerialize.path, async (context) => {
416
+ try {
417
+ const body = await bodyOf(context.req.raw);
418
+ if (typeof body.preflightDigest !== "string") throw new Error("A preflight report digest is required.");
419
+ const bytes = await readDocument(cwd, body.path);
420
+ const serialized = await serializeStructuredDocument(requestFormat(body.format), bytes, requestOperations(body.operations), body.preflightDigest, body.csvDialect);
421
+ return context.json({
422
+ bytes: Buffer.from(serialized.bytes).toString("base64"),
423
+ encoding: "base64",
424
+ manifest: serialized.manifest,
425
+ operationLog: serialized.operationLog
426
+ });
427
+ } catch (error) {
428
+ return errorResponse(context, error);
429
+ }
430
+ });
431
+ return app;
432
+ }
433
+ //#endregion
434
+ //#region src/extensions/workspaces/sessions/(backend)/api/author/_lib/authorApi.ts
435
+ const inactiveState = () => ({
436
+ activation: "inactive",
437
+ capabilityCount: 0
438
+ });
439
+ const createBridge = () => createAuthorBridgeState({
440
+ now: Date.now,
441
+ issueToken: randomUUID,
442
+ scheduleTimeout(callback, delayMs) {
443
+ const timer = setTimeout(callback, delayMs);
444
+ timer.unref?.();
445
+ return () => clearTimeout(timer);
446
+ }
447
+ });
448
+ function createAuthorApi(options = {}) {
449
+ const app = new Hono();
450
+ const registry = options.registry ?? createAuthorCanvasRegistry(options.cwd ?? process.cwd(), createBridge);
451
+ app.get(apiRoutes_default.state.path, (context) => context.json({
452
+ sessionId: options.sessionId ?? null,
453
+ ...(options.readState ?? inactiveState)()
454
+ }));
455
+ app.route("/", createAuthorBridgeApi(registry));
456
+ app.route("/", createAuthorDocumentApi({
457
+ ...options.cwd === void 0 ? {} : { cwd: options.cwd },
458
+ open: (path, signal, alias) => registry.open(path, signal, alias)
459
+ }));
460
+ return app;
461
+ }
462
+ const api = {
463
+ basePath: API_BASE_PATH,
464
+ start(context) {
465
+ if (context.cwd === void 0) throw new Error("Author requires the session working directory.");
466
+ const registry = createAuthorCanvasRegistry(context.cwd, createBridge);
467
+ const app = createAuthorApi({
468
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
469
+ cwd: context.cwd,
470
+ registry
471
+ });
472
+ return {
473
+ fetch: (request) => app.fetch(request),
474
+ close: () => registry.dispose()
475
+ };
476
+ }
477
+ };
478
+ /** The session API and tools share the same browser ownership and pending requests. */
479
+ function createAuthorSessionApi(cwd, sessionId) {
480
+ const registry = createAuthorCanvasRegistry(cwd, createBridge);
481
+ return {
482
+ closeCanvases: () => registry.closeAll(),
483
+ catalog: {
484
+ open: (path, signal, alias) => registry.open(path, signal, alias),
485
+ async describe(signal, alias) {
486
+ signal?.throwIfAborted();
487
+ return registry.describe(alias);
488
+ },
489
+ execute: (input, signal) => registry.invoke(input, signal)
490
+ },
491
+ api: {
492
+ basePath: API_BASE_PATH,
493
+ start() {
494
+ const app = createAuthorApi({
495
+ cwd,
496
+ sessionId,
497
+ registry
498
+ });
499
+ return {
500
+ fetch: (request) => app.fetch(request),
501
+ close: () => registry.dispose()
502
+ };
503
+ }
504
+ }
505
+ };
506
+ }
507
+ //#endregion
508
+ //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
509
+ var root_server_default = defineRoot(({ agent }) => {
510
+ const session = agent ? createAuthorSessionApi(agent.context.cwd, agent.context.sessionId) : void 0;
511
+ const isActive = () => (agent?.context.selection.state?.["minor-mode"] ?? []).includes(authorMinorMode.descriptor.id);
512
+ const mode = agent ? authorMinorMode.createOwner({
513
+ isActive,
514
+ detail: () => "document authoring available",
515
+ async setActive(enabled) {
516
+ const modes = (agent.context.selection.state?.["minor-mode"] ?? []).filter((id) => id !== authorMinorMode.descriptor.id);
517
+ await agent.changeSelection({
518
+ axis: "state",
519
+ key: "minor-mode",
520
+ values: enabled ? [...modes, authorMinorMode.descriptor.id] : modes
521
+ });
522
+ if (!enabled) session?.closeCanvases();
523
+ }
524
+ }) : void 0;
525
+ return {
526
+ value: {
527
+ api: session?.api ?? api,
528
+ catalog: createAuthorCatalog(session?.catalog),
529
+ assertAvailable: () => {
530
+ if (!isActive()) throw new Error("Author mode is not active.");
531
+ },
532
+ service: void 0
533
+ },
534
+ services: mode ? [serverMinorModes([mode])] : []
535
+ };
536
+ });
537
+ //#endregion
538
+ //#region src/types/webAuthor.ts
539
+ const authorChannelType = "author_webmcp";
540
+ //#endregion
541
+ //#region src/services/webAuthorChannel/index.ts
542
+ /**
543
+ * Which route carries each message the browser sends.
544
+ *
545
+ * The paths come from the package's one route table, so the hub cannot address
546
+ * a bridge route the session no longer serves. `release` is absent because it
547
+ * is answered by the disconnect route rather than one of its own.
548
+ */
549
+ const BRIDGE_ROUTE = {
550
+ register: apiRoutes_default.bridgeRegister.path,
551
+ catalog: apiRoutes_default.bridgeCatalog.path,
552
+ result: apiRoutes_default.bridgeResult.path,
553
+ cancelled: apiRoutes_default.bridgeCancelled.path
554
+ };
555
+ function isRecord(value) {
556
+ return typeof value === "object" && value !== null && !Array.isArray(value);
557
+ }
558
+ function browserMessage(value) {
559
+ if (!isRecord(value) || typeof value.kind !== "string" || typeof value.alias !== "string" || !AUTHOR_ALIAS_PATTERN.test(value.alias)) return void 0;
560
+ if (![
561
+ "register",
562
+ "release",
563
+ "catalog",
564
+ "result",
565
+ "cancelled"
566
+ ].includes(value.kind)) return void 0;
567
+ return value;
568
+ }
569
+ async function responsePayload(response) {
570
+ const value = await response.json();
571
+ if (response.ok) return value;
572
+ return {
573
+ kind: "rejected",
574
+ reason: isRecord(value) && typeof value.error === "string" ? value.error : `Author bridge HTTP ${response.status}`
575
+ };
576
+ }
577
+ function createAuthorChannel() {
578
+ const bindings = /* @__PURE__ */ new Map();
579
+ let host;
580
+ let closed = false;
581
+ const keyOf = (sessionId, connectionId, alias) => `${sessionId}\0${connectionId}\0${alias}`;
582
+ const send = async (scope, path, value, signal) => await host.requestSessionApi(scope, {
583
+ basePath: API_BASE_PATH,
584
+ path,
585
+ method: "POST",
586
+ body: JSON.stringify(value),
587
+ ...signal === void 0 ? {} : { signal }
588
+ });
589
+ const publish = (binding, payload) => host?.publishToConnection?.(binding.connectionId, binding.scope.sessionId, {
590
+ ...payload,
591
+ alias: binding.alias
592
+ }) ?? false;
593
+ const poll = async (binding) => {
594
+ binding.poll?.abort();
595
+ const controller = new AbortController();
596
+ binding.poll = controller;
597
+ while (!closed && bindings.get(keyOf(binding.scope.sessionId, binding.connectionId, binding.alias)) === binding) try {
598
+ const payload = await responsePayload(await send(binding.scope, apiRoutes_default.bridgeNext.path, {
599
+ alias: binding.alias,
600
+ bindingId: binding.connectionId,
601
+ generation: binding.generation,
602
+ ownerToken: binding.ownerToken
603
+ }, controller.signal));
604
+ if (!publish(binding, payload) || payload.kind === "rejected") break;
605
+ } catch (error) {
606
+ if (!controller.signal.aborted) host?.onNotice(error instanceof Error ? error.message : String(error));
607
+ break;
608
+ }
609
+ };
610
+ return {
611
+ frameType: authorChannelType,
612
+ lifecycle: "hub",
613
+ receiveWithoutSubscription: true,
614
+ start(channelHost) {
615
+ host = channelHost;
616
+ return {
617
+ payloadFor: () => void 0,
618
+ sessionRemoved(sessionId) {
619
+ for (const [key, binding] of bindings) {
620
+ if (binding.scope.sessionId !== sessionId) continue;
621
+ binding.poll?.abort();
622
+ bindings.delete(key);
623
+ send(binding.scope, apiRoutes_default.bridgeDisconnect.path, {
624
+ alias: binding.alias,
625
+ bindingId: binding.connectionId,
626
+ generation: binding.generation
627
+ }).catch((error) => host?.onNotice(error instanceof Error ? error.message : String(error)));
628
+ }
629
+ },
630
+ close() {
631
+ closed = true;
632
+ for (const binding of bindings.values()) {
633
+ binding.poll?.abort();
634
+ send(binding.scope, apiRoutes_default.bridgeDisconnect.path, {
635
+ alias: binding.alias,
636
+ bindingId: binding.connectionId,
637
+ generation: binding.generation
638
+ }).catch((error) => host?.onNotice(error instanceof Error ? error.message : String(error)));
639
+ }
640
+ bindings.clear();
641
+ }
642
+ };
643
+ },
644
+ receive(scope, payload, connection) {
645
+ const message = browserMessage(payload);
646
+ if (message === void 0 || host === void 0 || closed) return;
647
+ (async () => {
648
+ const key = keyOf(scope.sessionId, connection.connectionId, message.alias);
649
+ const previous = bindings.get(key);
650
+ if (message.kind === "release") {
651
+ if (previous === void 0 || previous.generation !== message.generation) return;
652
+ previous.poll?.abort();
653
+ bindings.delete(key);
654
+ await send(scope, apiRoutes_default.bridgeDisconnect.path, {
655
+ alias: message.alias,
656
+ bindingId: connection.connectionId,
657
+ generation: message.generation
658
+ });
659
+ return;
660
+ }
661
+ const route = BRIDGE_ROUTE[message.kind];
662
+ const reply = await responsePayload(await send(scope, route, {
663
+ ...message,
664
+ bindingId: connection.connectionId
665
+ }));
666
+ if (reply.kind === "accepted") {
667
+ const binding = {
668
+ alias: message.alias,
669
+ scope,
670
+ connectionId: connection.connectionId,
671
+ generation: reply.generation,
672
+ ownerToken: reply.ownerToken
673
+ };
674
+ previous?.poll?.abort();
675
+ bindings.set(key, binding);
676
+ publish(binding, reply);
677
+ poll(binding);
678
+ } else if (reply.kind === "rejected") {
679
+ if (previous !== void 0) publish(previous, reply);
680
+ else host?.publishToConnection?.(connection.connectionId, scope.sessionId, {
681
+ ...reply,
682
+ alias: message.alias
683
+ });
684
+ }
685
+ })().catch((error) => host?.onNotice(error instanceof Error ? error.message : String(error)));
686
+ },
687
+ disconnected(connection) {
688
+ for (const [key, binding] of bindings) {
689
+ if (binding.connectionId !== connection.connectionId) continue;
690
+ binding.poll?.abort();
691
+ bindings.delete(key);
692
+ send(binding.scope, apiRoutes_default.bridgeDisconnect.path, {
693
+ alias: binding.alias,
694
+ bindingId: binding.connectionId,
695
+ generation: binding.generation
696
+ }).catch((error) => host?.onNotice(error instanceof Error ? error.message : String(error)));
697
+ }
698
+ }
699
+ };
700
+ }
701
+ //#endregion
702
+ //#region src/extensions/(backend)/channel/author-webmcp.server.ts
703
+ var author_webmcp_server_default = defineChannel(createAuthorChannel);
704
+ //#endregion
705
+ //#region src/services/authorBridgeMethod/index.ts
706
+ function createAuthorBridgeMethod(scope, host) {
707
+ return defineServerMethod(authorBridgeMethod(scope), ({ sessionId, message }, caller) => {
708
+ if (!caller.connectionId) throw new Error("Author bridge requires an authenticated browser connection.");
709
+ if (!host.context.receiveChannel?.(sessionId, "author_webmcp", message, caller.connectionId)) throw new Error("Author bridge session is outside this mount.");
710
+ return {};
711
+ });
712
+ }
713
+ //#endregion
714
+ //#region src/extensions/(backend)/method/bridge.server.ts
715
+ var bridge_server_default = defineMethod(({ host }) => host.scope === "session" ? { register: () => ({ dispose: () => void 0 }) } : createAuthorBridgeMethod(host.scope, host));
716
+ //#endregion
717
+ //#region src/extensions/workspaces/sessions/(backend)/api/author/route.server.ts
718
+ var route_server_default = defineRoute((context) => context.root.api);
719
+ //#endregion
720
+ //#region src/extensions/workspaces/sessions/(backend)/command/author.server.ts
721
+ var author_server_default = defineCommand$1((context) => createAuthorCommand(context.root.service));
722
+ //#endregion
723
+ //#region src/extensions/workspaces/sessions/(backend)/hook/before-agent-start.server.ts
724
+ var before_agent_start_server_default = defineHook({
725
+ when: {
726
+ state: { "minor-mode": "author" },
727
+ attribution: {
728
+ kind: "minor",
729
+ mode: "author"
730
+ }
731
+ },
732
+ event: "before_agent_start",
733
+ handle(event) {
734
+ return { systemPrompt: `${typeof event.systemPrompt === "string" ? event.systemPrompt : ""}\n\n${AUTHOR_GUIDANCE}`.trim() };
735
+ }
736
+ });
737
+ //#endregion
738
+ //#region src/extensions/workspaces/sessions/(backend)/resource/po-use-author.server.ts
739
+ var po_use_author_server_default = defineResource({
740
+ when: {
741
+ state: { "minor-mode": "author" },
742
+ attribution: {
743
+ kind: "minor",
744
+ mode: "author"
745
+ }
746
+ },
747
+ name: "po-use-author",
748
+ kind: "skill",
749
+ read: () => readPackageResource(import.meta.url, "src/prompts/po-use-author/SKILL.md")
750
+ });
751
+ //#endregion
752
+ //#region src/extensions/workspaces/sessions/(backend)/tool/describe-author-tools.server.ts
753
+ var describe_author_tools_server_default = defineTool$1((context) => ({
754
+ ...createAuthorTools(context.root.catalog, context.root.assertAvailable)[1],
755
+ when: AUTHOR_TOOL_WHEN
756
+ }));
757
+ //#endregion
758
+ //#region src/extensions/workspaces/sessions/(backend)/tool/open-authoring-file.server.ts
759
+ var open_authoring_file_server_default = defineTool$1((context) => ({
760
+ ...createAuthorTools(context.root.catalog, context.root.assertAvailable)[0],
761
+ when: AUTHOR_TOOL_WHEN
762
+ }));
763
+ //#endregion
764
+ //#region src/extensions/workspaces/sessions/(backend)/tool/use-author-tools.server.ts
765
+ var use_author_tools_server_default = defineTool$1((context) => ({
766
+ ...createAuthorTools(context.root.catalog, context.root.assertAvailable)[2],
767
+ when: AUTHOR_TOOL_WHEN
768
+ }));
769
+ //#endregion
770
+ //#region generated/server.ts
771
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
772
+ const via = (identity, value) => value === void 0 ? void 0 : {
773
+ ...identity,
774
+ ...value
775
+ };
776
+ const facet = defineServerPlugin({
777
+ name: "@agimon-ai/po-author",
778
+ providerOverrides: composeServerProviderOverrides(root_server_default),
779
+ global: (context) => ({
780
+ get channels() {
781
+ return [() => via({ frameType: "author_webmcp" }, author_webmcp_server_default())];
782
+ },
783
+ get methods() {
784
+ return [at(bridge_server_default, context)];
785
+ }
786
+ }),
787
+ workspace: (context) => ({
788
+ get channels() {
789
+ return [() => via({ frameType: "author_webmcp" }, author_webmcp_server_default())];
790
+ },
791
+ get methods() {
792
+ return [at(bridge_server_default, context)];
793
+ }
794
+ }),
795
+ session: async (context) => {
796
+ const scopeSession = await root_server_default(context);
797
+ const contextSession = {
798
+ ...context,
799
+ root: scopeSession.value
800
+ };
801
+ return {
802
+ ...composeRootHooks(scopeSession),
803
+ get api() {
804
+ return [at(route_server_default, contextSession)];
805
+ },
806
+ get channels() {
807
+ return [() => via({ frameType: "author_webmcp" }, author_webmcp_server_default())];
808
+ },
809
+ get commands() {
810
+ return [via({ name: "author" }, at(author_server_default, contextSession))];
811
+ },
812
+ get hooks() {
813
+ return [via({ event: "before_agent_start" }, at(before_agent_start_server_default, contextSession))];
814
+ },
815
+ get methods() {
816
+ return [at(bridge_server_default, context)];
817
+ },
818
+ get resources() {
819
+ return [at(po_use_author_server_default, contextSession)];
820
+ },
821
+ get tools() {
822
+ return [
823
+ via({ name: "describe_author_tools" }, at(describe_author_tools_server_default, contextSession)),
824
+ via({ name: "open_authoring_file" }, at(open_authoring_file_server_default, contextSession)),
825
+ via({ name: "use_author_tools" }, at(use_author_tools_server_default, contextSession))
826
+ ];
827
+ },
828
+ services: [...scopeSession.services ?? []],
829
+ get activities() {
830
+ return [...scopeSession.activities ?? []];
831
+ }
832
+ };
833
+ }
834
+ });
835
+ //#endregion
836
+ export { facet as default, facet };
837
+
838
+ //# sourceMappingURL=server.mjs.map