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