@agimon-ai/po-plan 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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/apiContracts.cjs +57 -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 +52 -0
  8. package/dist/apiContracts.mjs.map +1 -0
  9. package/dist/config-BC-Tou05.mjs +25 -0
  10. package/dist/config-BC-Tou05.mjs.map +1 -0
  11. package/dist/config-D0iTexGm.cjs +42 -0
  12. package/dist/config-D0iTexGm.cjs.map +1 -0
  13. package/dist/config-DWo57HKB.d.mts +13 -0
  14. package/dist/config-DWo57HKB.d.mts.map +1 -0
  15. package/dist/config.cjs +12 -0
  16. package/dist/config.d.mts +2 -0
  17. package/dist/config.mjs +2 -0
  18. package/dist/extensions/mcp-ui.mjs +46 -0
  19. package/dist/extensions/mcp-ui.mjs.map +1 -0
  20. package/dist/extensions/mcp.cjs +93 -0
  21. package/dist/extensions/mcp.cjs.map +1 -0
  22. package/dist/extensions/mcp.d.mts +5 -0
  23. package/dist/extensions/mcp.d.mts.map +1 -0
  24. package/dist/extensions/mcp.mjs +88 -0
  25. package/dist/extensions/mcp.mjs.map +1 -0
  26. package/dist/extensions/server.cjs +1025 -0
  27. package/dist/extensions/server.cjs.map +1 -0
  28. package/dist/extensions/server.d.mts +5 -0
  29. package/dist/extensions/server.d.mts.map +1 -0
  30. package/dist/extensions/server.mjs +993 -0
  31. package/dist/extensions/server.mjs.map +1 -0
  32. package/dist/extensions/web.mjs +861 -0
  33. package/dist/extensions/web.mjs.map +1 -0
  34. package/dist/fableFlow-BDc8a__Y.mjs +214 -0
  35. package/dist/fableFlow-BDc8a__Y.mjs.map +1 -0
  36. package/dist/fableFlow-CzfrLPzJ.cjs +279 -0
  37. package/dist/fableFlow-CzfrLPzJ.cjs.map +1 -0
  38. package/dist/fableFlow.cjs +13 -0
  39. package/dist/fableFlow.d.mts +2 -0
  40. package/dist/fableFlow.mjs +2 -0
  41. package/dist/index-C8e7IK60.d.mts +68 -0
  42. package/dist/index-C8e7IK60.d.mts.map +1 -0
  43. package/dist/index-C9QBG9cw.d.mts +51 -0
  44. package/dist/index-C9QBG9cw.d.mts.map +1 -0
  45. package/dist/index.cjs +31 -0
  46. package/dist/index.d.mts +4 -0
  47. package/dist/index.mjs +4 -0
  48. package/dist/logSinkTelemetry.cjs +42 -0
  49. package/dist/logSinkTelemetry.cjs.map +1 -0
  50. package/dist/logSinkTelemetry.d.mts +33 -0
  51. package/dist/logSinkTelemetry.d.mts.map +1 -0
  52. package/dist/logSinkTelemetry.mjs +40 -0
  53. package/dist/logSinkTelemetry.mjs.map +1 -0
  54. package/dist/planConfig.cjs +190 -0
  55. package/dist/planConfig.cjs.map +1 -0
  56. package/dist/planConfig.d.mts +47 -0
  57. package/dist/planConfig.d.mts.map +1 -0
  58. package/dist/planConfig.mjs +186 -0
  59. package/dist/planConfig.mjs.map +1 -0
  60. package/dist/prompts-DUVUPNZd.cjs +103 -0
  61. package/dist/prompts-DUVUPNZd.cjs.map +1 -0
  62. package/dist/prompts-DnVJGtEC.mjs +68 -0
  63. package/dist/prompts-DnVJGtEC.mjs.map +1 -0
  64. package/dist/prompts.cjs +8 -0
  65. package/dist/prompts.d.mts +2 -0
  66. package/dist/prompts.mjs +2 -0
  67. package/llms.txt +9 -0
  68. package/package.json +164 -0
  69. package/src/prompts/po-use-plan/SKILL.md +44 -0
  70. package/src/types/planApi.ts +125 -0
  71. package/src/types/planSettings.ts +78 -0
@@ -0,0 +1,993 @@
1
+ import { l as resolvePlanningPlansDirectory, r as loadDoomConfig } from "../config-BC-Tou05.mjs";
2
+ import { i as buildFlavorPlanningPrompt, o as buildPlanModeBasePrompt } from "../prompts-DnVJGtEC.mjs";
3
+ import { Buffer } from "node:buffer";
4
+ import { composeServerProviderOverrides, defineServerPlugin, readPackageResource } from "@agimon-ai/po-core/serverFacet";
5
+ import { composeRootHooks, defineRoot, defineRoute, defineRoutedContribution, defineToolRestriction } from "@agimon-ai/po-core/extensionFile";
6
+ import { Type } from "typebox";
7
+ import { createHash } from "node:crypto";
8
+ import fs, { mkdir, readFile, writeFile } from "node:fs/promises";
9
+ import { Hono } from "hono";
10
+ import fs$1 from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import { piAgentDirectory } from "@agimon-ai/po-core/piSettings";
14
+ import { apiResponse, defineApiRoutes } from "@agimon-ai/po-core/web";
15
+ import { DOOM_VOICE_AUTO_MODE_ID } from "@agimon-ai/po-core/voiceTools";
16
+ import { defineMinorMode, serverMinorModes } from "@agimon-ai/po-minor-mode";
17
+ //#region src/schemas/mcpTools.ts
18
+ const markdown = Type.String({ minLength: 1 });
19
+ Type.Object({ markdown }, { additionalProperties: false });
20
+ const writePlanParameters = Type.Object({ markdown: Type.Optional(markdown) }, { additionalProperties: false });
21
+ //#endregion
22
+ //#region src/services/mcpTools/index.ts
23
+ const PLAN_MCP_TOOLS_SERVICE = "doom/plan-mcp-tools";
24
+ function mountMcpTools(tools) {
25
+ return (context) => {
26
+ context.plugin((providerContext) => {
27
+ providerContext.provide(PLAN_MCP_TOOLS_SERVICE, {
28
+ get: (name) => tools.find((tool) => tool.name === name),
29
+ names: () => tools.map((tool) => tool.name)
30
+ });
31
+ });
32
+ };
33
+ }
34
+ //#endregion
35
+ //#region src/services/planPointer/index.ts
36
+ /**
37
+ * Record and read where write_plan left this session's plan file.
38
+ *
39
+ * Pi already gives every session an agent directory, so the records live under
40
+ * it rather than in a second place a reader would have to know about. The
41
+ * posture matches the plan file itself, 0700 on the directory and 0600 on the
42
+ * record: a plan is a private working document, not something to leave
43
+ * world-readable in a shared home.
44
+ */
45
+ const POINTER_DIRECTORY = "doom-plan";
46
+ const PRIVATE_DIRECTORY_MODE = 448;
47
+ const PRIVATE_FILE_MODE = 384;
48
+ function isRecord$1(value) {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ }
51
+ var PlanPointerService = class {
52
+ env;
53
+ homeDirectory;
54
+ constructor(options = {}) {
55
+ this.env = options.env ?? {};
56
+ this.homeDirectory = options.homeDirectory ?? os.homedir();
57
+ }
58
+ /** Where the records live; exposed so a test can look without guessing. */
59
+ directory() {
60
+ return path.join(piAgentDirectory(this.env, this.homeDirectory), POINTER_DIRECTORY);
61
+ }
62
+ /**
63
+ * The record for one session. The id is hashed rather than used as written:
64
+ * it reaches here from a host that may word it however it likes, and a
65
+ * filename is not the place to find that out.
66
+ */
67
+ pathFor(sessionId) {
68
+ return path.join(this.directory(), `${this.hash(sessionId)}.json`);
69
+ }
70
+ read(sessionId) {
71
+ let raw;
72
+ try {
73
+ raw = fs$1.readFileSync(this.pathFor(sessionId), "utf8");
74
+ } catch {
75
+ return;
76
+ }
77
+ let parsed;
78
+ try {
79
+ parsed = JSON.parse(raw);
80
+ } catch {
81
+ return;
82
+ }
83
+ if (!isRecord$1(parsed)) return void 0;
84
+ const { path: planPath, title, writtenAt, planId } = parsed;
85
+ if (typeof planPath !== "string" || !path.isAbsolute(planPath)) return void 0;
86
+ if (typeof title !== "string" || typeof writtenAt !== "string") return void 0;
87
+ return {
88
+ path: planPath,
89
+ title,
90
+ writtenAt,
91
+ ...typeof planId === "string" ? { planId } : {}
92
+ };
93
+ }
94
+ write(sessionId, record) {
95
+ fs$1.mkdirSync(this.directory(), {
96
+ recursive: true,
97
+ mode: PRIVATE_DIRECTORY_MODE
98
+ });
99
+ fs$1.writeFileSync(this.pathFor(sessionId), JSON.stringify(record), { mode: PRIVATE_FILE_MODE });
100
+ }
101
+ clear(sessionId) {
102
+ fs$1.rmSync(this.pathFor(sessionId), { force: true });
103
+ }
104
+ hash(value) {
105
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
106
+ }
107
+ };
108
+ //#endregion
109
+ //#region src/types/apiRoutes.ts
110
+ /**
111
+ * This package's routes, as data.
112
+ *
113
+ * No scope and no base path. The build reads both off
114
+ * `src/extensions/workspaces/sessions/(backend)/api/plans/`, so the mount is
115
+ * stated once, by the folder that creates it. The paths used to be written
116
+ * here as helpers, again in the contract, and again inside every URL the page
117
+ * built, and the copies were free to disagree.
118
+ *
119
+ * This is the one file the routes, the Hono app and the browser all read, so a
120
+ * path cannot move on one side alone.
121
+ *
122
+ * Neither route takes a query or a path parameter: a session has one current
123
+ * plan, so `current` answers it and `save` takes the manual write back.
124
+ */
125
+ var apiRoutes_default = defineApiRoutes({
126
+ current: {
127
+ method: "GET",
128
+ path: "/current",
129
+ response: apiResponse()
130
+ },
131
+ save: {
132
+ method: "PUT",
133
+ path: "/content",
134
+ response: apiResponse()
135
+ }
136
+ });
137
+ //#endregion
138
+ //#region src/types/planApi.ts
139
+ /**
140
+ * The wire vocabulary this package's API shares with whatever calls it.
141
+ *
142
+ * It lives under src/types because that is the one server root a browser
143
+ * bundle may read: the routes answer with these shapes and the cockpit plugin
144
+ * reads them, so neither half can drift from the other. The paths themselves
145
+ * are not here; they are declared once in `./apiRoutes`, which the Hono app
146
+ * and the generated client both read.
147
+ *
148
+ * Two routes, and neither takes a path. A session has one current plan, so
149
+ * `current` answers it and `content` takes the manual save back; the file they
150
+ * act on is the one write_plan recorded, never one the page named. That is
151
+ * also why there is nothing to escape here: a reader cannot address a second
152
+ * file through these URLs.
153
+ */
154
+ /** Where a host mounts this package's API; the segment after /api/plugins/. */
155
+ const API_BASE_PATH = "plans";
156
+ /**
157
+ * Footer status key the session publishes once a plan exists, which is what
158
+ * makes the cockpit's activity group appear at all. It outlives plan mode: the
159
+ * plan is most worth reading while the agent implements it.
160
+ */
161
+ const PLAN_STATUS_KEY = "doom-plan-document";
162
+ /** The selector contract shared by complete_plan and its cockpit composer prompt. */
163
+ const PLAN_REVIEW_TITLE = "Plan complete. What would you like to do?";
164
+ const EXIT_PLAN_MODE_CHOICE = "Exit plan mode and start implementation";
165
+ const CONTINUE_PLANNING_CHOICE = "Continue planning";
166
+ const EXIT_PLAN_DECISION = "exit";
167
+ const CONTINUE_PLAN_DECISION = "continue";
168
+ /**
169
+ * The label the reader sees paired with the decision the tool acts on. A headless
170
+ * client answers with `value`, so a facet never has to recognise the wording; Pi's
171
+ * own `ui.select` carries labels only, so its half maps the label back itself.
172
+ */
173
+ const PLAN_REVIEW_CHOICES = [{
174
+ label: EXIT_PLAN_MODE_CHOICE,
175
+ value: EXIT_PLAN_DECISION
176
+ }, {
177
+ label: CONTINUE_PLANNING_CHOICE,
178
+ value: CONTINUE_PLAN_DECISION
179
+ }];
180
+ PLAN_REVIEW_CHOICES.map((choice) => choice.label);
181
+ /** Between the plan's title and the stamp that marks a rewrite. */
182
+ const STATUS_SEPARATOR = " · ";
183
+ /** The status line the session publishes; the stamp changes on every write. */
184
+ function formatPlanStatus(title, stamp) {
185
+ return `${title}${STATUS_SEPARATOR}${stamp}`;
186
+ }
187
+ //#endregion
188
+ //#region src/services/planApi/index.ts
189
+ /**
190
+ * This package's HTTP surface: the session's current plan, and the manual save.
191
+ *
192
+ * Each path comes from the route table the browser reads, so a route the page
193
+ * calls and the route this serves cannot be spelled apart. Both are relative
194
+ * to the mount its host gives it, so nothing here repeats where it was mounted. The host is one session's own server, which is what
195
+ * makes the session id available without the page supplying it.
196
+ *
197
+ * Neither route takes a path. A session has exactly one current plan, and the
198
+ * file both routes act on is the one the pointer names, so a page can address
199
+ * the plan and nothing else; there is no traversal question to answer because
200
+ * there is no path to traverse.
201
+ *
202
+ * The save refuses unless the reader proves they were looking at the content
203
+ * that is still on disk. The agent may rewrite the plan while a tab is open,
204
+ * and silently discarding whichever side finished second is the one outcome
205
+ * worth ruling out.
206
+ */
207
+ /** Past this, the plan is reported rather than carried. */
208
+ const MAX_PLAN_BYTES = 1048576;
209
+ const NO_PLAN = "This session has not written a plan yet.";
210
+ const STALE_SAVE = "The plan changed since it was opened.";
211
+ const NOT_A_FILE = "The plan file is no longer a regular file, so it was not written.";
212
+ function hashOf(content) {
213
+ return createHash("sha256").update(content, "utf8").digest("hex");
214
+ }
215
+ /** Reads the plan as the source view will show it, or says why it cannot. */
216
+ async function readPlan(planPath) {
217
+ let raw;
218
+ try {
219
+ raw = await fs.readFile(planPath);
220
+ } catch {
221
+ return {
222
+ content: "",
223
+ hash: "",
224
+ unavailable: true,
225
+ reason: "the plan file no longer exists"
226
+ };
227
+ }
228
+ if (raw.byteLength > 1048576) return {
229
+ content: "",
230
+ hash: "",
231
+ unavailable: true,
232
+ reason: `the plan is larger than ${MAX_PLAN_BYTES} bytes`
233
+ };
234
+ const content = raw.toString("utf8");
235
+ return {
236
+ content,
237
+ hash: hashOf(content),
238
+ unavailable: false
239
+ };
240
+ }
241
+ function createPlanApi(options = {}) {
242
+ const app = new Hono();
243
+ const pointers = options.pointers ?? new PlanPointerService({ env: process.env });
244
+ const pointerFor = () => options.sessionId === void 0 ? void 0 : pointers.read(options.sessionId);
245
+ app.get(apiRoutes_default.current.path, async (context) => {
246
+ const pointer = pointerFor();
247
+ if (pointer === void 0) return context.json({ error: NO_PLAN }, 404);
248
+ const view = {
249
+ path: pointer.path,
250
+ title: pointer.title,
251
+ writtenAt: pointer.writtenAt,
252
+ ...pointer.planId === void 0 ? {} : { planId: pointer.planId },
253
+ ...await readPlan(pointer.path)
254
+ };
255
+ return context.json(view);
256
+ });
257
+ app.put(apiRoutes_default.save.path, async (context) => {
258
+ let request;
259
+ try {
260
+ request = await context.req.json();
261
+ } catch {
262
+ return context.json({ error: "The save body is not JSON." }, 400);
263
+ }
264
+ if (typeof request.content !== "string" || typeof request.expectedHash !== "string") return context.json({ error: "A save carries the content to write and the hash it was read at." }, 400);
265
+ const pointer = pointerFor();
266
+ if (pointer === void 0) return context.json({ error: NO_PLAN }, 404);
267
+ const current = await readPlan(pointer.path);
268
+ if (current.unavailable) return context.json({ error: current.reason ?? NO_PLAN }, 409);
269
+ if (current.hash !== request.expectedHash) return context.json({
270
+ error: STALE_SAVE,
271
+ hash: current.hash
272
+ }, 409);
273
+ const stats = await fs.lstat(pointer.path);
274
+ if (!stats.isFile() || stats.nlink !== 1) return context.json({ error: NOT_A_FILE }, 409);
275
+ await fs.writeFile(pointer.path, request.content, {
276
+ encoding: "utf8",
277
+ mode: 384
278
+ });
279
+ return context.json({ hash: hashOf(request.content) });
280
+ });
281
+ return app;
282
+ }
283
+ /** The named export a host imports from this package's built entry. */
284
+ const api = {
285
+ basePath: API_BASE_PATH,
286
+ start(context) {
287
+ const app = createPlanApi(context.sessionId === void 0 ? {} : { sessionId: context.sessionId });
288
+ return {
289
+ fetch: (request) => app.fetch(request),
290
+ close: () => void 0
291
+ };
292
+ }
293
+ };
294
+ //#endregion
295
+ //#region src/services/optionalTeamServices/index.ts
296
+ const DOOM_SUBAGENT_POLICY_SERVICE = "doom/subagent-policy";
297
+ function readDoomSubagentPolicyService(ctx) {
298
+ return ctx.get(DOOM_SUBAGENT_POLICY_SERVICE);
299
+ }
300
+ //#endregion
301
+ //#region src/services/planServerSession/index.ts
302
+ const RECORD_DEBUG_EVIDENCE_TOOL = "record_debug_evidence";
303
+ const WRITE_PLAN_TOOL = "write_plan";
304
+ const COMPLETE_PLAN_TOOL = "complete_plan";
305
+ const PLAN_MODE_ID = "plan";
306
+ const PLAN_MODEL_SNAPSHOT = "plan-server-model-snapshot";
307
+ const PLAN_FLAVOR_SNAPSHOT = "plan-server-flavor";
308
+ const PLAN_DEBUG_EVIDENCE = "plan-debug-evidence";
309
+ const PLAN_DOCUMENT = "plan-document";
310
+ /** This host supports UI review without narrated decisions. */
311
+ const PLAN_SERVER_CAPABILITIES = {
312
+ completePlanTakesDecision: false,
313
+ narratesReview: false,
314
+ fableAvailable: false
315
+ };
316
+ const PLAN_SUBAGENT_POLICY = {
317
+ owner: "@agimon-ai/po-plan",
318
+ allowedTools: [
319
+ "read",
320
+ "bash",
321
+ "grep",
322
+ "find",
323
+ "ls",
324
+ "mcp"
325
+ ],
326
+ requiredTools: ["bash"],
327
+ allowMcpTools: true,
328
+ allowedExternalProfiles: [],
329
+ denyExtensions: false
330
+ };
331
+ function serverPlanSubagentPolicy(host) {
332
+ return (context) => {
333
+ context.inject([DOOM_SUBAGENT_POLICY_SERVICE], (child) => {
334
+ const service = readDoomSubagentPolicyService(child);
335
+ if (!service) return void 0;
336
+ let handle;
337
+ const sync = () => {
338
+ if (modeSelected(host)) {
339
+ if (handle) handle.update(PLAN_SUBAGENT_POLICY);
340
+ else handle = service.register(PLAN_SUBAGENT_POLICY);
341
+ } else {
342
+ handle?.dispose();
343
+ handle = void 0;
344
+ }
345
+ };
346
+ sync();
347
+ child.effect(() => host.subscribeSelection(sync));
348
+ return () => handle?.dispose();
349
+ });
350
+ };
351
+ }
352
+ function modeSelected(host) {
353
+ return (host.context.selection.state?.["minor-mode"] ?? []).includes(PLAN_MODE_ID);
354
+ }
355
+ function output(value, isError = false) {
356
+ return {
357
+ content: [{
358
+ type: "text",
359
+ text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
360
+ }],
361
+ details: value,
362
+ ...isError ? { isError: true } : {}
363
+ };
364
+ }
365
+ function createPlanServerSession(host) {
366
+ let flavor = "normal";
367
+ let debugEvidence;
368
+ let modeOwner;
369
+ const selected = () => modeSelected(host);
370
+ const homeDirectory = () => host.context.environment.HOME ?? os.homedir();
371
+ const plansDirectory = () => resolvePlanningPlansDirectory(loadDoomConfig(host.context.repoRoot, homeDirectory()).modes?.planning?.plansDirectory, host.context.repoRoot, homeDirectory());
372
+ const pointers = new PlanPointerService({ env: host.context.environment });
373
+ /**
374
+ * The activity group's line, and the pointer its panel reads.
375
+ *
376
+ * A cockpit drives this facet and never the Pi runtime, so both were written
377
+ * only on the Pi side: the group never appeared, and when it did its panel
378
+ * had no pointer to answer from.
379
+ */
380
+ const announcePlanDocument = (filePath, title, writtenAt) => {
381
+ try {
382
+ pointers.write(host.context.sessionId, {
383
+ path: filePath,
384
+ title,
385
+ writtenAt
386
+ });
387
+ } catch {}
388
+ host.context.client.setStatus(PLAN_STATUS_KEY, formatPlanStatus(title, planStampOf(writtenAt)));
389
+ };
390
+ const modeState = () => {
391
+ const active = selected();
392
+ return {
393
+ activation: active ? "active" : "inactive",
394
+ condition: "ready",
395
+ ...active ? {
396
+ detail: `${flavor} - read only`,
397
+ modelContextVariant: flavor
398
+ } : {},
399
+ actions: [{
400
+ id: "activate",
401
+ enabled: true
402
+ }, {
403
+ id: "deactivate",
404
+ enabled: active,
405
+ ...active ? {} : { disabledReason: "Plan mode is inactive." }
406
+ }]
407
+ };
408
+ };
409
+ const publishMode = () => modeOwner?.publish();
410
+ const restoreModel = async () => {
411
+ const session = host.context.session;
412
+ const data = (await session.entries({
413
+ type: "custom",
414
+ customType: PLAN_MODEL_SNAPSHOT,
415
+ limit: 1
416
+ }))[0]?.data;
417
+ if (data === void 0 || data === null) return;
418
+ if (typeof data !== "object" || !("thinkingLevel" in data) || ![
419
+ "off",
420
+ "minimal",
421
+ "low",
422
+ "medium",
423
+ "high",
424
+ "xhigh",
425
+ "max"
426
+ ].includes(String(data.thinkingLevel))) throw new Error("The saved planning model settings are invalid.");
427
+ if (!session.setModelSettings) throw new Error("This session cannot restore its model settings.");
428
+ await session.setModelSettings(data);
429
+ await session.appendCustomEntry(PLAN_MODEL_SNAPSHOT, null);
430
+ };
431
+ const selectPlan = async (enabled, nextFlavor = flavor) => {
432
+ const wasEnabled = selected();
433
+ const session = host.context.session;
434
+ if (enabled && !wasEnabled) {
435
+ const config = loadDoomConfig(host.context.repoRoot, homeDirectory()).modes?.planning?.main;
436
+ if (config?.model || config?.thinking) {
437
+ if (!session.readModelSettings || !session.setModelSettings) throw new Error("This session cannot apply planning model settings.");
438
+ const previous = await session.readModelSettings();
439
+ const settings = {};
440
+ if (config.model) {
441
+ const separator = config.model.indexOf("/");
442
+ const provider = separator > 0 ? config.model.slice(0, separator) : previous.model?.provider;
443
+ if (!provider) throw new Error("The planning model needs a provider.");
444
+ settings.model = {
445
+ provider,
446
+ id: separator > 0 ? config.model.slice(separator + 1) : config.model
447
+ };
448
+ }
449
+ if (config.thinking) settings.thinkingLevel = config.thinking;
450
+ await session.appendCustomEntry(PLAN_MODEL_SNAPSHOT, previous);
451
+ try {
452
+ await session.setModelSettings(settings);
453
+ } catch (error) {
454
+ await session.setModelSettings(previous);
455
+ await session.appendCustomEntry(PLAN_MODEL_SNAPSHOT, null);
456
+ throw error;
457
+ }
458
+ }
459
+ } else if (!enabled && wasEnabled) await restoreModel();
460
+ const modes = (host.context.selection.state?.["minor-mode"] ?? []).filter((mode) => mode !== PLAN_MODE_ID);
461
+ await host.changeSelection({
462
+ axis: "state",
463
+ key: "minor-mode",
464
+ values: enabled ? [...modes, PLAN_MODE_ID] : modes
465
+ });
466
+ flavor = nextFlavor;
467
+ await session.appendCustomEntry(PLAN_FLAVOR_SNAPSHOT, enabled ? { flavor } : null);
468
+ publishMode();
469
+ };
470
+ /** Newest value for a custom entry type, or undefined when none was recorded. */
471
+ const latestEntry = async (customType) => {
472
+ return (await host.context.session.entries({
473
+ type: "custom",
474
+ customType,
475
+ limit: 1
476
+ }))[0]?.data ?? void 0;
477
+ };
478
+ const restorePlanState = async () => {
479
+ const saved = await latestEntry(PLAN_FLAVOR_SNAPSHOT);
480
+ if (saved !== void 0 && saved !== null && typeof saved === "object" && "flavor" in saved) {
481
+ const candidate = String(saved.flavor);
482
+ if (candidate === "fable") flavor = "normal";
483
+ else if (candidate === "normal" || candidate === "debug" || candidate === "code-review") flavor = candidate;
484
+ }
485
+ const evidence = await latestEntry(PLAN_DEBUG_EVIDENCE);
486
+ if (evidence === void 0 || evidence === null) return;
487
+ try {
488
+ debugEvidence = parseDebugEvidencePacket(evidence);
489
+ } catch {
490
+ debugEvidence = void 0;
491
+ }
492
+ };
493
+ modeOwner = defineMinorMode({
494
+ descriptor: {
495
+ source: "@agimon-ai/po-plan",
496
+ id: PLAN_MODE_ID,
497
+ label: "Plan",
498
+ description: "Read-only planning with normal, debug, and code review flavors.",
499
+ order: 40,
500
+ actions: [{
501
+ id: "activate",
502
+ label: "Activate",
503
+ description: "Activate plan mode or switch its planning flavor.",
504
+ contexts: ["headless"],
505
+ parameters: [{
506
+ name: "flavor",
507
+ label: "Flavor",
508
+ kind: "enum",
509
+ required: true,
510
+ choices: [
511
+ {
512
+ value: "normal",
513
+ label: "Normal"
514
+ },
515
+ {
516
+ value: "debug",
517
+ label: "Debug"
518
+ },
519
+ {
520
+ value: "code-review",
521
+ label: "Code Review"
522
+ }
523
+ ]
524
+ }]
525
+ }, {
526
+ id: "deactivate",
527
+ label: "Deactivate",
528
+ description: "Exit plan mode and restore the previous agent configuration.",
529
+ contexts: ["headless"],
530
+ parameters: []
531
+ }]
532
+ },
533
+ state: () => modeState(),
534
+ async handleAction(_runtime, actionId, argumentsValue, { signal }) {
535
+ signal.throwIfAborted();
536
+ if (actionId === "activate") {
537
+ const nextFlavor = argumentsValue.flavor;
538
+ if (nextFlavor !== "normal" && nextFlavor !== "debug" && nextFlavor !== "code-review") throw new Error("A valid plan flavor is required.");
539
+ await selectPlan(true, nextFlavor);
540
+ return { message: `Plan mode is active with the ${nextFlavor} flavor.` };
541
+ }
542
+ if (actionId === "deactivate") {
543
+ await selectPlan(false);
544
+ return { message: "Plan mode deactivated." };
545
+ }
546
+ throw new Error(`Unknown plan mode action: ${actionId}`);
547
+ }
548
+ }).createOwner(void 0);
549
+ return {
550
+ services: [serverMinorModes([modeOwner]), serverPlanSubagentPolicy(host)],
551
+ resources: [{
552
+ when: {
553
+ state: { "minor-mode": PLAN_MODE_ID },
554
+ attribution: {
555
+ kind: "minor",
556
+ mode: PLAN_MODE_ID
557
+ }
558
+ },
559
+ name: "po-use-plan",
560
+ kind: "skill",
561
+ read: () => readPackageResource(import.meta.url, "src/prompts/po-use-plan/SKILL.md")
562
+ }],
563
+ tools: [
564
+ {
565
+ when: {
566
+ state: { "minor-mode": PLAN_MODE_ID },
567
+ attribution: {
568
+ kind: "minor",
569
+ mode: PLAN_MODE_ID
570
+ }
571
+ },
572
+ name: RECORD_DEBUG_EVIDENCE_TOOL,
573
+ label: "Record Debug Evidence",
574
+ description: "Record bounded debug evidence as optional planning context.",
575
+ parameters: {
576
+ type: "object",
577
+ properties: {
578
+ issue: { type: "string" },
579
+ expectedBehavior: { type: "string" },
580
+ reproductionAttempt: { type: "string" },
581
+ actualBehavior: { type: "string" },
582
+ logs: {
583
+ type: "array",
584
+ items: { type: "string" }
585
+ },
586
+ correlatedTraceEvidence: {
587
+ type: "array",
588
+ items: { type: "string" }
589
+ },
590
+ processOutput: {
591
+ type: "array",
592
+ items: { type: "string" }
593
+ },
594
+ browserConsoleEvidence: {
595
+ type: "array",
596
+ items: { type: "string" }
597
+ },
598
+ correlationIds: {
599
+ type: "array",
600
+ items: { type: "string" }
601
+ },
602
+ timestamps: {
603
+ type: "array",
604
+ items: { type: "string" }
605
+ },
606
+ verifiedFacts: {
607
+ type: "array",
608
+ items: { type: "string" }
609
+ },
610
+ hypotheses: {
611
+ type: "array",
612
+ items: { type: "string" }
613
+ },
614
+ unavailableEvidence: {
615
+ type: "array",
616
+ items: { type: "string" }
617
+ }
618
+ },
619
+ required: ["issue"],
620
+ additionalProperties: false
621
+ },
622
+ executionMode: "serial",
623
+ async execute(_toolCallId, parameters) {
624
+ try {
625
+ const parsedEvidence = parseDebugEvidencePacket(parameters);
626
+ await host.context.session.appendCustomEntry(PLAN_DEBUG_EVIDENCE, parsedEvidence);
627
+ debugEvidence = parsedEvidence;
628
+ return output({ recorded: true });
629
+ } catch (error) {
630
+ return output(error instanceof Error ? error.message : String(error), true);
631
+ }
632
+ }
633
+ },
634
+ {
635
+ when: {
636
+ state: { "minor-mode": PLAN_MODE_ID },
637
+ attribution: {
638
+ kind: "minor",
639
+ mode: PLAN_MODE_ID
640
+ }
641
+ },
642
+ name: WRITE_PLAN_TOOL,
643
+ label: "Write Plan",
644
+ description: "Save the supplied Markdown plan, or the plan already presented in the local session.",
645
+ parameters: writePlanParameters,
646
+ executionMode: "serial",
647
+ async execute(toolCallId, parameters, signal) {
648
+ try {
649
+ signal?.throwIfAborted();
650
+ let content = typeof parameters === "object" && parameters !== null && "markdown" in parameters ? parameters.markdown : void 0;
651
+ if (content !== void 0 && typeof content !== "string") throw new Error("Plan Markdown must be a string.");
652
+ if (content === void 0) content = visiblePlanForToolCall(await host.context.session.entries(), toolCallId);
653
+ if (content === void 0) {
654
+ if (host.context.selection.state?.["minor-mode"]?.includes(DOOM_VOICE_AUTO_MODE_ID)) throw new Error("Supply Markdown to write_plan during autonomous Voice; UI input is unavailable.");
655
+ const requested = await host.context.client.request({
656
+ kind: "input",
657
+ title: "Implementation plan",
658
+ multiline: true
659
+ }, signal);
660
+ content = typeof requested === "string" ? requested : "";
661
+ }
662
+ if (typeof content !== "string" || !content.trim()) throw new Error("No implementation plan content was supplied.");
663
+ const directory = plansDirectory();
664
+ await mkdir(directory, {
665
+ recursive: true,
666
+ mode: 448
667
+ });
668
+ const title = planTitleOf(content);
669
+ const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
670
+ const filename = `${planTitleSlug(content)}-${Date.now()}.md`;
671
+ const filePath = path.join(directory, filename);
672
+ await writeFile(filePath, `${content.trim()}\n`, {
673
+ encoding: "utf8",
674
+ mode: 384,
675
+ flag: "wx"
676
+ });
677
+ await host.context.session.appendCustomEntry(PLAN_DOCUMENT, {
678
+ path: filePath,
679
+ content: content.trim(),
680
+ title,
681
+ writtenAt
682
+ });
683
+ announcePlanDocument(filePath, title, writtenAt);
684
+ await selectPlan(true);
685
+ const saved = {
686
+ path: filePath,
687
+ written: true
688
+ };
689
+ return {
690
+ ...output(saved),
691
+ structuredContent: saved
692
+ };
693
+ } catch (error) {
694
+ return output(error instanceof Error ? error.message : String(error), true);
695
+ }
696
+ }
697
+ },
698
+ {
699
+ when: {
700
+ state: { "minor-mode": PLAN_MODE_ID },
701
+ attribution: {
702
+ kind: "minor",
703
+ mode: PLAN_MODE_ID
704
+ }
705
+ },
706
+ name: COMPLETE_PLAN_TOOL,
707
+ label: "Complete Plan",
708
+ description: "Request exit-or-continue approval in the local Po UI. Requires the user to interact there; this is not a ChatGPT approval dialog.",
709
+ parameters: {
710
+ type: "object",
711
+ properties: {},
712
+ additionalProperties: false
713
+ },
714
+ executionMode: "serial",
715
+ async execute(_toolCallId, _parameters, signal) {
716
+ try {
717
+ if (host.context.selection.state?.["minor-mode"]?.includes(DOOM_VOICE_AUTO_MODE_ID)) throw new Error("UI plan review is unavailable during autonomous Voice. Wait for explicit user direction.");
718
+ const exited = await host.context.client.request({
719
+ kind: "select",
720
+ title: PLAN_REVIEW_TITLE,
721
+ options: PLAN_REVIEW_CHOICES.map((choice) => ({ ...choice }))
722
+ }, signal) === EXIT_PLAN_DECISION;
723
+ await host.context.session.appendCustomEntry("plan-review", { decision: exited ? EXIT_PLAN_DECISION : CONTINUE_PLAN_DECISION });
724
+ await selectPlan(!exited);
725
+ return {
726
+ content: [{
727
+ type: "text",
728
+ text: exited ? PLAN_EXIT_APPROVED_TEXT : PLAN_CONTINUE_TEXT
729
+ }],
730
+ details: { exited }
731
+ };
732
+ } catch (error) {
733
+ return output(error instanceof Error ? error.message : String(error), true);
734
+ }
735
+ }
736
+ }
737
+ ],
738
+ hooks: [
739
+ {
740
+ when: {
741
+ state: { "minor-mode": PLAN_MODE_ID },
742
+ attribution: {
743
+ kind: "minor",
744
+ mode: PLAN_MODE_ID
745
+ }
746
+ },
747
+ event: "before_agent_start",
748
+ async handle(event) {
749
+ const prompt = typeof event.systemPrompt === "string" ? event.systemPrompt : "";
750
+ const directory = plansDirectory();
751
+ const sections = [buildPlanModeBasePrompt(directory, PLAN_SERVER_CAPABILITIES), buildFlavorPlanningPrompt(flavor, directory, debugEvidence)];
752
+ const saved = await latestEntry(PLAN_DOCUMENT);
753
+ if (saved !== void 0 && saved !== null && typeof saved === "object" && "path" in saved) {
754
+ const document = saved;
755
+ let content = document.content ?? "";
756
+ try {
757
+ content = await readFile(document.path, "utf8");
758
+ } catch {
759
+ content = document.content ?? "";
760
+ }
761
+ if (content.trim()) sections.push(`[CURRENT PLAN]\nSource: ${document.path}\n\n${content}`);
762
+ }
763
+ return { systemPrompt: `${prompt}\n\n${sections.join("\n\n")}`.trim() };
764
+ }
765
+ },
766
+ {
767
+ event: "session_start",
768
+ async handle() {
769
+ await restorePlanState();
770
+ const pointer = pointers.read(host.context.sessionId);
771
+ if (pointer) host.context.client.setStatus(PLAN_STATUS_KEY, formatPlanStatus(pointer.title, planStampOf(pointer.writtenAt)));
772
+ if (!selected()) await restoreModel();
773
+ }
774
+ },
775
+ {
776
+ event: "context",
777
+ async handle(event) {
778
+ if (!Array.isArray(event.messages)) return;
779
+ return { messages: event.messages.filter((message) => message.customType !== PLAN_MODE_CONTEXT) };
780
+ }
781
+ }
782
+ ]
783
+ };
784
+ }
785
+ const DEFAULT_PLAN_TITLE = "implementation-plan";
786
+ const PLAN_TITLE_MAX_LENGTH = 64;
787
+ const DEBUG_EVIDENCE_MAX_BYTES = 32768;
788
+ const DEBUG_EVIDENCE_MAX_TEXT_BYTES = 4096;
789
+ const DEBUG_EVIDENCE_MAX_ITEMS = 32;
790
+ const PLAN_MODE_CONTEXT = "agent-harness-plan-mode-context";
791
+ const PLAN_EXIT_APPROVED_TEXT = "The user approved exiting plan mode. Full tool access is restored. Begin implementing the approved plan.";
792
+ const PLAN_CONTINUE_TEXT = "The user chose to continue planning. Remain read-only and refine the plan.";
793
+ /** The plan's own heading, as the status line and the pointer record word it. */
794
+ function planTitleOf(markdown) {
795
+ return markdown.match(/^#\s+(.+?)\s*$/m)?.[1] ?? DEFAULT_PLAN_TITLE;
796
+ }
797
+ function planTitleSlug(markdown) {
798
+ return planTitleOf(markdown).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, PLAN_TITLE_MAX_LENGTH) || DEFAULT_PLAN_TITLE;
799
+ }
800
+ /**
801
+ * How the status line words when a plan was written. A malformed timestamp is
802
+ * shown as it came rather than as an invalid date: the stamp's other job is to
803
+ * change on a rewrite, and it can still do that.
804
+ */
805
+ function planStampOf(writtenAt) {
806
+ const at = new Date(writtenAt);
807
+ return Number.isNaN(at.getTime()) ? writtenAt : at.toLocaleTimeString();
808
+ }
809
+ function visiblePlanForToolCall(entries, toolCallId) {
810
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
811
+ const entry = entries[index];
812
+ if (!entry || typeof entry !== "object") continue;
813
+ const candidate = entry;
814
+ if (candidate.type !== "message" || candidate.message?.role !== "assistant") continue;
815
+ if (!Array.isArray(candidate.message.content)) continue;
816
+ const toolIndex = candidate.message.content.findIndex((block) => {
817
+ if (!block || typeof block !== "object") return false;
818
+ const content = block;
819
+ return content.type === "toolCall" && content.id === toolCallId && content.name === WRITE_PLAN_TOOL;
820
+ });
821
+ if (toolIndex < 0) continue;
822
+ return candidate.message.content.slice(0, toolIndex).flatMap((block) => {
823
+ if (!block || typeof block !== "object") return [];
824
+ const content = block;
825
+ return content.type === "text" && typeof content.text === "string" ? [content.text] : [];
826
+ }).join("\n\n").trim() || void 0;
827
+ }
828
+ }
829
+ function isRecord(value) {
830
+ return typeof value === "object" && value !== null && !Array.isArray(value);
831
+ }
832
+ function isString(value) {
833
+ return typeof value === "string";
834
+ }
835
+ function boundedDebugText(value, field, required) {
836
+ if (!isString(value)) throw new Error(`${field} must be a string.`);
837
+ const text = value.trim();
838
+ if (required && !text) throw new Error(`${field} must not be empty.`);
839
+ if (Buffer.byteLength(text, "utf8") > DEBUG_EVIDENCE_MAX_TEXT_BYTES) throw new Error(`${field} exceeds the bounded debug evidence limit.`);
840
+ return text;
841
+ }
842
+ function boundedDebugList(value, field) {
843
+ if (!Array.isArray(value) || value.length > DEBUG_EVIDENCE_MAX_ITEMS) throw new Error(`${field} must be a bounded string array.`);
844
+ return value.map((item, index) => boundedDebugText(item, `${field}[${index}]`, false));
845
+ }
846
+ function optionalDebugText(value, field) {
847
+ return value === void 0 ? "" : boundedDebugText(value, field, false);
848
+ }
849
+ function optionalDebugList(value, field) {
850
+ return value === void 0 ? [] : boundedDebugList(value, field);
851
+ }
852
+ function assertExactObjectKeys(value, keys, field) {
853
+ const allowed = new Set(keys);
854
+ const unexpected = Object.keys(value).find((key) => !allowed.has(key));
855
+ if (unexpected) throw new Error(`${field} contains unsupported field '${unexpected}'.`);
856
+ }
857
+ function parseDebugEvidencePacket(value) {
858
+ if (!isRecord(value)) throw new Error("Debug evidence must be an object.");
859
+ assertExactObjectKeys(value, [
860
+ "issue",
861
+ "expectedBehavior",
862
+ "reproductionAttempt",
863
+ "actualBehavior",
864
+ "logs",
865
+ "correlatedTraceEvidence",
866
+ "processOutput",
867
+ "browserConsoleEvidence",
868
+ "correlationIds",
869
+ "timestamps",
870
+ "verifiedFacts",
871
+ "hypotheses",
872
+ "unavailableEvidence"
873
+ ], "Debug evidence");
874
+ const packet = {
875
+ issue: boundedDebugText(value.issue, "issue", true),
876
+ expectedBehavior: optionalDebugText(value.expectedBehavior, "expectedBehavior"),
877
+ reproductionAttempt: optionalDebugText(value.reproductionAttempt, "reproductionAttempt"),
878
+ actualBehavior: optionalDebugText(value.actualBehavior, "actualBehavior"),
879
+ logs: optionalDebugList(value.logs, "logs"),
880
+ correlatedTraceEvidence: optionalDebugList(value.correlatedTraceEvidence, "correlatedTraceEvidence"),
881
+ processOutput: optionalDebugList(value.processOutput, "processOutput"),
882
+ browserConsoleEvidence: optionalDebugList(value.browserConsoleEvidence, "browserConsoleEvidence"),
883
+ correlationIds: optionalDebugList(value.correlationIds, "correlationIds"),
884
+ timestamps: optionalDebugList(value.timestamps, "timestamps"),
885
+ verifiedFacts: optionalDebugList(value.verifiedFacts, "verifiedFacts"),
886
+ hypotheses: optionalDebugList(value.hypotheses, "hypotheses"),
887
+ unavailableEvidence: optionalDebugList(value.unavailableEvidence, "unavailableEvidence")
888
+ };
889
+ if (Buffer.byteLength(JSON.stringify(packet), "utf8") > DEBUG_EVIDENCE_MAX_BYTES) throw new Error("Debug evidence exceeds the bounded packet size.");
890
+ return packet;
891
+ }
892
+ //#endregion
893
+ //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
894
+ var root_server_default = defineRoot(({ agent }) => {
895
+ const value = {
896
+ api: [api],
897
+ ...agent ? createPlanServerSession(agent) : {}
898
+ };
899
+ return {
900
+ value,
901
+ services: [...value.services ?? [], ...value.tools?.length ? [mountMcpTools(value.tools)] : []]
902
+ };
903
+ });
904
+ //#endregion
905
+ //#region src/extensions/workspaces/sessions/(backend)/api/plans/route.server.ts
906
+ var route_server_default = defineRoute((context) => context.root.api[0]);
907
+ //#endregion
908
+ //#region src/extensions/workspaces/sessions/(backend)/hook/beforeAgentStart.server.ts
909
+ var beforeAgentStart_server_default = defineRoutedContribution((context) => context.root.hooks?.[0], { cardinality: "optional" });
910
+ //#endregion
911
+ //#region src/extensions/workspaces/sessions/(backend)/hook/context.server.ts
912
+ var context_server_default = defineRoutedContribution((context) => context.root.hooks?.[2], { cardinality: "optional" });
913
+ //#endregion
914
+ //#region src/extensions/workspaces/sessions/(backend)/hook/sessionStart.server.ts
915
+ var sessionStart_server_default = defineRoutedContribution((context) => context.root.hooks?.[1], { cardinality: "optional" });
916
+ //#endregion
917
+ //#region src/extensions/workspaces/sessions/(backend)/resource/poUsePlan.server.ts
918
+ var poUsePlan_server_default = defineRoutedContribution((context) => context.root.resources?.[0], { cardinality: "optional" });
919
+ //#endregion
920
+ //#region src/extensions/workspaces/sessions/(backend)/tool-restriction/plan.server.ts
921
+ var plan_server_default = defineToolRestriction({
922
+ when: { state: { "minor-mode": DOOM_VOICE_AUTO_MODE_ID } },
923
+ excludedTools: ["complete_plan"]
924
+ });
925
+ //#endregion
926
+ //#region src/extensions/workspaces/sessions/(backend)/tool-restriction/planReadOnly.server.ts
927
+ var planReadOnly_server_default = defineToolRestriction({
928
+ when: { state: { "minor-mode": "plan" } },
929
+ excludedTools: ["edit", "write"]
930
+ });
931
+ //#endregion
932
+ //#region src/extensions/workspaces/sessions/(backend)/tool/completePlan.server.ts
933
+ var completePlan_server_default = defineRoutedContribution((context) => context.root.tools?.[2], { cardinality: "optional" });
934
+ //#endregion
935
+ //#region src/extensions/workspaces/sessions/(backend)/tool/recordDebugEvidence.server.ts
936
+ var recordDebugEvidence_server_default = defineRoutedContribution((context) => context.root.tools?.[0], { cardinality: "optional" });
937
+ //#endregion
938
+ //#region src/extensions/workspaces/sessions/(backend)/tool/writePlan.server.ts
939
+ var writePlan_server_default = defineRoutedContribution((context) => context.root.tools?.[1], { cardinality: "optional" });
940
+ //#endregion
941
+ //#region generated/server.ts
942
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
943
+ /* v8 ignore next -- generated normalization branch */
944
+ const defined = (values) => values.filter((value) => value !== void 0);
945
+ const via = (identity, value) => value === void 0 ? void 0 : {
946
+ ...identity,
947
+ ...value
948
+ };
949
+ const facet = defineServerPlugin({
950
+ name: "@agimon-ai/po-plan",
951
+ providerOverrides: composeServerProviderOverrides(root_server_default),
952
+ session: async (context) => {
953
+ const scopeSession = await root_server_default(context);
954
+ const contextSession = {
955
+ ...context,
956
+ root: scopeSession.value
957
+ };
958
+ return {
959
+ ...composeRootHooks(scopeSession),
960
+ get api() {
961
+ return [at(route_server_default, contextSession)];
962
+ },
963
+ get hooks() {
964
+ return defined([
965
+ via({ event: "before_agent_start" }, at(beforeAgentStart_server_default, contextSession)),
966
+ via({ event: "context" }, at(context_server_default, contextSession)),
967
+ via({ event: "session_start" }, at(sessionStart_server_default, contextSession))
968
+ ]);
969
+ },
970
+ get resources() {
971
+ return defined([at(poUsePlan_server_default, contextSession)]);
972
+ },
973
+ get toolRestrictions() {
974
+ return [at(plan_server_default, contextSession), at(planReadOnly_server_default, contextSession)];
975
+ },
976
+ get tools() {
977
+ return defined([
978
+ via({ name: "complete_plan" }, at(completePlan_server_default, contextSession)),
979
+ via({ name: "record_debug_evidence" }, at(recordDebugEvidence_server_default, contextSession)),
980
+ via({ name: "write_plan" }, at(writePlan_server_default, contextSession))
981
+ ]);
982
+ },
983
+ services: [...scopeSession.services ?? []],
984
+ get activities() {
985
+ return [...scopeSession.activities ?? []];
986
+ }
987
+ };
988
+ }
989
+ });
990
+ //#endregion
991
+ export { facet as default, facet };
992
+
993
+ //# sourceMappingURL=server.mjs.map