@agimon-ai/doompi-plan 0.0.1-alpha.39 → 0.0.1-alpha.40

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 (73) hide show
  1. package/README.md +17 -2
  2. package/dist/adapters/node/planPointer.cjs +2 -0
  3. package/dist/adapters/node/planPointer.cjs.map +1 -0
  4. package/dist/adapters/node/planPointer.mjs +2 -0
  5. package/dist/adapters/node/planPointer.mjs.map +1 -0
  6. package/dist/adapters/planApi.cjs +2 -0
  7. package/dist/adapters/planApi.cjs.map +1 -0
  8. package/dist/adapters/planApi.d.cts +15 -0
  9. package/dist/adapters/planApi.d.cts.map +1 -0
  10. package/dist/adapters/planApi.d.mts +15 -0
  11. package/dist/adapters/planApi.d.mts.map +1 -0
  12. package/dist/adapters/planApi.mjs +2 -0
  13. package/dist/adapters/planApi.mjs.map +1 -0
  14. package/dist/adapters/telemetry/logSinkTelemetry.cjs +1 -1
  15. package/dist/adapters/telemetry/logSinkTelemetry.cjs.map +1 -1
  16. package/dist/adapters/telemetry/logSinkTelemetry.d.cts +1 -0
  17. package/dist/adapters/telemetry/logSinkTelemetry.d.cts.map +1 -1
  18. package/dist/adapters/telemetry/logSinkTelemetry.d.mts +1 -0
  19. package/dist/adapters/telemetry/logSinkTelemetry.d.mts.map +1 -1
  20. package/dist/adapters/telemetry/logSinkTelemetry.mjs +1 -1
  21. package/dist/adapters/telemetry/logSinkTelemetry.mjs.map +1 -1
  22. package/dist/planConfig.cjs +1 -1
  23. package/dist/planConfig.d.cts +3 -2
  24. package/dist/planConfig.d.mts +3 -2
  25. package/dist/planConfig.mjs +1 -1
  26. package/dist/schemas/plan/planConfig.cjs +1 -1
  27. package/dist/schemas/plan/planConfig.cjs.map +1 -1
  28. package/dist/schemas/plan/planConfig.d.cts +8 -12
  29. package/dist/schemas/plan/planConfig.d.cts.map +1 -1
  30. package/dist/schemas/plan/planConfig.d.mts +8 -12
  31. package/dist/schemas/plan/planConfig.d.mts.map +1 -1
  32. package/dist/schemas/plan/planConfig.mjs +1 -1
  33. package/dist/schemas/plan/planConfig.mjs.map +1 -1
  34. package/dist/services/planMode.cjs +4 -4
  35. package/dist/services/planMode.cjs.map +1 -1
  36. package/dist/services/planMode.d.cts +3 -0
  37. package/dist/services/planMode.d.cts.map +1 -1
  38. package/dist/services/planMode.d.mts +3 -0
  39. package/dist/services/planMode.d.mts.map +1 -1
  40. package/dist/services/planMode.mjs +4 -4
  41. package/dist/services/planMode.mjs.map +1 -1
  42. package/dist/sessionApi.cjs +1 -0
  43. package/dist/sessionApi.d.cts +2 -0
  44. package/dist/sessionApi.d.mts +2 -0
  45. package/dist/sessionApi.mjs +1 -0
  46. package/dist/types/planApi.cjs +2 -0
  47. package/dist/types/planApi.cjs.map +1 -0
  48. package/dist/types/planApi.d.cts +18 -0
  49. package/dist/types/planApi.d.cts.map +1 -0
  50. package/dist/types/planApi.d.mts +18 -0
  51. package/dist/types/planApi.d.mts.map +1 -0
  52. package/dist/types/planApi.mjs +2 -0
  53. package/dist/types/planApi.mjs.map +1 -0
  54. package/dist/types/planPointer.d.cts +27 -0
  55. package/dist/types/planPointer.d.cts.map +1 -0
  56. package/dist/types/planPointer.d.mts +27 -0
  57. package/dist/types/planPointer.d.mts.map +1 -0
  58. package/dist/types/planSettings.cjs +2 -0
  59. package/dist/types/planSettings.cjs.map +1 -0
  60. package/dist/types/planSettings.d.cts +28 -0
  61. package/dist/types/planSettings.d.cts.map +1 -0
  62. package/dist/types/planSettings.d.mts +28 -0
  63. package/dist/types/planSettings.d.mts.map +1 -0
  64. package/dist/types/planSettings.mjs +2 -0
  65. package/dist/types/planSettings.mjs.map +1 -0
  66. package/package.json +21 -6
  67. package/src/types/planApi.ts +131 -0
  68. package/src/types/planSettings.ts +78 -0
  69. package/web/PlanActivitySection.tsx +62 -0
  70. package/web/PlanPanel.tsx +190 -0
  71. package/web/index.ts +18 -2
  72. package/web/planApi.ts +80 -0
  73. package/web/planSettings.ts +30 -0
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # @agimon-ai/doompi-plan
2
2
 
3
- A reviewable planning lifecycle for Pi with narrowed tools, plan storage, normal, debug, and
4
- Fable-assisted flows, and explicit completion.
3
+ Create and review stored plans in Pi with narrowed tools, explicit completion, and normal, debug, or Fable-assisted flows.
5
4
 
6
5
  Part of the [DoomPi distribution](https://www.npmjs.com/package/@agimon-ai/doompi).
7
6
 
@@ -60,6 +59,21 @@ it does not activate an omitted feedback layer.
60
59
  In autonomous Voice mode, review choices are narrated, the turn terminates, and the next user
61
60
  message carries the decision.
62
61
 
62
+ ## Read and edit the plan in the cockpit
63
+
64
+ Once `write_plan` succeeds, a `plan` group appears in the cockpit's activity dock and stays for the
65
+ rest of the session, including after Plan mode is exited: that is when the agent starts
66
+ implementing, which is when the plan is most worth having open. The row opens a temporary tab that
67
+ renders the plan as Markdown, with a `source` view that edits it and saves back to the plan file.
68
+
69
+ The agent reads the plan file at the start of every turn, so a saved edit is what it implements. A
70
+ save carries the hash the reader loaded and is refused if the agent rewrote the plan in the
71
+ meantime, rather than overwriting it.
72
+
73
+ The cockpit reaches the plan through this package's session API, mounted at `/api/plugin/plans`.
74
+ Because a host imports the built entry, a change here needs `pnpm build` and `doompi sync` before a
75
+ running cockpit sees it.
76
+
63
77
  ## Configure models and storage
64
78
 
65
79
  Put planning settings in the repository's `.doom/config.yaml` or the global
@@ -85,6 +99,7 @@ calls consume provider quota.
85
99
 
86
100
  The root exports planning configuration schemas, prompts, Fable flow helpers, and the Plan mode
87
101
  service. Focused exports include `/config`, `/planConfig`, `/planMode`, `/fableFlow`, and `/prompts`.
102
+ `/session-api` is the entry a host mounts for the cockpit's plan tab.
88
103
 
89
104
  ## Development
90
105
 
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:crypto"),n=require("node:fs");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);const a=[`.pi`,`agent`];function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var s=class{env;homeDirectory;constructor(e={}){this.env=e.env??process.env,this.homeDirectory=e.homeDirectory??r.default.homedir()}directory(){let e=this.env.PI_CODING_AGENT_DIR||i.default.join(this.homeDirectory,...a);return i.default.join(e,`doom-plan`)}pathFor(e){return i.default.join(this.directory(),`${this.hash(e)}.json`)}read(e){let t;try{t=n.default.readFileSync(this.pathFor(e),`utf8`)}catch{return}let r;try{r=JSON.parse(t)}catch{return}if(!o(r))return;let{path:a,title:s,writtenAt:c,planId:l}=r;if(!(typeof a!=`string`||!i.default.isAbsolute(a))&&typeof s==`string`&&typeof c==`string`)return{path:a,title:s,writtenAt:c,...typeof l==`string`?{planId:l}:{}}}write(e,t){n.default.mkdirSync(this.directory(),{recursive:!0,mode:448}),n.default.writeFileSync(this.pathFor(e),JSON.stringify(t),{mode:384})}clear(e){n.default.rmSync(this.pathFor(e),{force:!0})}hash(e){return(0,t.createHash)(`sha256`).update(e).digest(`hex`).slice(0,16)}};exports.NodePlanPointerAdapter=s;
2
+ //# sourceMappingURL=planPointer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planPointer.cjs","names":["os","path","fs","createHash"],"sources":["../../../src/adapters/node/planPointer.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { PlanPointerRecord } from '../../types/planApi.ts';\nimport type { PlanPointerPort } from '../../types/planPointer.ts';\n\n/**\n * Record and read where write_plan left this session's plan file.\n *\n * One technology per adapter. This is where node enters the package; the\n * services that depend on the capability keep importing the port.\n *\n * Pi already gives every session an agent directory, so the records live under\n * it rather than in a second place a reader would have to know about. The\n * posture matches the plan file itself, 0700 on the directory and 0600 on the\n * record: a plan is a private working document, not something to leave\n * world-readable in a shared home.\n */\n\nconst POINTER_DIRECTORY = 'doom-plan';\nconst AGENT_DIRECTORY = ['.pi', 'agent'];\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nexport interface NodePlanPointerOptions {\n env?: NodeJS.ProcessEnv;\n homeDirectory?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport class NodePlanPointerAdapter implements PlanPointerPort {\n private readonly env: NodeJS.ProcessEnv;\n private readonly homeDirectory: string;\n\n constructor(options: NodePlanPointerOptions = {}) {\n this.env = options.env ?? process.env;\n this.homeDirectory = options.homeDirectory ?? os.homedir();\n }\n\n /** Where the records live; exposed so a test can look without guessing. */\n directory(): string {\n const agentDirectory = this.env.PI_CODING_AGENT_DIR || path.join(this.homeDirectory, ...AGENT_DIRECTORY);\n return path.join(agentDirectory, POINTER_DIRECTORY);\n }\n\n /**\n * The record for one session. The id is hashed rather than used as written:\n * it reaches here from a host that may word it however it likes, and a\n * filename is not the place to find that out.\n */\n pathFor(sessionId: string): string {\n return path.join(this.directory(), `${this.hash(sessionId)}.json`);\n }\n\n read(sessionId: string): PlanPointerRecord | undefined {\n let raw: string;\n try {\n raw = fs.readFileSync(this.pathFor(sessionId), 'utf8');\n } catch {\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n const { path: planPath, title, writtenAt, planId } = parsed;\n // A record half-written by an interrupted run reads as no plan at all,\n // which is a state the cockpit already handles.\n if (typeof planPath !== 'string' || !path.isAbsolute(planPath)) return undefined;\n if (typeof title !== 'string' || typeof writtenAt !== 'string') return undefined;\n return { path: planPath, title, writtenAt, ...(typeof planId === 'string' ? { planId } : {}) };\n }\n\n write(sessionId: string, record: PlanPointerRecord): void {\n fs.mkdirSync(this.directory(), { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n fs.writeFileSync(this.pathFor(sessionId), JSON.stringify(record), { mode: PRIVATE_FILE_MODE });\n }\n\n clear(sessionId: string): void {\n fs.rmSync(this.pathFor(sessionId), { force: true });\n }\n\n private hash(value: string): string {\n return createHash('sha256').update(value).digest('hex').slice(0, 16);\n }\n}\n"],"mappings":"iNAoBA,MACM,EAAkB,CAAC,MAAO,OAAO,EASvC,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,IAAa,EAAb,KAA+D,CAC7D,IACA,cAEA,YAAY,EAAkC,CAAC,EAAG,CAChD,KAAK,IAAM,EAAQ,KAAO,QAAQ,IAClC,KAAK,cAAgB,EAAQ,eAAiBA,EAAAA,QAAG,QAAQ,CAC3D,CAGA,WAAoB,CAClB,IAAM,EAAiB,KAAK,IAAI,qBAAuBC,EAAAA,QAAK,KAAK,KAAK,cAAe,GAAG,CAAe,EACvG,OAAOA,EAAAA,QAAK,KAAK,EAAgB,WAAiB,CACpD,CAOA,QAAQ,EAA2B,CACjC,OAAOA,EAAAA,QAAK,KAAK,KAAK,UAAU,EAAG,GAAG,KAAK,KAAK,CAAS,EAAE,MAAM,CACnE,CAEA,KAAK,EAAkD,CACrD,IAAI,EACJ,GAAI,CACF,EAAMC,EAAAA,QAAG,aAAa,KAAK,QAAQ,CAAS,EAAG,MAAM,CACvD,MAAQ,CACN,MACF,CACA,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAQ,CACN,MACF,CACA,GAAI,CAAC,EAAS,CAAM,EAAG,OACvB,GAAM,CAAE,KAAM,EAAU,QAAO,YAAW,UAAW,EAGjD,YAAO,GAAa,UAAY,CAACD,EAAAA,QAAK,WAAW,CAAQ,IACzD,OAAO,GAAU,UAAY,OAAO,GAAc,SACtD,MAAO,CAAE,KAAM,EAAU,QAAO,YAAW,GAAI,OAAO,GAAW,SAAW,CAAE,QAAO,EAAI,CAAC,CAAG,CAC/F,CAEA,MAAM,EAAmB,EAAiC,CACxD,EAAA,QAAG,UAAU,KAAK,UAAU,EAAG,CAAE,UAAW,GAAM,KAAM,GAAuB,CAAC,EAChF,EAAA,QAAG,cAAc,KAAK,QAAQ,CAAS,EAAG,KAAK,UAAU,CAAM,EAAG,CAAE,KAAM,GAAkB,CAAC,CAC/F,CAEA,MAAM,EAAyB,CAC7B,EAAA,QAAG,OAAO,KAAK,QAAQ,CAAS,EAAG,CAAE,MAAO,EAAK,CAAC,CACpD,CAEA,KAAa,EAAuB,CAClC,OAAA,EAAOE,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAE,CACrE,CACF"}
@@ -0,0 +1,2 @@
1
+ import{createHash as e}from"node:crypto";import t from"node:fs";import n from"node:os";import r from"node:path";const i=[`.pi`,`agent`];function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var o=class{env;homeDirectory;constructor(e={}){this.env=e.env??process.env,this.homeDirectory=e.homeDirectory??n.homedir()}directory(){let e=this.env.PI_CODING_AGENT_DIR||r.join(this.homeDirectory,...i);return r.join(e,`doom-plan`)}pathFor(e){return r.join(this.directory(),`${this.hash(e)}.json`)}read(e){let n;try{n=t.readFileSync(this.pathFor(e),`utf8`)}catch{return}let i;try{i=JSON.parse(n)}catch{return}if(!a(i))return;let{path:o,title:s,writtenAt:c,planId:l}=i;if(!(typeof o!=`string`||!r.isAbsolute(o))&&typeof s==`string`&&typeof c==`string`)return{path:o,title:s,writtenAt:c,...typeof l==`string`?{planId:l}:{}}}write(e,n){t.mkdirSync(this.directory(),{recursive:!0,mode:448}),t.writeFileSync(this.pathFor(e),JSON.stringify(n),{mode:384})}clear(e){t.rmSync(this.pathFor(e),{force:!0})}hash(t){return e(`sha256`).update(t).digest(`hex`).slice(0,16)}};export{o as NodePlanPointerAdapter};
2
+ //# sourceMappingURL=planPointer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planPointer.mjs","names":[],"sources":["../../../src/adapters/node/planPointer.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { PlanPointerRecord } from '../../types/planApi.ts';\nimport type { PlanPointerPort } from '../../types/planPointer.ts';\n\n/**\n * Record and read where write_plan left this session's plan file.\n *\n * One technology per adapter. This is where node enters the package; the\n * services that depend on the capability keep importing the port.\n *\n * Pi already gives every session an agent directory, so the records live under\n * it rather than in a second place a reader would have to know about. The\n * posture matches the plan file itself, 0700 on the directory and 0600 on the\n * record: a plan is a private working document, not something to leave\n * world-readable in a shared home.\n */\n\nconst POINTER_DIRECTORY = 'doom-plan';\nconst AGENT_DIRECTORY = ['.pi', 'agent'];\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nexport interface NodePlanPointerOptions {\n env?: NodeJS.ProcessEnv;\n homeDirectory?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport class NodePlanPointerAdapter implements PlanPointerPort {\n private readonly env: NodeJS.ProcessEnv;\n private readonly homeDirectory: string;\n\n constructor(options: NodePlanPointerOptions = {}) {\n this.env = options.env ?? process.env;\n this.homeDirectory = options.homeDirectory ?? os.homedir();\n }\n\n /** Where the records live; exposed so a test can look without guessing. */\n directory(): string {\n const agentDirectory = this.env.PI_CODING_AGENT_DIR || path.join(this.homeDirectory, ...AGENT_DIRECTORY);\n return path.join(agentDirectory, POINTER_DIRECTORY);\n }\n\n /**\n * The record for one session. The id is hashed rather than used as written:\n * it reaches here from a host that may word it however it likes, and a\n * filename is not the place to find that out.\n */\n pathFor(sessionId: string): string {\n return path.join(this.directory(), `${this.hash(sessionId)}.json`);\n }\n\n read(sessionId: string): PlanPointerRecord | undefined {\n let raw: string;\n try {\n raw = fs.readFileSync(this.pathFor(sessionId), 'utf8');\n } catch {\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n const { path: planPath, title, writtenAt, planId } = parsed;\n // A record half-written by an interrupted run reads as no plan at all,\n // which is a state the cockpit already handles.\n if (typeof planPath !== 'string' || !path.isAbsolute(planPath)) return undefined;\n if (typeof title !== 'string' || typeof writtenAt !== 'string') return undefined;\n return { path: planPath, title, writtenAt, ...(typeof planId === 'string' ? { planId } : {}) };\n }\n\n write(sessionId: string, record: PlanPointerRecord): void {\n fs.mkdirSync(this.directory(), { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n fs.writeFileSync(this.pathFor(sessionId), JSON.stringify(record), { mode: PRIVATE_FILE_MODE });\n }\n\n clear(sessionId: string): void {\n fs.rmSync(this.pathFor(sessionId), { force: true });\n }\n\n private hash(value: string): string {\n return createHash('sha256').update(value).digest('hex').slice(0, 16);\n }\n}\n"],"mappings":"gHAoBA,MACM,EAAkB,CAAC,MAAO,OAAO,EASvC,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,IAAa,EAAb,KAA+D,CAC7D,IACA,cAEA,YAAY,EAAkC,CAAC,EAAG,CAChD,KAAK,IAAM,EAAQ,KAAO,QAAQ,IAClC,KAAK,cAAgB,EAAQ,eAAiB,EAAG,QAAQ,CAC3D,CAGA,WAAoB,CAClB,IAAM,EAAiB,KAAK,IAAI,qBAAuB,EAAK,KAAK,KAAK,cAAe,GAAG,CAAe,EACvG,OAAO,EAAK,KAAK,EAAgB,WAAiB,CACpD,CAOA,QAAQ,EAA2B,CACjC,OAAO,EAAK,KAAK,KAAK,UAAU,EAAG,GAAG,KAAK,KAAK,CAAS,EAAE,MAAM,CACnE,CAEA,KAAK,EAAkD,CACrD,IAAI,EACJ,GAAI,CACF,EAAM,EAAG,aAAa,KAAK,QAAQ,CAAS,EAAG,MAAM,CACvD,MAAQ,CACN,MACF,CACA,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAQ,CACN,MACF,CACA,GAAI,CAAC,EAAS,CAAM,EAAG,OACvB,GAAM,CAAE,KAAM,EAAU,QAAO,YAAW,UAAW,EAGjD,YAAO,GAAa,UAAY,CAAC,EAAK,WAAW,CAAQ,IACzD,OAAO,GAAU,UAAY,OAAO,GAAc,SACtD,MAAO,CAAE,KAAM,EAAU,QAAO,YAAW,GAAI,OAAO,GAAW,SAAW,CAAE,QAAO,EAAI,CAAC,CAAG,CAC/F,CAEA,MAAM,EAAmB,EAAiC,CACxD,EAAG,UAAU,KAAK,UAAU,EAAG,CAAE,UAAW,GAAM,KAAM,GAAuB,CAAC,EAChF,EAAG,cAAc,KAAK,QAAQ,CAAS,EAAG,KAAK,UAAU,CAAM,EAAG,CAAE,KAAM,GAAkB,CAAC,CAC/F,CAEA,MAAM,EAAyB,CAC7B,EAAG,OAAO,KAAK,QAAQ,CAAS,EAAG,CAAE,MAAO,EAAK,CAAC,CACpD,CAEA,KAAa,EAAuB,CAClC,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAE,CACrE,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./node/planPointer.cjs"),n=require("../types/planApi.cjs");let r=require("node:crypto"),i=require("node:fs/promises");i=e.__toESM(i,1);let a=require("hono");const o=1048576,s=`This session has not written a plan yet.`;function c(e){return(0,r.createHash)(`sha256`).update(e,`utf8`).digest(`hex`)}async function l(e){let t;try{t=await i.default.readFile(e)}catch{return{content:``,hash:``,unavailable:!0,reason:`the plan file no longer exists`}}if(t.byteLength>1048576)return{content:``,hash:``,unavailable:!0,reason:`the plan is larger than ${o} bytes`};let n=t.toString(`utf8`);return{content:n,hash:c(n),unavailable:!1}}function u(e={}){let r=new a.Hono,o=e.pointers??new t.NodePlanPointerAdapter,u=()=>e.sessionId===void 0?void 0:o.read(e.sessionId);return r.get(n.currentPath(),async e=>{let t=u();if(t===void 0)return e.json({error:s},404);let n={path:t.path,title:t.title,writtenAt:t.writtenAt,...t.planId===void 0?{}:{planId:t.planId},...await l(t.path)};return e.json(n)}),r.put(n.contentPath(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The save body is not JSON.`},400)}if(typeof t.content!=`string`||typeof t.expectedHash!=`string`)return e.json({error:`A save carries the content to write and the hash it was read at.`},400);let n=u();if(n===void 0)return e.json({error:s},404);let r=await l(n.path);if(r.unavailable)return e.json({error:r.reason??s},409);if(r.hash!==t.expectedHash)return e.json({error:`The plan changed since it was opened.`,hash:r.hash},409);let a=await i.default.lstat(n.path);return!a.isFile()||a.nlink!==1?e.json({error:`The plan file is no longer a regular file, so it was not written.`},409):(await i.default.writeFile(n.path,t.content,{encoding:`utf8`,mode:384}),e.json({hash:c(t.content)}))}),r}const d={basePath:n.API_BASE_PATH,start(e){let t=u(e.sessionId===void 0?{}:{sessionId:e.sessionId});return{fetch:e=>t.fetch(e),close:()=>void 0}}};exports.MAX_PLAN_BYTES=o,exports.api=d,exports.createPlanApi=u;
2
+ //# sourceMappingURL=planApi.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planApi.cjs","names":["createHash","fs","Hono","NodePlanPointerAdapter","currentPath","contentPath","API_BASE_PATH"],"sources":["../../src/adapters/planApi.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport { Hono } from 'hono';\nimport {\n API_BASE_PATH,\n contentPath,\n currentPath,\n type PlanDetailView,\n type PlanSaveRequest,\n type PlanSaveView,\n} from '../types/planApi.ts';\nimport type { PlanPointerPort } from '../types/planPointer.ts';\nimport { NodePlanPointerAdapter } from './node/planPointer.ts';\n\n/**\n * This package's HTTP surface: the session's current plan, and the manual save.\n *\n * Routes are relative to the mount its host gives it, so nothing here repeats\n * where it was mounted. The host is one session's own server, which is what\n * makes the session id available without the page supplying it.\n *\n * Neither route takes a path. A session has exactly one current plan, and the\n * file both routes act on is the one the pointer names, so a page can address\n * the plan and nothing else; there is no traversal question to answer because\n * there is no path to traverse.\n *\n * The save refuses unless the reader proves they were looking at the content\n * that is still on disk. The agent may rewrite the plan while a tab is open,\n * and silently discarding whichever side finished second is the one outcome\n * worth ruling out.\n */\n\n/** Past this, the plan is reported rather than carried. */\nexport const MAX_PLAN_BYTES = 1024 * 1024;\n\nconst NO_PLAN = 'This session has not written a plan yet.';\nconst STALE_SAVE = 'The plan changed since it was opened.';\nconst NOT_A_FILE = 'The plan file is no longer a regular file, so it was not written.';\n\nexport interface PlanApiOptions {\n /** The session these routes answer for; absent when the host is the hub. */\n sessionId?: string;\n pointers?: PlanPointerPort;\n}\n\nfunction hashOf(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n\n/** Reads the plan as the source view will show it, or says why it cannot. */\nasync function readPlan(\n planPath: string,\n): Promise<Pick<PlanDetailView, 'content' | 'hash' | 'unavailable' | 'reason'>> {\n let raw: Buffer;\n try {\n raw = await fs.readFile(planPath);\n } catch {\n return { content: '', hash: '', unavailable: true, reason: 'the plan file no longer exists' };\n }\n if (raw.byteLength > MAX_PLAN_BYTES) {\n return { content: '', hash: '', unavailable: true, reason: `the plan is larger than ${MAX_PLAN_BYTES} bytes` };\n }\n const content = raw.toString('utf8');\n return { content, hash: hashOf(content), unavailable: false };\n}\n\nexport function createPlanApi(options: PlanApiOptions = {}): Hono {\n const app = new Hono();\n const pointers = options.pointers ?? new NodePlanPointerAdapter();\n const pointerFor = (): ReturnType<PlanPointerPort['read']> =>\n options.sessionId === undefined ? undefined : pointers.read(options.sessionId);\n\n app.get(currentPath(), async (context) => {\n const pointer = pointerFor();\n if (pointer === undefined) return context.json({ error: NO_PLAN }, 404);\n const view: PlanDetailView = {\n path: pointer.path,\n title: pointer.title,\n writtenAt: pointer.writtenAt,\n ...(pointer.planId === undefined ? {} : { planId: pointer.planId }),\n ...(await readPlan(pointer.path)),\n };\n return context.json(view);\n });\n\n app.put(contentPath(), async (context) => {\n let request: PlanSaveRequest;\n try {\n request = (await context.req.json()) as PlanSaveRequest;\n } catch {\n return context.json({ error: 'The save body is not JSON.' }, 400);\n }\n if (typeof request.content !== 'string' || typeof request.expectedHash !== 'string') {\n return context.json({ error: 'A save carries the content to write and the hash it was read at.' }, 400);\n }\n const pointer = pointerFor();\n if (pointer === undefined) return context.json({ error: NO_PLAN }, 404);\n\n const current = await readPlan(pointer.path);\n if (current.unavailable) return context.json({ error: current.reason ?? NO_PLAN }, 409);\n if (current.hash !== request.expectedHash) {\n return context.json({ error: STALE_SAVE, hash: current.hash }, 409);\n }\n // The same guard write_plan applies before it writes: a plan whose\n // destination has become a symlink or gained a second name is not written\n // through, whatever the pointer still says.\n const stats = await fs.lstat(pointer.path);\n if (!stats.isFile() || stats.nlink !== 1) return context.json({ error: NOT_A_FILE }, 409);\n\n await fs.writeFile(pointer.path, request.content, { encoding: 'utf8', mode: 0o600 });\n return context.json({ hash: hashOf(request.content) } satisfies PlanSaveView);\n });\n\n return app;\n}\n\n/** The named export a host imports from this package's built entry. */\nexport const api: DoomApi = {\n basePath: API_BASE_PATH,\n start(context: DoomApiContext): DoomApiHandler {\n const app = createPlanApi(context.sessionId === undefined ? {} : { sessionId: context.sessionId });\n return {\n fetch: (request) => app.fetch(request),\n // Nothing outlives a request here; an API that starts a watch or a timer\n // tears it down in this callback.\n close: () => undefined,\n };\n },\n};\n"],"mappings":"6NAkCA,MAAa,EAAiB,QAExB,EAAU,2CAUhB,SAAS,EAAO,EAAyB,CACvC,OAAA,EAAOA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,EAAS,MAAM,CAAC,CAAC,OAAO,KAAK,CAClE,CAGA,eAAe,EACb,EAC8E,CAC9E,IAAI,EACJ,GAAI,CACF,EAAM,MAAMC,EAAAA,QAAG,SAAS,CAAQ,CAClC,MAAQ,CACN,MAAO,CAAE,QAAS,GAAI,KAAM,GAAI,YAAa,GAAM,OAAQ,gCAAiC,CAC9F,CACA,GAAI,EAAI,WAAA,QACN,MAAO,CAAE,QAAS,GAAI,KAAM,GAAI,YAAa,GAAM,OAAQ,2BAA2B,EAAe,OAAQ,EAE/G,IAAM,EAAU,EAAI,SAAS,MAAM,EACnC,MAAO,CAAE,UAAS,KAAM,EAAO,CAAO,EAAG,YAAa,EAAM,CAC9D,CAEA,SAAgB,EAAc,EAA0B,CAAC,EAAS,CAChE,IAAM,EAAM,IAAIC,EAAAA,KACV,EAAW,EAAQ,UAAY,IAAIC,EAAAA,uBACnC,MACJ,EAAQ,YAAc,IAAA,GAAY,IAAA,GAAY,EAAS,KAAK,EAAQ,SAAS,EA2C/E,OAzCA,EAAI,IAAIC,EAAAA,YAAY,EAAG,KAAO,IAAY,CACxC,IAAM,EAAU,EAAW,EAC3B,GAAI,IAAY,IAAA,GAAW,OAAO,EAAQ,KAAK,CAAE,MAAO,CAAQ,EAAG,GAAG,EACtE,IAAM,EAAuB,CAC3B,KAAM,EAAQ,KACd,MAAO,EAAQ,MACf,UAAW,EAAQ,UACnB,GAAI,EAAQ,SAAW,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQ,EAAQ,MAAO,EACjE,GAAI,MAAM,EAAS,EAAQ,IAAI,CACjC,EACA,OAAO,EAAQ,KAAK,CAAI,CAC1B,CAAC,EAED,EAAI,IAAIC,EAAAA,YAAY,EAAG,KAAO,IAAY,CACxC,IAAI,EACJ,GAAI,CACF,EAAW,MAAM,EAAQ,IAAI,KAAK,CACpC,MAAQ,CACN,OAAO,EAAQ,KAAK,CAAE,MAAO,4BAA6B,EAAG,GAAG,CAClE,CACA,GAAI,OAAO,EAAQ,SAAY,UAAY,OAAO,EAAQ,cAAiB,SACzE,OAAO,EAAQ,KAAK,CAAE,MAAO,kEAAmE,EAAG,GAAG,EAExG,IAAM,EAAU,EAAW,EAC3B,GAAI,IAAY,IAAA,GAAW,OAAO,EAAQ,KAAK,CAAE,MAAO,CAAQ,EAAG,GAAG,EAEtE,IAAM,EAAU,MAAM,EAAS,EAAQ,IAAI,EAC3C,GAAI,EAAQ,YAAa,OAAO,EAAQ,KAAK,CAAE,MAAO,EAAQ,QAAU,CAAQ,EAAG,GAAG,EACtF,GAAI,EAAQ,OAAS,EAAQ,aAC3B,OAAO,EAAQ,KAAK,CAAE,MAAO,wCAAY,KAAM,EAAQ,IAAK,EAAG,GAAG,EAKpE,IAAM,EAAQ,MAAMJ,EAAAA,QAAG,MAAM,EAAQ,IAAI,EAIzC,MAHI,CAAC,EAAM,OAAO,GAAK,EAAM,QAAU,EAAU,EAAQ,KAAK,CAAE,MAAO,mEAAW,EAAG,GAAG,GAExF,MAAMA,EAAAA,QAAG,UAAU,EAAQ,KAAM,EAAQ,QAAS,CAAE,SAAU,OAAQ,KAAM,GAAM,CAAC,EAC5E,EAAQ,KAAK,CAAE,KAAM,EAAO,EAAQ,OAAO,CAAE,CAAwB,EAC9E,CAAC,EAEM,CACT,CAGA,MAAa,EAAe,CAC1B,SAAUK,EAAAA,cACV,MAAM,EAAyC,CAC7C,IAAM,EAAM,EAAc,EAAQ,YAAc,IAAA,GAAY,CAAC,EAAI,CAAE,UAAW,EAAQ,SAAU,CAAC,EACjG,MAAO,CACL,MAAQ,GAAY,EAAI,MAAM,CAAO,EAGrC,UAAa,IAAA,EACf,CACF,CACF"}
@@ -0,0 +1,15 @@
1
+ import { PlanPointerPort } from "../types/planPointer.cjs";
2
+ import { DoomApi } from "@agimon-ai/doompi-extension-contracts/package-api";
3
+ import { Hono } from "hono";
4
+ //#region src/adapters/planApi.d.ts
5
+ interface PlanApiOptions {
6
+ /** The session these routes answer for; absent when the host is the hub. */
7
+ sessionId?: string;
8
+ pointers?: PlanPointerPort;
9
+ }
10
+ declare function createPlanApi(options?: PlanApiOptions): Hono;
11
+ /** The named export a host imports from this package's built entry. */
12
+ declare const api: DoomApi;
13
+ //#endregion
14
+ export { PlanApiOptions, api, createPlanApi };
15
+ //# sourceMappingURL=planApi.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planApi.d.cts","names":[],"sources":["../../src/adapters/planApi.ts"],"mappings":";;;;UAwCiB;;EAEf;EACA,WAAW;;iBAwBG,cAAc,UAAS,iBAAsB;;cAmDhD,KAAK"}
@@ -0,0 +1,15 @@
1
+ import { PlanPointerPort } from "../types/planPointer.mjs";
2
+ import { Hono } from "hono";
3
+ import { DoomApi } from "@agimon-ai/doompi-extension-contracts/package-api";
4
+ //#region src/adapters/planApi.d.ts
5
+ interface PlanApiOptions {
6
+ /** The session these routes answer for; absent when the host is the hub. */
7
+ sessionId?: string;
8
+ pointers?: PlanPointerPort;
9
+ }
10
+ declare function createPlanApi(options?: PlanApiOptions): Hono;
11
+ /** The named export a host imports from this package's built entry. */
12
+ declare const api: DoomApi;
13
+ //#endregion
14
+ export { PlanApiOptions, api, createPlanApi };
15
+ //# sourceMappingURL=planApi.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planApi.d.mts","names":[],"sources":["../../src/adapters/planApi.ts"],"mappings":";;;;UAwCiB;;EAEf;EACA,WAAW;;iBAwBG,cAAc,UAAS,iBAAsB;;cAmDhD,KAAK"}
@@ -0,0 +1,2 @@
1
+ import{NodePlanPointerAdapter as e}from"./node/planPointer.mjs";import{API_BASE_PATH as t,contentPath as n,currentPath as r}from"../types/planApi.mjs";import{createHash as i}from"node:crypto";import a from"node:fs/promises";import{Hono as o}from"hono";const s=1048576,c=`This session has not written a plan yet.`;function l(e){return i(`sha256`).update(e,`utf8`).digest(`hex`)}async function u(e){let t;try{t=await a.readFile(e)}catch{return{content:``,hash:``,unavailable:!0,reason:`the plan file no longer exists`}}if(t.byteLength>1048576)return{content:``,hash:``,unavailable:!0,reason:`the plan is larger than ${s} bytes`};let n=t.toString(`utf8`);return{content:n,hash:l(n),unavailable:!1}}function d(t={}){let i=new o,s=t.pointers??new e,d=()=>t.sessionId===void 0?void 0:s.read(t.sessionId);return i.get(r(),async e=>{let t=d();if(t===void 0)return e.json({error:c},404);let n={path:t.path,title:t.title,writtenAt:t.writtenAt,...t.planId===void 0?{}:{planId:t.planId},...await u(t.path)};return e.json(n)}),i.put(n(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The save body is not JSON.`},400)}if(typeof t.content!=`string`||typeof t.expectedHash!=`string`)return e.json({error:`A save carries the content to write and the hash it was read at.`},400);let n=d();if(n===void 0)return e.json({error:c},404);let r=await u(n.path);if(r.unavailable)return e.json({error:r.reason??c},409);if(r.hash!==t.expectedHash)return e.json({error:`The plan changed since it was opened.`,hash:r.hash},409);let i=await a.lstat(n.path);return!i.isFile()||i.nlink!==1?e.json({error:`The plan file is no longer a regular file, so it was not written.`},409):(await a.writeFile(n.path,t.content,{encoding:`utf8`,mode:384}),e.json({hash:l(t.content)}))}),i}const f={basePath:t,start(e){let t=d(e.sessionId===void 0?{}:{sessionId:e.sessionId});return{fetch:e=>t.fetch(e),close:()=>void 0}}};export{s as MAX_PLAN_BYTES,f as api,d as createPlanApi};
2
+ //# sourceMappingURL=planApi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planApi.mjs","names":[],"sources":["../../src/adapters/planApi.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport { Hono } from 'hono';\nimport {\n API_BASE_PATH,\n contentPath,\n currentPath,\n type PlanDetailView,\n type PlanSaveRequest,\n type PlanSaveView,\n} from '../types/planApi.ts';\nimport type { PlanPointerPort } from '../types/planPointer.ts';\nimport { NodePlanPointerAdapter } from './node/planPointer.ts';\n\n/**\n * This package's HTTP surface: the session's current plan, and the manual save.\n *\n * Routes are relative to the mount its host gives it, so nothing here repeats\n * where it was mounted. The host is one session's own server, which is what\n * makes the session id available without the page supplying it.\n *\n * Neither route takes a path. A session has exactly one current plan, and the\n * file both routes act on is the one the pointer names, so a page can address\n * the plan and nothing else; there is no traversal question to answer because\n * there is no path to traverse.\n *\n * The save refuses unless the reader proves they were looking at the content\n * that is still on disk. The agent may rewrite the plan while a tab is open,\n * and silently discarding whichever side finished second is the one outcome\n * worth ruling out.\n */\n\n/** Past this, the plan is reported rather than carried. */\nexport const MAX_PLAN_BYTES = 1024 * 1024;\n\nconst NO_PLAN = 'This session has not written a plan yet.';\nconst STALE_SAVE = 'The plan changed since it was opened.';\nconst NOT_A_FILE = 'The plan file is no longer a regular file, so it was not written.';\n\nexport interface PlanApiOptions {\n /** The session these routes answer for; absent when the host is the hub. */\n sessionId?: string;\n pointers?: PlanPointerPort;\n}\n\nfunction hashOf(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n\n/** Reads the plan as the source view will show it, or says why it cannot. */\nasync function readPlan(\n planPath: string,\n): Promise<Pick<PlanDetailView, 'content' | 'hash' | 'unavailable' | 'reason'>> {\n let raw: Buffer;\n try {\n raw = await fs.readFile(planPath);\n } catch {\n return { content: '', hash: '', unavailable: true, reason: 'the plan file no longer exists' };\n }\n if (raw.byteLength > MAX_PLAN_BYTES) {\n return { content: '', hash: '', unavailable: true, reason: `the plan is larger than ${MAX_PLAN_BYTES} bytes` };\n }\n const content = raw.toString('utf8');\n return { content, hash: hashOf(content), unavailable: false };\n}\n\nexport function createPlanApi(options: PlanApiOptions = {}): Hono {\n const app = new Hono();\n const pointers = options.pointers ?? new NodePlanPointerAdapter();\n const pointerFor = (): ReturnType<PlanPointerPort['read']> =>\n options.sessionId === undefined ? undefined : pointers.read(options.sessionId);\n\n app.get(currentPath(), async (context) => {\n const pointer = pointerFor();\n if (pointer === undefined) return context.json({ error: NO_PLAN }, 404);\n const view: PlanDetailView = {\n path: pointer.path,\n title: pointer.title,\n writtenAt: pointer.writtenAt,\n ...(pointer.planId === undefined ? {} : { planId: pointer.planId }),\n ...(await readPlan(pointer.path)),\n };\n return context.json(view);\n });\n\n app.put(contentPath(), async (context) => {\n let request: PlanSaveRequest;\n try {\n request = (await context.req.json()) as PlanSaveRequest;\n } catch {\n return context.json({ error: 'The save body is not JSON.' }, 400);\n }\n if (typeof request.content !== 'string' || typeof request.expectedHash !== 'string') {\n return context.json({ error: 'A save carries the content to write and the hash it was read at.' }, 400);\n }\n const pointer = pointerFor();\n if (pointer === undefined) return context.json({ error: NO_PLAN }, 404);\n\n const current = await readPlan(pointer.path);\n if (current.unavailable) return context.json({ error: current.reason ?? NO_PLAN }, 409);\n if (current.hash !== request.expectedHash) {\n return context.json({ error: STALE_SAVE, hash: current.hash }, 409);\n }\n // The same guard write_plan applies before it writes: a plan whose\n // destination has become a symlink or gained a second name is not written\n // through, whatever the pointer still says.\n const stats = await fs.lstat(pointer.path);\n if (!stats.isFile() || stats.nlink !== 1) return context.json({ error: NOT_A_FILE }, 409);\n\n await fs.writeFile(pointer.path, request.content, { encoding: 'utf8', mode: 0o600 });\n return context.json({ hash: hashOf(request.content) } satisfies PlanSaveView);\n });\n\n return app;\n}\n\n/** The named export a host imports from this package's built entry. */\nexport const api: DoomApi = {\n basePath: API_BASE_PATH,\n start(context: DoomApiContext): DoomApiHandler {\n const app = createPlanApi(context.sessionId === undefined ? {} : { sessionId: context.sessionId });\n return {\n fetch: (request) => app.fetch(request),\n // Nothing outlives a request here; an API that starts a watch or a timer\n // tears it down in this callback.\n close: () => undefined,\n };\n },\n};\n"],"mappings":"4PAkCA,MAAa,EAAiB,QAExB,EAAU,2CAUhB,SAAS,EAAO,EAAyB,CACvC,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,EAAS,MAAM,CAAC,CAAC,OAAO,KAAK,CAClE,CAGA,eAAe,EACb,EAC8E,CAC9E,IAAI,EACJ,GAAI,CACF,EAAM,MAAM,EAAG,SAAS,CAAQ,CAClC,MAAQ,CACN,MAAO,CAAE,QAAS,GAAI,KAAM,GAAI,YAAa,GAAM,OAAQ,gCAAiC,CAC9F,CACA,GAAI,EAAI,WAAA,QACN,MAAO,CAAE,QAAS,GAAI,KAAM,GAAI,YAAa,GAAM,OAAQ,2BAA2B,EAAe,OAAQ,EAE/G,IAAM,EAAU,EAAI,SAAS,MAAM,EACnC,MAAO,CAAE,UAAS,KAAM,EAAO,CAAO,EAAG,YAAa,EAAM,CAC9D,CAEA,SAAgB,EAAc,EAA0B,CAAC,EAAS,CAChE,IAAM,EAAM,IAAI,EACV,EAAW,EAAQ,UAAY,IAAI,EACnC,MACJ,EAAQ,YAAc,IAAA,GAAY,IAAA,GAAY,EAAS,KAAK,EAAQ,SAAS,EA2C/E,OAzCA,EAAI,IAAI,EAAY,EAAG,KAAO,IAAY,CACxC,IAAM,EAAU,EAAW,EAC3B,GAAI,IAAY,IAAA,GAAW,OAAO,EAAQ,KAAK,CAAE,MAAO,CAAQ,EAAG,GAAG,EACtE,IAAM,EAAuB,CAC3B,KAAM,EAAQ,KACd,MAAO,EAAQ,MACf,UAAW,EAAQ,UACnB,GAAI,EAAQ,SAAW,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQ,EAAQ,MAAO,EACjE,GAAI,MAAM,EAAS,EAAQ,IAAI,CACjC,EACA,OAAO,EAAQ,KAAK,CAAI,CAC1B,CAAC,EAED,EAAI,IAAI,EAAY,EAAG,KAAO,IAAY,CACxC,IAAI,EACJ,GAAI,CACF,EAAW,MAAM,EAAQ,IAAI,KAAK,CACpC,MAAQ,CACN,OAAO,EAAQ,KAAK,CAAE,MAAO,4BAA6B,EAAG,GAAG,CAClE,CACA,GAAI,OAAO,EAAQ,SAAY,UAAY,OAAO,EAAQ,cAAiB,SACzE,OAAO,EAAQ,KAAK,CAAE,MAAO,kEAAmE,EAAG,GAAG,EAExG,IAAM,EAAU,EAAW,EAC3B,GAAI,IAAY,IAAA,GAAW,OAAO,EAAQ,KAAK,CAAE,MAAO,CAAQ,EAAG,GAAG,EAEtE,IAAM,EAAU,MAAM,EAAS,EAAQ,IAAI,EAC3C,GAAI,EAAQ,YAAa,OAAO,EAAQ,KAAK,CAAE,MAAO,EAAQ,QAAU,CAAQ,EAAG,GAAG,EACtF,GAAI,EAAQ,OAAS,EAAQ,aAC3B,OAAO,EAAQ,KAAK,CAAE,MAAO,wCAAY,KAAM,EAAQ,IAAK,EAAG,GAAG,EAKpE,IAAM,EAAQ,MAAM,EAAG,MAAM,EAAQ,IAAI,EAIzC,MAHI,CAAC,EAAM,OAAO,GAAK,EAAM,QAAU,EAAU,EAAQ,KAAK,CAAE,MAAO,mEAAW,EAAG,GAAG,GAExF,MAAM,EAAG,UAAU,EAAQ,KAAM,EAAQ,QAAS,CAAE,SAAU,OAAQ,KAAM,GAAM,CAAC,EAC5E,EAAQ,KAAK,CAAE,KAAM,EAAO,EAAQ,OAAO,CAAE,CAAwB,EAC9E,CAAC,EAEM,CACT,CAGA,MAAa,EAAe,CAC1B,SAAU,EACV,MAAM,EAAyC,CAC7C,IAAM,EAAM,EAAc,EAAQ,YAAc,IAAA,GAAY,CAAC,EAAI,CAAE,UAAW,EAAQ,SAAU,CAAC,EACjG,MAAO,CACL,MAAQ,GAAY,EAAI,MAAM,CAAO,EAGrC,UAAa,IAAA,EACf,CACF,CACF"}
@@ -1,2 +1,2 @@
1
- let e=require("@agimon-ai/doompi-telemetry");const t={configLoadFailed:`doom_plan.config_load_failed`,writePlanFailed:`doom_plan.write_plan_failed`,writePlanTimedOut:`doom_plan.write_plan_timed_out`,writePlanUnsafePath:`doom_plan.write_plan_unsafe_path`,modelResolveFailed:`doom_plan.model_resolve_failed`,modeEnabled:`doom_plan.mode_enabled`,modeDisabled:`doom_plan.mode_disabled`,planWritten:`doom_plan.plan_written`,planReviewCompleted:`doom_plan.plan_review_completed`};function n(t={}){let n=(0,e.createDoomTelemetry)({serviceName:`doom-plan`,packageName:`@agimon-ai/doompi-plan`,cwd:t.cwd,workspaceRoot:t.workspaceRoot,env:t.env,telemetryFactory:t.telemetryFactory,warn:t.warn,enableLogs:!0,enableTraces:!0});return{recordError:(e,t,r)=>n.recordError(e,t,r),recordWarning:(e,t,r)=>n.recordWarning(e,t,r),recordEvent:(e,t)=>n.recordEvent(e,t),flush:()=>n.flush(),shutdown:()=>n.shutdown()}}exports.PLAN_EVENT=t,exports.createPlanTelemetry=n;
1
+ let e=require("@agimon-ai/doompi-telemetry");const t={configLoadFailed:`doom_plan.config_load_failed`,writePlanFailed:`doom_plan.write_plan_failed`,writePlanTimedOut:`doom_plan.write_plan_timed_out`,writePlanUnsafePath:`doom_plan.write_plan_unsafe_path`,modelResolveFailed:`doom_plan.model_resolve_failed`,modeEnabled:`doom_plan.mode_enabled`,modeDisabled:`doom_plan.mode_disabled`,planWritten:`doom_plan.plan_written`,planPointerFailed:`doom_plan.plan_pointer_failed`,planReviewCompleted:`doom_plan.plan_review_completed`};function n(t={}){let n=(0,e.createDoomTelemetry)({serviceName:`doom-plan`,packageName:`@agimon-ai/doompi-plan`,cwd:t.cwd,workspaceRoot:t.workspaceRoot,env:t.env,telemetryFactory:t.telemetryFactory,warn:t.warn,enableLogs:!0,enableTraces:!0});return{recordError:(e,t,r)=>n.recordError(e,t,r),recordWarning:(e,t,r)=>n.recordWarning(e,t,r),recordEvent:(e,t)=>n.recordEvent(e,t),flush:()=>n.flush(),shutdown:()=>n.shutdown()}}exports.PLAN_EVENT=t,exports.createPlanTelemetry=n;
2
2
  //# sourceMappingURL=logSinkTelemetry.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"logSinkTelemetry.cjs","names":["createDoomTelemetry"],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nconst SERVICE_NAME = 'doom-plan';\nconst PACKAGE_NAME = '@agimon-ai/doompi-plan';\n\nexport const PLAN_EVENT = {\n configLoadFailed: 'doom_plan.config_load_failed',\n writePlanFailed: 'doom_plan.write_plan_failed',\n writePlanTimedOut: 'doom_plan.write_plan_timed_out',\n writePlanUnsafePath: 'doom_plan.write_plan_unsafe_path',\n modelResolveFailed: 'doom_plan.model_resolve_failed',\n modeEnabled: 'doom_plan.mode_enabled',\n modeDisabled: 'doom_plan.mode_disabled',\n planWritten: 'doom_plan.plan_written',\n planReviewCompleted: 'doom_plan.plan_review_completed',\n} as const;\n\nexport type PlanEventName = (typeof PLAN_EVENT)[keyof typeof PLAN_EVENT];\nexport type PlanEventAttributes = Record<string, string | number | boolean>;\n\nexport interface PlanTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport interface PlanTelemetry {\n recordError(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordWarning(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordEvent(event: PlanEventName, attributes?: PlanEventAttributes): Promise<void>;\n flush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\nexport function createPlanTelemetry(options: PlanTelemetryOptions = {}): PlanTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n recordEvent: (event, attributes) => telemetry.recordEvent(event, attributes),\n flush: () => telemetry.flush(),\n shutdown: () => telemetry.shutdown(),\n };\n}\n"],"mappings":"6CAEA,MAGa,EAAa,CACxB,iBAAkB,+BAClB,gBAAiB,8BACjB,kBAAmB,iCACnB,oBAAqB,mCACrB,mBAAoB,iCACpB,YAAa,yBACb,aAAc,0BACd,YAAa,yBACb,oBAAqB,iCACvB,EAqBA,SAAgB,EAAoB,EAAgC,CAAC,EAAkB,CACrF,IAAM,GAAA,EAA2BA,EAAAA,oBAAAA,CAAoB,CACnD,YAAa,YACb,YAAa,yBACb,IAAK,EAAQ,IACb,cAAe,EAAQ,cACvB,IAAK,EAAQ,IACb,iBAAkB,EAAQ,iBAC1B,KAAM,EAAQ,KACd,WAAY,GACZ,aAAc,EAChB,CAAC,EACD,MAAO,CACL,aAAc,EAAO,EAAO,IAAe,EAAU,YAAY,EAAO,EAAO,CAAU,EACzF,eAAgB,EAAO,EAAO,IAAe,EAAU,cAAc,EAAO,EAAO,CAAU,EAC7F,aAAc,EAAO,IAAe,EAAU,YAAY,EAAO,CAAU,EAC3E,UAAa,EAAU,MAAM,EAC7B,aAAgB,EAAU,SAAS,CACrC,CACF"}
1
+ {"version":3,"file":"logSinkTelemetry.cjs","names":["createDoomTelemetry"],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nconst SERVICE_NAME = 'doom-plan';\nconst PACKAGE_NAME = '@agimon-ai/doompi-plan';\n\nexport const PLAN_EVENT = {\n configLoadFailed: 'doom_plan.config_load_failed',\n writePlanFailed: 'doom_plan.write_plan_failed',\n writePlanTimedOut: 'doom_plan.write_plan_timed_out',\n writePlanUnsafePath: 'doom_plan.write_plan_unsafe_path',\n modelResolveFailed: 'doom_plan.model_resolve_failed',\n modeEnabled: 'doom_plan.mode_enabled',\n modeDisabled: 'doom_plan.mode_disabled',\n planWritten: 'doom_plan.plan_written',\n planPointerFailed: 'doom_plan.plan_pointer_failed',\n planReviewCompleted: 'doom_plan.plan_review_completed',\n} as const;\n\nexport type PlanEventName = (typeof PLAN_EVENT)[keyof typeof PLAN_EVENT];\nexport type PlanEventAttributes = Record<string, string | number | boolean>;\n\nexport interface PlanTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport interface PlanTelemetry {\n recordError(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordWarning(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordEvent(event: PlanEventName, attributes?: PlanEventAttributes): Promise<void>;\n flush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\nexport function createPlanTelemetry(options: PlanTelemetryOptions = {}): PlanTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n recordEvent: (event, attributes) => telemetry.recordEvent(event, attributes),\n flush: () => telemetry.flush(),\n shutdown: () => telemetry.shutdown(),\n };\n}\n"],"mappings":"6CAEA,MAGa,EAAa,CACxB,iBAAkB,+BAClB,gBAAiB,8BACjB,kBAAmB,iCACnB,oBAAqB,mCACrB,mBAAoB,iCACpB,YAAa,yBACb,aAAc,0BACd,YAAa,yBACb,kBAAmB,gCACnB,oBAAqB,iCACvB,EAqBA,SAAgB,EAAoB,EAAgC,CAAC,EAAkB,CACrF,IAAM,GAAA,EAA2BA,EAAAA,oBAAAA,CAAoB,CACnD,YAAa,YACb,YAAa,yBACb,IAAK,EAAQ,IACb,cAAe,EAAQ,cACvB,IAAK,EAAQ,IACb,iBAAkB,EAAQ,iBAC1B,KAAM,EAAQ,KACd,WAAY,GACZ,aAAc,EAChB,CAAC,EACD,MAAO,CACL,aAAc,EAAO,EAAO,IAAe,EAAU,YAAY,EAAO,EAAO,CAAU,EACzF,eAAgB,EAAO,EAAO,IAAe,EAAU,cAAc,EAAO,EAAO,CAAU,EAC7F,aAAc,EAAO,IAAe,EAAU,YAAY,EAAO,CAAU,EAC3E,UAAa,EAAU,MAAM,EAC7B,aAAgB,EAAU,SAAS,CACrC,CACF"}
@@ -9,6 +9,7 @@ declare const PLAN_EVENT: {
9
9
  readonly modeEnabled: 'doom_plan.mode_enabled';
10
10
  readonly modeDisabled: 'doom_plan.mode_disabled';
11
11
  readonly planWritten: 'doom_plan.plan_written';
12
+ readonly planPointerFailed: 'doom_plan.plan_pointer_failed';
12
13
  readonly planReviewCompleted: 'doom_plan.plan_review_completed';
13
14
  };
14
15
  type PlanEventName = (typeof PLAN_EVENT)[keyof typeof PLAN_EVENT];
@@ -1 +1 @@
1
- {"version":3,"file":"logSinkTelemetry.d.cts","names":[],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"mappings":";;cAKa;WACX;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;KAGU,wBAAwB,yBAAyB;KACjD,sBAAsB;UAEjB;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;UAGO;EACf,YAAY,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACrF,cAAc,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACvF,YAAY,OAAO,eAAe,aAAa,sBAAsB;EACrE,SAAS;EACT,YAAY;;iBAGE,oBAAoB,UAAS,uBAA4B"}
1
+ {"version":3,"file":"logSinkTelemetry.d.cts","names":[],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"mappings":";;cAKa;WACX;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;KAGU,wBAAwB,yBAAyB;KACjD,sBAAsB;UAEjB;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;UAGO;EACf,YAAY,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACrF,cAAc,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACvF,YAAY,OAAO,eAAe,aAAa,sBAAsB;EACrE,SAAS;EACT,YAAY;;iBAGE,oBAAoB,UAAS,uBAA4B"}
@@ -9,6 +9,7 @@ declare const PLAN_EVENT: {
9
9
  readonly modeEnabled: 'doom_plan.mode_enabled';
10
10
  readonly modeDisabled: 'doom_plan.mode_disabled';
11
11
  readonly planWritten: 'doom_plan.plan_written';
12
+ readonly planPointerFailed: 'doom_plan.plan_pointer_failed';
12
13
  readonly planReviewCompleted: 'doom_plan.plan_review_completed';
13
14
  };
14
15
  type PlanEventName = (typeof PLAN_EVENT)[keyof typeof PLAN_EVENT];
@@ -1 +1 @@
1
- {"version":3,"file":"logSinkTelemetry.d.mts","names":[],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"mappings":";;cAKa;WACX;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;KAGU,wBAAwB,yBAAyB;KACjD,sBAAsB;UAEjB;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;UAGO;EACf,YAAY,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACrF,cAAc,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACvF,YAAY,OAAO,eAAe,aAAa,sBAAsB;EACrE,SAAS;EACT,YAAY;;iBAGE,oBAAoB,UAAS,uBAA4B"}
1
+ {"version":3,"file":"logSinkTelemetry.d.mts","names":[],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"mappings":";;cAKa;WACX;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;KAGU,wBAAwB,yBAAyB;KACjD,sBAAsB;UAEjB;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;UAGO;EACf,YAAY,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACrF,cAAc,OAAO,eAAe,gBAAgB,aAAa,sBAAsB;EACvF,YAAY,OAAO,eAAe,aAAa,sBAAsB;EACrE,SAAS;EACT,YAAY;;iBAGE,oBAAoB,UAAS,uBAA4B"}
@@ -1,2 +1,2 @@
1
- import{createDoomTelemetry as e}from"@agimon-ai/doompi-telemetry";const t={configLoadFailed:`doom_plan.config_load_failed`,writePlanFailed:`doom_plan.write_plan_failed`,writePlanTimedOut:`doom_plan.write_plan_timed_out`,writePlanUnsafePath:`doom_plan.write_plan_unsafe_path`,modelResolveFailed:`doom_plan.model_resolve_failed`,modeEnabled:`doom_plan.mode_enabled`,modeDisabled:`doom_plan.mode_disabled`,planWritten:`doom_plan.plan_written`,planReviewCompleted:`doom_plan.plan_review_completed`};function n(t={}){let n=e({serviceName:`doom-plan`,packageName:`@agimon-ai/doompi-plan`,cwd:t.cwd,workspaceRoot:t.workspaceRoot,env:t.env,telemetryFactory:t.telemetryFactory,warn:t.warn,enableLogs:!0,enableTraces:!0});return{recordError:(e,t,r)=>n.recordError(e,t,r),recordWarning:(e,t,r)=>n.recordWarning(e,t,r),recordEvent:(e,t)=>n.recordEvent(e,t),flush:()=>n.flush(),shutdown:()=>n.shutdown()}}export{t as PLAN_EVENT,n as createPlanTelemetry};
1
+ import{createDoomTelemetry as e}from"@agimon-ai/doompi-telemetry";const t={configLoadFailed:`doom_plan.config_load_failed`,writePlanFailed:`doom_plan.write_plan_failed`,writePlanTimedOut:`doom_plan.write_plan_timed_out`,writePlanUnsafePath:`doom_plan.write_plan_unsafe_path`,modelResolveFailed:`doom_plan.model_resolve_failed`,modeEnabled:`doom_plan.mode_enabled`,modeDisabled:`doom_plan.mode_disabled`,planWritten:`doom_plan.plan_written`,planPointerFailed:`doom_plan.plan_pointer_failed`,planReviewCompleted:`doom_plan.plan_review_completed`};function n(t={}){let n=e({serviceName:`doom-plan`,packageName:`@agimon-ai/doompi-plan`,cwd:t.cwd,workspaceRoot:t.workspaceRoot,env:t.env,telemetryFactory:t.telemetryFactory,warn:t.warn,enableLogs:!0,enableTraces:!0});return{recordError:(e,t,r)=>n.recordError(e,t,r),recordWarning:(e,t,r)=>n.recordWarning(e,t,r),recordEvent:(e,t)=>n.recordEvent(e,t),flush:()=>n.flush(),shutdown:()=>n.shutdown()}}export{t as PLAN_EVENT,n as createPlanTelemetry};
2
2
  //# sourceMappingURL=logSinkTelemetry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"logSinkTelemetry.mjs","names":[],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nconst SERVICE_NAME = 'doom-plan';\nconst PACKAGE_NAME = '@agimon-ai/doompi-plan';\n\nexport const PLAN_EVENT = {\n configLoadFailed: 'doom_plan.config_load_failed',\n writePlanFailed: 'doom_plan.write_plan_failed',\n writePlanTimedOut: 'doom_plan.write_plan_timed_out',\n writePlanUnsafePath: 'doom_plan.write_plan_unsafe_path',\n modelResolveFailed: 'doom_plan.model_resolve_failed',\n modeEnabled: 'doom_plan.mode_enabled',\n modeDisabled: 'doom_plan.mode_disabled',\n planWritten: 'doom_plan.plan_written',\n planReviewCompleted: 'doom_plan.plan_review_completed',\n} as const;\n\nexport type PlanEventName = (typeof PLAN_EVENT)[keyof typeof PLAN_EVENT];\nexport type PlanEventAttributes = Record<string, string | number | boolean>;\n\nexport interface PlanTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport interface PlanTelemetry {\n recordError(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordWarning(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordEvent(event: PlanEventName, attributes?: PlanEventAttributes): Promise<void>;\n flush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\nexport function createPlanTelemetry(options: PlanTelemetryOptions = {}): PlanTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n recordEvent: (event, attributes) => telemetry.recordEvent(event, attributes),\n flush: () => telemetry.flush(),\n shutdown: () => telemetry.shutdown(),\n };\n}\n"],"mappings":"kEAEA,MAGa,EAAa,CACxB,iBAAkB,+BAClB,gBAAiB,8BACjB,kBAAmB,iCACnB,oBAAqB,mCACrB,mBAAoB,iCACpB,YAAa,yBACb,aAAc,0BACd,YAAa,yBACb,oBAAqB,iCACvB,EAqBA,SAAgB,EAAoB,EAAgC,CAAC,EAAkB,CACrF,IAAM,EAA2B,EAAoB,CACnD,YAAa,YACb,YAAa,yBACb,IAAK,EAAQ,IACb,cAAe,EAAQ,cACvB,IAAK,EAAQ,IACb,iBAAkB,EAAQ,iBAC1B,KAAM,EAAQ,KACd,WAAY,GACZ,aAAc,EAChB,CAAC,EACD,MAAO,CACL,aAAc,EAAO,EAAO,IAAe,EAAU,YAAY,EAAO,EAAO,CAAU,EACzF,eAAgB,EAAO,EAAO,IAAe,EAAU,cAAc,EAAO,EAAO,CAAU,EAC7F,aAAc,EAAO,IAAe,EAAU,YAAY,EAAO,CAAU,EAC3E,UAAa,EAAU,MAAM,EAC7B,aAAgB,EAAU,SAAS,CACrC,CACF"}
1
+ {"version":3,"file":"logSinkTelemetry.mjs","names":[],"sources":["../../../src/adapters/telemetry/logSinkTelemetry.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nconst SERVICE_NAME = 'doom-plan';\nconst PACKAGE_NAME = '@agimon-ai/doompi-plan';\n\nexport const PLAN_EVENT = {\n configLoadFailed: 'doom_plan.config_load_failed',\n writePlanFailed: 'doom_plan.write_plan_failed',\n writePlanTimedOut: 'doom_plan.write_plan_timed_out',\n writePlanUnsafePath: 'doom_plan.write_plan_unsafe_path',\n modelResolveFailed: 'doom_plan.model_resolve_failed',\n modeEnabled: 'doom_plan.mode_enabled',\n modeDisabled: 'doom_plan.mode_disabled',\n planWritten: 'doom_plan.plan_written',\n planPointerFailed: 'doom_plan.plan_pointer_failed',\n planReviewCompleted: 'doom_plan.plan_review_completed',\n} as const;\n\nexport type PlanEventName = (typeof PLAN_EVENT)[keyof typeof PLAN_EVENT];\nexport type PlanEventAttributes = Record<string, string | number | boolean>;\n\nexport interface PlanTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport interface PlanTelemetry {\n recordError(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordWarning(event: PlanEventName, error: unknown, attributes?: PlanEventAttributes): Promise<void>;\n recordEvent(event: PlanEventName, attributes?: PlanEventAttributes): Promise<void>;\n flush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\nexport function createPlanTelemetry(options: PlanTelemetryOptions = {}): PlanTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n recordEvent: (event, attributes) => telemetry.recordEvent(event, attributes),\n flush: () => telemetry.flush(),\n shutdown: () => telemetry.shutdown(),\n };\n}\n"],"mappings":"kEAEA,MAGa,EAAa,CACxB,iBAAkB,+BAClB,gBAAiB,8BACjB,kBAAmB,iCACnB,oBAAqB,mCACrB,mBAAoB,iCACpB,YAAa,yBACb,aAAc,0BACd,YAAa,yBACb,kBAAmB,gCACnB,oBAAqB,iCACvB,EAqBA,SAAgB,EAAoB,EAAgC,CAAC,EAAkB,CACrF,IAAM,EAA2B,EAAoB,CACnD,YAAa,YACb,YAAa,yBACb,IAAK,EAAQ,IACb,cAAe,EAAQ,cACvB,IAAK,EAAQ,IACb,iBAAkB,EAAQ,iBAC1B,KAAM,EAAQ,KACd,WAAY,GACZ,aAAc,EAChB,CAAC,EACD,MAAO,CACL,aAAc,EAAO,EAAO,IAAe,EAAU,YAAY,EAAO,EAAO,CAAU,EACzF,eAAgB,EAAO,EAAO,IAAe,EAAU,cAAc,EAAO,EAAO,CAAU,EAC7F,aAAc,EAAO,IAAe,EAAU,YAAY,EAAO,CAAU,EAC3E,UAAa,EAAU,MAAM,EAC7B,aAAgB,EAAU,SAAS,CACrC,CACF"}
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./schemas/plan/planConfig.cjs");exports.PLAN_CONFIG_SECTION_ID=e.PLAN_CONFIG_SECTION_ID,exports.PLAN_SETTINGS=e.PLAN_SETTINGS,exports.planConfigSections=e.planConfigSections,exports.planSettingByFieldId=e.planSettingByFieldId;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types/planSettings.cjs"),t=require("./schemas/plan/planConfig.cjs");exports.PLAN_CONFIG_SECTION_ID=e.PLAN_CONFIG_SECTION_ID,exports.PLAN_SETTINGS=t.PLAN_SETTINGS,exports.planConfigSections=t.planConfigSections,exports.planSettingByFieldId=t.planSettingByFieldId;
@@ -1,2 +1,3 @@
1
- import { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, planConfigSections, planSettingByFieldId } from "./schemas/plan/planConfig.cjs";
2
- export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, planConfigSections, planSettingByFieldId };
1
+ import { PLAN_CONFIG_SECTION_ID } from "./types/planSettings.cjs";
2
+ import { PLAN_SETTINGS, PlanModelChoice, PlanSettingDescriptor, planConfigSections, planSettingByFieldId } from "./schemas/plan/planConfig.cjs";
3
+ export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, PlanSettingDescriptor, planConfigSections, planSettingByFieldId };
@@ -1,2 +1,3 @@
1
- import { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, planConfigSections, planSettingByFieldId } from "./schemas/plan/planConfig.mjs";
2
- export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, planConfigSections, planSettingByFieldId };
1
+ import { PLAN_CONFIG_SECTION_ID } from "./types/planSettings.mjs";
2
+ import { PLAN_SETTINGS, PlanModelChoice, PlanSettingDescriptor, planConfigSections, planSettingByFieldId } from "./schemas/plan/planConfig.mjs";
3
+ export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, PlanSettingDescriptor, planConfigSections, planSettingByFieldId };
@@ -1 +1 @@
1
- import{PLAN_CONFIG_SECTION_ID as e,PLAN_SETTINGS as t,planConfigSections as n,planSettingByFieldId as r}from"./schemas/plan/planConfig.mjs";export{e as PLAN_CONFIG_SECTION_ID,t as PLAN_SETTINGS,n as planConfigSections,r as planSettingByFieldId};
1
+ import{PLAN_CONFIG_SECTION_ID as e}from"./types/planSettings.mjs";import{PLAN_SETTINGS as t,planConfigSections as n,planSettingByFieldId as r}from"./schemas/plan/planConfig.mjs";export{e as PLAN_CONFIG_SECTION_ID,t as PLAN_SETTINGS,n as planConfigSections,r as planSettingByFieldId};
@@ -1,2 +1,2 @@
1
- let e=require("@agimon-ai/doompi-config"),t=require("@agimon-ai/doompi-extension-contracts/config");const n=`planning`,r=[`modes`,n],i=`main`,a=`subagents`,o=`inherit the session model`,s=`inherit`,c=[{id:`main.model`,label:`main model`,keyPath:[...r,i,`model`],placeholder:o,detail:`Model the main agent switches to while plan mode is on.`,models:!0,read:e=>e?.main?.model},{id:`main.thinking`,label:`main thinking`,keyPath:[...r,i,`thinking`],placeholder:s,detail:`Thinking level appended to the main model while planning.`,options:e.DOOM_PLANNING_THINKING_LEVELS,read:e=>e?.main?.thinking},{id:`subagents.model`,label:`subagent model`,keyPath:[...r,a,`model`],placeholder:o,detail:`Model forced onto delegated planning subagents.`,models:!0,read:e=>e?.subagents?.model},{id:`subagents.thinking`,label:`subagent thinking`,keyPath:[...r,a,`thinking`],placeholder:s,detail:`Thinking level appended to the subagent model.`,options:e.DOOM_PLANNING_THINKING_LEVELS,read:e=>e?.subagents?.thinking},{id:`plansdirectory`,label:`plans directory`,keyPath:[...r,`plansDirectory`],placeholder:`~/.pi/plans`,detail:`Absolute, repo-relative, or under ~. Written plans land here.`,read:e=>e?.plansDirectory}];function l(e){return c.find(t=>t.id===e)}function u(e){return[{id:`inherit`,label:o,detail:`Leaves the setting unset.`,action:t.CONFIG_ACTION.clear},...e.map(e=>({id:`${e.provider}/${e.id}`,label:e.id,group:e.provider,detail:e.provider,action:t.CONFIG_ACTION.set}))]}function d(e,n){if(e.options)return{kind:`enum`,choices:e.options.map(e=>({id:e,label:e,action:t.CONFIG_ACTION.set}))};if(!(!e.models||n.length===0))return{kind:`choice`,choices:u(n)}}function f(e,t,r=[]){let i=c.map(t=>{let n=t.read(e),i=d(t,r);return{id:t.id,label:t.label,kind:i?.kind??`text`,keyPath:t.keyPath.join(`.`),placeholder:t.placeholder,detail:t.detail,...n?{value:n}:{},...i?{choices:i.choices}:{}}});return[{id:n,title:`planning`,order:30,detail:`plan mode`,fields:i,...t?{notice:t,noticeLevel:`error`}:{}}]}exports.PLAN_CONFIG_SECTION_ID=n,exports.PLAN_SETTINGS=c,exports.planConfigSections=f,exports.planSettingByFieldId=l;
1
+ const e=require("../../types/planSettings.cjs");let t=require("@agimon-ai/doompi-extension-contracts/config");const n=`inherit`,r={"main.model":e=>e?.main?.model,"main.thinking":e=>e?.main?.thinking,"subagents.model":e=>e?.subagents?.model,"subagents.thinking":e=>e?.subagents?.thinking,plansdirectory:e=>e?.plansDirectory},i=e.PLAN_SETTING_SHAPES.map(e=>({...e,read:r[e.id]}));function a(e){return i.find(t=>t.id===e)}function o(r){return[{id:n,label:e.PLAN_SETTING_SHAPES.find(e=>e.models===!0)?.placeholder??n,detail:`Leaves the setting unset.`,action:t.CONFIG_ACTION.clear},...r.map(e=>({id:`${e.provider}/${e.id}`,label:e.id,group:e.provider,detail:e.provider,action:t.CONFIG_ACTION.set}))]}function s(e,n){if(e.options)return{kind:`enum`,choices:e.options.map(e=>({id:e,label:e,action:t.CONFIG_ACTION.set}))};if(!(!e.models||n.length===0))return{kind:`choice`,choices:o(n)}}function c(t,n,r=[]){let a=i.map(e=>{let n=e.read(t),i=s(e,r);return{id:e.id,label:e.label,kind:i?.kind??`text`,keyPath:e.keyPath.join(`.`),placeholder:e.placeholder,detail:e.detail,...n?{value:n}:{},...i?{choices:i.choices}:{}}});return[{id:e.PLAN_CONFIG_SECTION_ID,title:`planning`,order:30,detail:`plan mode`,fields:a,...n?{notice:n,noticeLevel:`error`}:{}}]}exports.PLAN_CONFIG_SECTION_ID=e.PLAN_CONFIG_SECTION_ID,exports.PLAN_SETTINGS=i,exports.planConfigSections=c,exports.planSettingByFieldId=a;
2
2
  //# sourceMappingURL=planConfig.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"planConfig.cjs","names":["DOOM_PLANNING_THINKING_LEVELS","CONFIG_ACTION"],"sources":["../../../src/schemas/plan/planConfig.ts"],"sourcesContent":["/**\n * Plan mode's slice of the `SPC e c` config panel.\n *\n * Plan mode reads `modes.planning` from disk on every activation, so an edit\n * here lands on the next time the mode is turned on with no restart and nothing\n * to invalidate.\n *\n * The descriptor table is the whole design: the panel renders it and the write\n * handler looks the key path back up in it, so adding a setting means adding a\n * row rather than a case.\n */\n\nimport { DOOM_PLANNING_THINKING_LEVELS } from '@agimon-ai/doompi-config';\nimport {\n CONFIG_ACTION,\n type ConfigChoice,\n type ConfigField,\n type ConfigSection,\n} from '@agimon-ai/doompi-extension-contracts/config';\nimport type { PlanningModeConfig } from './config.ts';\n\nexport const PLAN_CONFIG_SECTION_ID = 'planning';\nconst SECTION_ORDER = 30;\nconst PLANNING_PATH = ['modes', PLAN_CONFIG_SECTION_ID] as const;\nconst MAIN_KEY = 'main';\nconst SUBAGENTS_KEY = 'subagents';\n/** Both agents fall back to the session's own settings when left unset. */\nconst INHERIT_MODEL = 'inherit the session model';\nconst INHERIT_THINKING = 'inherit';\n/** Selecting this clears the setting, which is how the file spells \"inherit\". */\nconst INHERIT_CHOICE_ID = 'inherit';\n\ninterface PlanSettingDescriptor {\n readonly id: string;\n readonly label: string;\n readonly keyPath: readonly string[];\n readonly placeholder: string;\n readonly detail: string;\n /** Present for a closed set; the panel offers these rather than free text. */\n readonly options?: readonly string[];\n /** Offers the session's own models rather than free text. See `planConfigSections`. */\n readonly models?: boolean;\n readonly read: (config: PlanningModeConfig | undefined) => string | undefined;\n}\n\nexport const PLAN_SETTINGS: readonly PlanSettingDescriptor[] = [\n {\n id: 'main.model',\n label: 'main model',\n keyPath: [...PLANNING_PATH, MAIN_KEY, 'model'],\n placeholder: INHERIT_MODEL,\n detail: 'Model the main agent switches to while plan mode is on.',\n models: true,\n read: (config) => config?.main?.model,\n },\n {\n id: 'main.thinking',\n label: 'main thinking',\n keyPath: [...PLANNING_PATH, MAIN_KEY, 'thinking'],\n placeholder: INHERIT_THINKING,\n detail: 'Thinking level appended to the main model while planning.',\n options: DOOM_PLANNING_THINKING_LEVELS,\n read: (config) => config?.main?.thinking,\n },\n {\n id: 'subagents.model',\n label: 'subagent model',\n keyPath: [...PLANNING_PATH, SUBAGENTS_KEY, 'model'],\n placeholder: INHERIT_MODEL,\n detail: 'Model forced onto delegated planning subagents.',\n models: true,\n read: (config) => config?.subagents?.model,\n },\n {\n id: 'subagents.thinking',\n label: 'subagent thinking',\n keyPath: [...PLANNING_PATH, SUBAGENTS_KEY, 'thinking'],\n placeholder: INHERIT_THINKING,\n detail: 'Thinking level appended to the subagent model.',\n options: DOOM_PLANNING_THINKING_LEVELS,\n read: (config) => config?.subagents?.thinking,\n },\n {\n id: 'plansdirectory',\n label: 'plans directory',\n keyPath: [...PLANNING_PATH, 'plansDirectory'],\n placeholder: '~/.pi/plans',\n detail: 'Absolute, repo-relative, or under ~. Written plans land here.',\n read: (config) => config?.plansDirectory,\n },\n];\n\nexport function planSettingByFieldId(fieldId: string): PlanSettingDescriptor | undefined {\n return PLAN_SETTINGS.find((setting) => setting.id === fieldId);\n}\n\n/** A model this session could actually switch to, as the panel offers it. */\nexport interface PlanModelChoice {\n readonly provider: string;\n readonly id: string;\n}\n\n/**\n * The models the session can reach, plus the entry that clears the setting.\n *\n * Offered as a list because a model spec is `provider/id` and typing one from\n * memory is how a setting ends up naming a model this session cannot use. The\n * inherit entry CLEARS rather than writing a value: unset is what \"use the\n * session's own model\" means in the file.\n */\nfunction modelChoices(models: readonly PlanModelChoice[]): ConfigChoice[] {\n const inherit: ConfigChoice = {\n id: INHERIT_CHOICE_ID,\n label: INHERIT_MODEL,\n detail: 'Leaves the setting unset.',\n action: CONFIG_ACTION.clear,\n };\n return [\n inherit,\n ...models.map((model): ConfigChoice => ({\n id: `${model.provider}/${model.id}`,\n label: model.id,\n group: model.provider,\n detail: model.provider,\n action: CONFIG_ACTION.set,\n })),\n ];\n}\n\nfunction choicesFor(\n setting: PlanSettingDescriptor,\n models: readonly PlanModelChoice[],\n): { choices: ConfigChoice[]; kind: 'enum' | 'choice' } | undefined {\n // `set` rather than a bespoke action: choosing a level is the same write as\n // typing one, so it shares the handler.\n if (setting.options) {\n return {\n kind: 'enum',\n choices: setting.options.map((option) => ({ id: option, label: option, action: CONFIG_ACTION.set })),\n };\n }\n // A model setting with nothing to offer stays free text rather than opening\n // an empty list: a session with no authenticated provider can still be edited.\n if (!setting.models || models.length === 0) return undefined;\n return { kind: 'choice', choices: modelChoices(models) };\n}\n\nexport function planConfigSections(\n config: PlanningModeConfig | undefined,\n notice?: string,\n models: readonly PlanModelChoice[] = [],\n): readonly ConfigSection[] {\n const fields: ConfigField[] = PLAN_SETTINGS.map((setting) => {\n const value = setting.read(config);\n const offered = choicesFor(setting, models);\n return {\n id: setting.id,\n label: setting.label,\n kind: offered?.kind ?? ('text' as const),\n keyPath: setting.keyPath.join('.'),\n placeholder: setting.placeholder,\n detail: setting.detail,\n ...(value ? { value } : {}),\n ...(offered ? { choices: offered.choices } : {}),\n };\n });\n return [\n {\n id: PLAN_CONFIG_SECTION_ID,\n title: 'planning',\n order: SECTION_ORDER,\n detail: 'plan mode',\n fields,\n ...(notice ? { notice, noticeLevel: 'error' as const } : {}),\n },\n ];\n}\n"],"mappings":"oGAqBA,MAAa,EAAyB,WAEhC,EAAgB,CAAC,QAAS,CAAsB,EAChD,EAAW,OACX,EAAgB,YAEhB,EAAgB,4BAChB,EAAmB,UAiBZ,EAAkD,CAC7D,CACE,GAAI,aACJ,MAAO,aACP,QAAS,CAAC,GAAG,EAAe,EAAU,OAAO,EAC7C,YAAa,EACb,OAAQ,0DACR,OAAQ,GACR,KAAO,GAAW,GAAQ,MAAM,KAClC,EACA,CACE,GAAI,gBACJ,MAAO,gBACP,QAAS,CAAC,GAAG,EAAe,EAAU,UAAU,EAChD,YAAa,EACb,OAAQ,4DACR,QAASA,EAAAA,8BACT,KAAO,GAAW,GAAQ,MAAM,QAClC,EACA,CACE,GAAI,kBACJ,MAAO,iBACP,QAAS,CAAC,GAAG,EAAe,EAAe,OAAO,EAClD,YAAa,EACb,OAAQ,kDACR,OAAQ,GACR,KAAO,GAAW,GAAQ,WAAW,KACvC,EACA,CACE,GAAI,qBACJ,MAAO,oBACP,QAAS,CAAC,GAAG,EAAe,EAAe,UAAU,EACrD,YAAa,EACb,OAAQ,iDACR,QAASA,EAAAA,8BACT,KAAO,GAAW,GAAQ,WAAW,QACvC,EACA,CACE,GAAI,iBACJ,MAAO,kBACP,QAAS,CAAC,GAAG,EAAe,gBAAgB,EAC5C,YAAa,cACb,OAAQ,gEACR,KAAO,GAAW,GAAQ,cAC5B,CACF,EAEA,SAAgB,EAAqB,EAAoD,CACvF,OAAO,EAAc,KAAM,GAAY,EAAQ,KAAO,CAAO,CAC/D,CAgBA,SAAS,EAAa,EAAoD,CAOxE,MAAO,CACL,CANA,GAAI,UACJ,MAAO,EACP,OAAQ,4BACR,OAAQC,EAAAA,cAAc,KAGhB,EACN,GAAG,EAAO,IAAK,IAAyB,CACtC,GAAI,GAAG,EAAM,SAAS,GAAG,EAAM,KAC/B,MAAO,EAAM,GACb,MAAO,EAAM,SACb,OAAQ,EAAM,SACd,OAAQA,EAAAA,cAAc,GACxB,EAAE,CACJ,CACF,CAEA,SAAS,EACP,EACA,EACkE,CAGlE,GAAI,EAAQ,QACV,MAAO,CACL,KAAM,OACN,QAAS,EAAQ,QAAQ,IAAK,IAAY,CAAE,GAAI,EAAQ,MAAO,EAAQ,OAAQA,EAAAA,cAAc,GAAI,EAAE,CACrG,EAIE,MAAC,EAAQ,QAAU,EAAO,SAAW,GACzC,MAAO,CAAE,KAAM,SAAU,QAAS,EAAa,CAAM,CAAE,CACzD,CAEA,SAAgB,EACd,EACA,EACA,EAAqC,CAAC,EACZ,CAC1B,IAAM,EAAwB,EAAc,IAAK,GAAY,CAC3D,IAAM,EAAQ,EAAQ,KAAK,CAAM,EAC3B,EAAU,EAAW,EAAS,CAAM,EAC1C,MAAO,CACL,GAAI,EAAQ,GACZ,MAAO,EAAQ,MACf,KAAM,GAAS,MAAS,OACxB,QAAS,EAAQ,QAAQ,KAAK,GAAG,EACjC,YAAa,EAAQ,YACrB,OAAQ,EAAQ,OAChB,GAAI,EAAQ,CAAE,OAAM,EAAI,CAAC,EACzB,GAAI,EAAU,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,CAChD,CACF,CAAC,EACD,MAAO,CACL,CACE,GAAI,EACJ,MAAO,WACP,MAAO,GACP,OAAQ,YACR,SACA,GAAI,EAAS,CAAE,SAAQ,YAAa,OAAiB,EAAI,CAAC,CAC5D,CACF,CACF"}
1
+ {"version":3,"file":"planConfig.cjs","names":["PLAN_SETTING_SHAPES","CONFIG_ACTION","PLAN_CONFIG_SECTION_ID"],"sources":["../../../src/schemas/plan/planConfig.ts"],"sourcesContent":["/**\n * Plan mode's slice of the `SPC e c` config panel.\n *\n * Plan mode reads `modes.planning` from disk on every activation, so an edit\n * here lands on the next time the mode is turned on with no restart and nothing\n * to invalidate.\n *\n * The descriptor table is the whole design: the panel renders it and the write\n * handler looks the key path back up in it, so adding a setting means adding a\n * row rather than a case.\n */\n\nimport {\n CONFIG_ACTION,\n type ConfigChoice,\n type ConfigField,\n type ConfigSection,\n} from '@agimon-ai/doompi-extension-contracts/config';\nimport type { PlanningModeConfig } from './config.ts';\nimport { PLAN_CONFIG_SECTION_ID, PLAN_SETTING_SHAPES, type PlanSettingShape } from '../../types/planSettings.ts';\n\nexport { PLAN_CONFIG_SECTION_ID };\nconst SECTION_ORDER = 30;\n/** Selecting this clears the setting, which is how the file spells \"inherit\". */\nconst INHERIT_CHOICE_ID = 'inherit';\n\n/**\n * A planning setting as the terminal panel needs it: the shared shape, plus how\n * to read the value out of a loaded config. The reader stays here because it\n * touches the parsed config types, which the browser half never sees.\n */\nexport interface PlanSettingDescriptor extends PlanSettingShape {\n readonly read: (config: PlanningModeConfig | undefined) => string | undefined;\n}\n\nconst READERS: Readonly<Record<string, (config: PlanningModeConfig | undefined) => string | undefined>> = {\n 'main.model': (config) => config?.main?.model,\n 'main.thinking': (config) => config?.main?.thinking,\n 'subagents.model': (config) => config?.subagents?.model,\n 'subagents.thinking': (config) => config?.subagents?.thinking,\n plansdirectory: (config) => config?.plansDirectory,\n};\n\nexport const PLAN_SETTINGS: readonly PlanSettingDescriptor[] = PLAN_SETTING_SHAPES.map((shape) => ({\n ...shape,\n read: READERS[shape.id]!,\n}));\n\nexport function planSettingByFieldId(fieldId: string): PlanSettingDescriptor | undefined {\n return PLAN_SETTINGS.find((setting) => setting.id === fieldId);\n}\n\n/** A model this session could actually switch to, as the panel offers it. */\nexport interface PlanModelChoice {\n readonly provider: string;\n readonly id: string;\n}\n\n/**\n * The models the session can reach, plus the entry that clears the setting.\n *\n * Offered as a list because a model spec is `provider/id` and typing one from\n * memory is how a setting ends up naming a model this session cannot use. The\n * inherit entry CLEARS rather than writing a value: unset is what \"use the\n * session's own model\" means in the file.\n */\nfunction modelChoices(models: readonly PlanModelChoice[]): ConfigChoice[] {\n const inherit: ConfigChoice = {\n id: INHERIT_CHOICE_ID,\n label: PLAN_SETTING_SHAPES.find((shape) => shape.models === true)?.placeholder ?? INHERIT_CHOICE_ID,\n detail: 'Leaves the setting unset.',\n action: CONFIG_ACTION.clear,\n };\n return [\n inherit,\n ...models.map((model): ConfigChoice => ({\n id: `${model.provider}/${model.id}`,\n label: model.id,\n group: model.provider,\n detail: model.provider,\n action: CONFIG_ACTION.set,\n })),\n ];\n}\n\nfunction choicesFor(\n setting: PlanSettingDescriptor,\n models: readonly PlanModelChoice[],\n): { choices: ConfigChoice[]; kind: 'enum' | 'choice' } | undefined {\n // `set` rather than a bespoke action: choosing a level is the same write as\n // typing one, so it shares the handler.\n if (setting.options) {\n return {\n kind: 'enum',\n choices: setting.options.map((option) => ({ id: option, label: option, action: CONFIG_ACTION.set })),\n };\n }\n // A model setting with nothing to offer stays free text rather than opening\n // an empty list: a session with no authenticated provider can still be edited.\n if (!setting.models || models.length === 0) return undefined;\n return { kind: 'choice', choices: modelChoices(models) };\n}\n\nexport function planConfigSections(\n config: PlanningModeConfig | undefined,\n notice?: string,\n models: readonly PlanModelChoice[] = [],\n): readonly ConfigSection[] {\n const fields: ConfigField[] = PLAN_SETTINGS.map((setting) => {\n const value = setting.read(config);\n const offered = choicesFor(setting, models);\n return {\n id: setting.id,\n label: setting.label,\n kind: offered?.kind ?? ('text' as const),\n keyPath: setting.keyPath.join('.'),\n placeholder: setting.placeholder,\n detail: setting.detail,\n ...(value ? { value } : {}),\n ...(offered ? { choices: offered.choices } : {}),\n };\n });\n return [\n {\n id: PLAN_CONFIG_SECTION_ID,\n title: 'planning',\n order: SECTION_ORDER,\n detail: 'plan mode',\n fields,\n ...(notice ? { notice, noticeLevel: 'error' as const } : {}),\n },\n ];\n}\n"],"mappings":"8GAsBA,MAEM,EAAoB,UAWpB,EAAoG,CACxG,aAAe,GAAW,GAAQ,MAAM,MACxC,gBAAkB,GAAW,GAAQ,MAAM,SAC3C,kBAAoB,GAAW,GAAQ,WAAW,MAClD,qBAAuB,GAAW,GAAQ,WAAW,SACrD,eAAiB,GAAW,GAAQ,cACtC,EAEa,EAAkDA,EAAAA,oBAAoB,IAAK,IAAW,CACjG,GAAG,EACH,KAAM,EAAQ,EAAM,GACtB,EAAE,EAEF,SAAgB,EAAqB,EAAoD,CACvF,OAAO,EAAc,KAAM,GAAY,EAAQ,KAAO,CAAO,CAC/D,CAgBA,SAAS,EAAa,EAAoD,CAOxE,MAAO,CACL,CANA,GAAI,EACJ,MAAOA,EAAAA,oBAAoB,KAAM,GAAU,EAAM,SAAW,EAAI,CAAC,EAAE,aAAe,EAClF,OAAQ,4BACR,OAAQC,EAAAA,cAAc,KAGhB,EACN,GAAG,EAAO,IAAK,IAAyB,CACtC,GAAI,GAAG,EAAM,SAAS,GAAG,EAAM,KAC/B,MAAO,EAAM,GACb,MAAO,EAAM,SACb,OAAQ,EAAM,SACd,OAAQA,EAAAA,cAAc,GACxB,EAAE,CACJ,CACF,CAEA,SAAS,EACP,EACA,EACkE,CAGlE,GAAI,EAAQ,QACV,MAAO,CACL,KAAM,OACN,QAAS,EAAQ,QAAQ,IAAK,IAAY,CAAE,GAAI,EAAQ,MAAO,EAAQ,OAAQA,EAAAA,cAAc,GAAI,EAAE,CACrG,EAIE,MAAC,EAAQ,QAAU,EAAO,SAAW,GACzC,MAAO,CAAE,KAAM,SAAU,QAAS,EAAa,CAAM,CAAE,CACzD,CAEA,SAAgB,EACd,EACA,EACA,EAAqC,CAAC,EACZ,CAC1B,IAAM,EAAwB,EAAc,IAAK,GAAY,CAC3D,IAAM,EAAQ,EAAQ,KAAK,CAAM,EAC3B,EAAU,EAAW,EAAS,CAAM,EAC1C,MAAO,CACL,GAAI,EAAQ,GACZ,MAAO,EAAQ,MACf,KAAM,GAAS,MAAS,OACxB,QAAS,EAAQ,QAAQ,KAAK,GAAG,EACjC,YAAa,EAAQ,YACrB,OAAQ,EAAQ,OAChB,GAAI,EAAQ,CAAE,OAAM,EAAI,CAAC,EACzB,GAAI,EAAU,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,CAChD,CACF,CAAC,EACD,MAAO,CACL,CACE,GAAIC,EAAAA,uBACJ,MAAO,WACP,MAAO,GACP,OAAQ,YACR,SACA,GAAI,EAAS,CAAE,SAAQ,YAAa,OAAiB,EAAI,CAAC,CAC5D,CACF,CACF"}
@@ -1,17 +1,13 @@
1
1
  import { PlanningModeConfig } from "./config.cjs";
2
+ import { PLAN_CONFIG_SECTION_ID, PlanSettingShape } from "../../types/planSettings.cjs";
2
3
  import { ConfigSection } from "@agimon-ai/doompi-extension-contracts/config";
3
4
  //#region src/schemas/plan/planConfig.d.ts
4
- declare const PLAN_CONFIG_SECTION_ID = "planning";
5
- interface PlanSettingDescriptor {
6
- readonly id: string;
7
- readonly label: string;
8
- readonly keyPath: readonly string[];
9
- readonly placeholder: string;
10
- readonly detail: string;
11
- /** Present for a closed set; the panel offers these rather than free text. */
12
- readonly options?: readonly string[];
13
- /** Offers the session's own models rather than free text. See `planConfigSections`. */
14
- readonly models?: boolean;
5
+ /**
6
+ * A planning setting as the terminal panel needs it: the shared shape, plus how
7
+ * to read the value out of a loaded config. The reader stays here because it
8
+ * touches the parsed config types, which the browser half never sees.
9
+ */
10
+ interface PlanSettingDescriptor extends PlanSettingShape {
15
11
  readonly read: (config: PlanningModeConfig | undefined) => string | undefined;
16
12
  }
17
13
  declare const PLAN_SETTINGS: readonly PlanSettingDescriptor[];
@@ -23,5 +19,5 @@ interface PlanModelChoice {
23
19
  }
24
20
  declare function planConfigSections(config: PlanningModeConfig | undefined, notice?: string, models?: readonly PlanModelChoice[]): readonly ConfigSection[];
25
21
  //#endregion
26
- export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, planConfigSections, planSettingByFieldId };
22
+ export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, PlanSettingDescriptor, planConfigSections, planSettingByFieldId };
27
23
  //# sourceMappingURL=planConfig.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"planConfig.d.cts","names":[],"sources":["../../../src/schemas/plan/planConfig.ts"],"mappings":";;;cAqBa;UAWH;WACC;WACA;WACA;WACA;WACA;;WAEA;;WAEA;WACA,OAAO,QAAQ;;cAGb,wBAAwB;iBA+CrB,qBAAqB,kBAAkB;;UAKtC;WACN;WACA;;iBAgDK,mBACd,QAAQ,gCACR,iBACA,kBAAiB,6BACP"}
1
+ {"version":3,"file":"planConfig.d.cts","names":[],"sources":["../../../src/schemas/plan/planConfig.ts"],"mappings":";;;;;;;;;UA+BiB,8BAA8B;WACpC,OAAO,QAAQ;;cAWb,wBAAwB;iBAKrB,qBAAqB,kBAAkB;;UAKtC;WACN;WACA;;iBAgDK,mBACd,QAAQ,gCACR,iBACA,kBAAiB,6BACP"}
@@ -1,17 +1,13 @@
1
1
  import { PlanningModeConfig } from "./config.mjs";
2
+ import { PLAN_CONFIG_SECTION_ID, PlanSettingShape } from "../../types/planSettings.mjs";
2
3
  import { ConfigSection } from "@agimon-ai/doompi-extension-contracts/config";
3
4
  //#region src/schemas/plan/planConfig.d.ts
4
- declare const PLAN_CONFIG_SECTION_ID = "planning";
5
- interface PlanSettingDescriptor {
6
- readonly id: string;
7
- readonly label: string;
8
- readonly keyPath: readonly string[];
9
- readonly placeholder: string;
10
- readonly detail: string;
11
- /** Present for a closed set; the panel offers these rather than free text. */
12
- readonly options?: readonly string[];
13
- /** Offers the session's own models rather than free text. See `planConfigSections`. */
14
- readonly models?: boolean;
5
+ /**
6
+ * A planning setting as the terminal panel needs it: the shared shape, plus how
7
+ * to read the value out of a loaded config. The reader stays here because it
8
+ * touches the parsed config types, which the browser half never sees.
9
+ */
10
+ interface PlanSettingDescriptor extends PlanSettingShape {
15
11
  readonly read: (config: PlanningModeConfig | undefined) => string | undefined;
16
12
  }
17
13
  declare const PLAN_SETTINGS: readonly PlanSettingDescriptor[];
@@ -23,5 +19,5 @@ interface PlanModelChoice {
23
19
  }
24
20
  declare function planConfigSections(config: PlanningModeConfig | undefined, notice?: string, models?: readonly PlanModelChoice[]): readonly ConfigSection[];
25
21
  //#endregion
26
- export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, planConfigSections, planSettingByFieldId };
22
+ export { PLAN_CONFIG_SECTION_ID, PLAN_SETTINGS, PlanModelChoice, PlanSettingDescriptor, planConfigSections, planSettingByFieldId };
27
23
  //# sourceMappingURL=planConfig.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"planConfig.d.mts","names":[],"sources":["../../../src/schemas/plan/planConfig.ts"],"mappings":";;;cAqBa;UAWH;WACC;WACA;WACA;WACA;WACA;;WAEA;;WAEA;WACA,OAAO,QAAQ;;cAGb,wBAAwB;iBA+CrB,qBAAqB,kBAAkB;;UAKtC;WACN;WACA;;iBAgDK,mBACd,QAAQ,gCACR,iBACA,kBAAiB,6BACP"}
1
+ {"version":3,"file":"planConfig.d.mts","names":[],"sources":["../../../src/schemas/plan/planConfig.ts"],"mappings":";;;;;;;;;UA+BiB,8BAA8B;WACpC,OAAO,QAAQ;;cAWb,wBAAwB;iBAKrB,qBAAqB,kBAAkB;;UAKtC;WACN;WACA;;iBAgDK,mBACd,QAAQ,gCACR,iBACA,kBAAiB,6BACP"}
@@ -1,2 +1,2 @@
1
- import{DOOM_PLANNING_THINKING_LEVELS as e}from"@agimon-ai/doompi-config";import{CONFIG_ACTION as t}from"@agimon-ai/doompi-extension-contracts/config";const n=`planning`,r=[`modes`,n],i=`main`,a=`subagents`,o=`inherit the session model`,s=`inherit`,c=[{id:`main.model`,label:`main model`,keyPath:[...r,i,`model`],placeholder:o,detail:`Model the main agent switches to while plan mode is on.`,models:!0,read:e=>e?.main?.model},{id:`main.thinking`,label:`main thinking`,keyPath:[...r,i,`thinking`],placeholder:s,detail:`Thinking level appended to the main model while planning.`,options:e,read:e=>e?.main?.thinking},{id:`subagents.model`,label:`subagent model`,keyPath:[...r,a,`model`],placeholder:o,detail:`Model forced onto delegated planning subagents.`,models:!0,read:e=>e?.subagents?.model},{id:`subagents.thinking`,label:`subagent thinking`,keyPath:[...r,a,`thinking`],placeholder:s,detail:`Thinking level appended to the subagent model.`,options:e,read:e=>e?.subagents?.thinking},{id:`plansdirectory`,label:`plans directory`,keyPath:[...r,`plansDirectory`],placeholder:`~/.pi/plans`,detail:`Absolute, repo-relative, or under ~. Written plans land here.`,read:e=>e?.plansDirectory}];function l(e){return c.find(t=>t.id===e)}function u(e){return[{id:`inherit`,label:o,detail:`Leaves the setting unset.`,action:t.clear},...e.map(e=>({id:`${e.provider}/${e.id}`,label:e.id,group:e.provider,detail:e.provider,action:t.set}))]}function d(e,n){if(e.options)return{kind:`enum`,choices:e.options.map(e=>({id:e,label:e,action:t.set}))};if(!(!e.models||n.length===0))return{kind:`choice`,choices:u(n)}}function f(e,t,r=[]){let i=c.map(t=>{let n=t.read(e),i=d(t,r);return{id:t.id,label:t.label,kind:i?.kind??`text`,keyPath:t.keyPath.join(`.`),placeholder:t.placeholder,detail:t.detail,...n?{value:n}:{},...i?{choices:i.choices}:{}}});return[{id:n,title:`planning`,order:30,detail:`plan mode`,fields:i,...t?{notice:t,noticeLevel:`error`}:{}}]}export{n as PLAN_CONFIG_SECTION_ID,c as PLAN_SETTINGS,f as planConfigSections,l as planSettingByFieldId};
1
+ import{PLAN_CONFIG_SECTION_ID as e,PLAN_SETTING_SHAPES as t}from"../../types/planSettings.mjs";import{CONFIG_ACTION as n}from"@agimon-ai/doompi-extension-contracts/config";const r=`inherit`,i={"main.model":e=>e?.main?.model,"main.thinking":e=>e?.main?.thinking,"subagents.model":e=>e?.subagents?.model,"subagents.thinking":e=>e?.subagents?.thinking,plansdirectory:e=>e?.plansDirectory},a=t.map(e=>({...e,read:i[e.id]}));function o(e){return a.find(t=>t.id===e)}function s(e){return[{id:r,label:t.find(e=>e.models===!0)?.placeholder??r,detail:`Leaves the setting unset.`,action:n.clear},...e.map(e=>({id:`${e.provider}/${e.id}`,label:e.id,group:e.provider,detail:e.provider,action:n.set}))]}function c(e,t){if(e.options)return{kind:`enum`,choices:e.options.map(e=>({id:e,label:e,action:n.set}))};if(!(!e.models||t.length===0))return{kind:`choice`,choices:s(t)}}function l(t,n,r=[]){let i=a.map(e=>{let n=e.read(t),i=c(e,r);return{id:e.id,label:e.label,kind:i?.kind??`text`,keyPath:e.keyPath.join(`.`),placeholder:e.placeholder,detail:e.detail,...n?{value:n}:{},...i?{choices:i.choices}:{}}});return[{id:e,title:`planning`,order:30,detail:`plan mode`,fields:i,...n?{notice:n,noticeLevel:`error`}:{}}]}export{e as PLAN_CONFIG_SECTION_ID,a as PLAN_SETTINGS,l as planConfigSections,o as planSettingByFieldId};
2
2
  //# sourceMappingURL=planConfig.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"planConfig.mjs","names":[],"sources":["../../../src/schemas/plan/planConfig.ts"],"sourcesContent":["/**\n * Plan mode's slice of the `SPC e c` config panel.\n *\n * Plan mode reads `modes.planning` from disk on every activation, so an edit\n * here lands on the next time the mode is turned on with no restart and nothing\n * to invalidate.\n *\n * The descriptor table is the whole design: the panel renders it and the write\n * handler looks the key path back up in it, so adding a setting means adding a\n * row rather than a case.\n */\n\nimport { DOOM_PLANNING_THINKING_LEVELS } from '@agimon-ai/doompi-config';\nimport {\n CONFIG_ACTION,\n type ConfigChoice,\n type ConfigField,\n type ConfigSection,\n} from '@agimon-ai/doompi-extension-contracts/config';\nimport type { PlanningModeConfig } from './config.ts';\n\nexport const PLAN_CONFIG_SECTION_ID = 'planning';\nconst SECTION_ORDER = 30;\nconst PLANNING_PATH = ['modes', PLAN_CONFIG_SECTION_ID] as const;\nconst MAIN_KEY = 'main';\nconst SUBAGENTS_KEY = 'subagents';\n/** Both agents fall back to the session's own settings when left unset. */\nconst INHERIT_MODEL = 'inherit the session model';\nconst INHERIT_THINKING = 'inherit';\n/** Selecting this clears the setting, which is how the file spells \"inherit\". */\nconst INHERIT_CHOICE_ID = 'inherit';\n\ninterface PlanSettingDescriptor {\n readonly id: string;\n readonly label: string;\n readonly keyPath: readonly string[];\n readonly placeholder: string;\n readonly detail: string;\n /** Present for a closed set; the panel offers these rather than free text. */\n readonly options?: readonly string[];\n /** Offers the session's own models rather than free text. See `planConfigSections`. */\n readonly models?: boolean;\n readonly read: (config: PlanningModeConfig | undefined) => string | undefined;\n}\n\nexport const PLAN_SETTINGS: readonly PlanSettingDescriptor[] = [\n {\n id: 'main.model',\n label: 'main model',\n keyPath: [...PLANNING_PATH, MAIN_KEY, 'model'],\n placeholder: INHERIT_MODEL,\n detail: 'Model the main agent switches to while plan mode is on.',\n models: true,\n read: (config) => config?.main?.model,\n },\n {\n id: 'main.thinking',\n label: 'main thinking',\n keyPath: [...PLANNING_PATH, MAIN_KEY, 'thinking'],\n placeholder: INHERIT_THINKING,\n detail: 'Thinking level appended to the main model while planning.',\n options: DOOM_PLANNING_THINKING_LEVELS,\n read: (config) => config?.main?.thinking,\n },\n {\n id: 'subagents.model',\n label: 'subagent model',\n keyPath: [...PLANNING_PATH, SUBAGENTS_KEY, 'model'],\n placeholder: INHERIT_MODEL,\n detail: 'Model forced onto delegated planning subagents.',\n models: true,\n read: (config) => config?.subagents?.model,\n },\n {\n id: 'subagents.thinking',\n label: 'subagent thinking',\n keyPath: [...PLANNING_PATH, SUBAGENTS_KEY, 'thinking'],\n placeholder: INHERIT_THINKING,\n detail: 'Thinking level appended to the subagent model.',\n options: DOOM_PLANNING_THINKING_LEVELS,\n read: (config) => config?.subagents?.thinking,\n },\n {\n id: 'plansdirectory',\n label: 'plans directory',\n keyPath: [...PLANNING_PATH, 'plansDirectory'],\n placeholder: '~/.pi/plans',\n detail: 'Absolute, repo-relative, or under ~. Written plans land here.',\n read: (config) => config?.plansDirectory,\n },\n];\n\nexport function planSettingByFieldId(fieldId: string): PlanSettingDescriptor | undefined {\n return PLAN_SETTINGS.find((setting) => setting.id === fieldId);\n}\n\n/** A model this session could actually switch to, as the panel offers it. */\nexport interface PlanModelChoice {\n readonly provider: string;\n readonly id: string;\n}\n\n/**\n * The models the session can reach, plus the entry that clears the setting.\n *\n * Offered as a list because a model spec is `provider/id` and typing one from\n * memory is how a setting ends up naming a model this session cannot use. The\n * inherit entry CLEARS rather than writing a value: unset is what \"use the\n * session's own model\" means in the file.\n */\nfunction modelChoices(models: readonly PlanModelChoice[]): ConfigChoice[] {\n const inherit: ConfigChoice = {\n id: INHERIT_CHOICE_ID,\n label: INHERIT_MODEL,\n detail: 'Leaves the setting unset.',\n action: CONFIG_ACTION.clear,\n };\n return [\n inherit,\n ...models.map((model): ConfigChoice => ({\n id: `${model.provider}/${model.id}`,\n label: model.id,\n group: model.provider,\n detail: model.provider,\n action: CONFIG_ACTION.set,\n })),\n ];\n}\n\nfunction choicesFor(\n setting: PlanSettingDescriptor,\n models: readonly PlanModelChoice[],\n): { choices: ConfigChoice[]; kind: 'enum' | 'choice' } | undefined {\n // `set` rather than a bespoke action: choosing a level is the same write as\n // typing one, so it shares the handler.\n if (setting.options) {\n return {\n kind: 'enum',\n choices: setting.options.map((option) => ({ id: option, label: option, action: CONFIG_ACTION.set })),\n };\n }\n // A model setting with nothing to offer stays free text rather than opening\n // an empty list: a session with no authenticated provider can still be edited.\n if (!setting.models || models.length === 0) return undefined;\n return { kind: 'choice', choices: modelChoices(models) };\n}\n\nexport function planConfigSections(\n config: PlanningModeConfig | undefined,\n notice?: string,\n models: readonly PlanModelChoice[] = [],\n): readonly ConfigSection[] {\n const fields: ConfigField[] = PLAN_SETTINGS.map((setting) => {\n const value = setting.read(config);\n const offered = choicesFor(setting, models);\n return {\n id: setting.id,\n label: setting.label,\n kind: offered?.kind ?? ('text' as const),\n keyPath: setting.keyPath.join('.'),\n placeholder: setting.placeholder,\n detail: setting.detail,\n ...(value ? { value } : {}),\n ...(offered ? { choices: offered.choices } : {}),\n };\n });\n return [\n {\n id: PLAN_CONFIG_SECTION_ID,\n title: 'planning',\n order: SECTION_ORDER,\n detail: 'plan mode',\n fields,\n ...(notice ? { notice, noticeLevel: 'error' as const } : {}),\n },\n ];\n}\n"],"mappings":"sJAqBA,MAAa,EAAyB,WAEhC,EAAgB,CAAC,QAAS,CAAsB,EAChD,EAAW,OACX,EAAgB,YAEhB,EAAgB,4BAChB,EAAmB,UAiBZ,EAAkD,CAC7D,CACE,GAAI,aACJ,MAAO,aACP,QAAS,CAAC,GAAG,EAAe,EAAU,OAAO,EAC7C,YAAa,EACb,OAAQ,0DACR,OAAQ,GACR,KAAO,GAAW,GAAQ,MAAM,KAClC,EACA,CACE,GAAI,gBACJ,MAAO,gBACP,QAAS,CAAC,GAAG,EAAe,EAAU,UAAU,EAChD,YAAa,EACb,OAAQ,4DACR,QAAS,EACT,KAAO,GAAW,GAAQ,MAAM,QAClC,EACA,CACE,GAAI,kBACJ,MAAO,iBACP,QAAS,CAAC,GAAG,EAAe,EAAe,OAAO,EAClD,YAAa,EACb,OAAQ,kDACR,OAAQ,GACR,KAAO,GAAW,GAAQ,WAAW,KACvC,EACA,CACE,GAAI,qBACJ,MAAO,oBACP,QAAS,CAAC,GAAG,EAAe,EAAe,UAAU,EACrD,YAAa,EACb,OAAQ,iDACR,QAAS,EACT,KAAO,GAAW,GAAQ,WAAW,QACvC,EACA,CACE,GAAI,iBACJ,MAAO,kBACP,QAAS,CAAC,GAAG,EAAe,gBAAgB,EAC5C,YAAa,cACb,OAAQ,gEACR,KAAO,GAAW,GAAQ,cAC5B,CACF,EAEA,SAAgB,EAAqB,EAAoD,CACvF,OAAO,EAAc,KAAM,GAAY,EAAQ,KAAO,CAAO,CAC/D,CAgBA,SAAS,EAAa,EAAoD,CAOxE,MAAO,CACL,CANA,GAAI,UACJ,MAAO,EACP,OAAQ,4BACR,OAAQ,EAAc,KAGhB,EACN,GAAG,EAAO,IAAK,IAAyB,CACtC,GAAI,GAAG,EAAM,SAAS,GAAG,EAAM,KAC/B,MAAO,EAAM,GACb,MAAO,EAAM,SACb,OAAQ,EAAM,SACd,OAAQ,EAAc,GACxB,EAAE,CACJ,CACF,CAEA,SAAS,EACP,EACA,EACkE,CAGlE,GAAI,EAAQ,QACV,MAAO,CACL,KAAM,OACN,QAAS,EAAQ,QAAQ,IAAK,IAAY,CAAE,GAAI,EAAQ,MAAO,EAAQ,OAAQ,EAAc,GAAI,EAAE,CACrG,EAIE,MAAC,EAAQ,QAAU,EAAO,SAAW,GACzC,MAAO,CAAE,KAAM,SAAU,QAAS,EAAa,CAAM,CAAE,CACzD,CAEA,SAAgB,EACd,EACA,EACA,EAAqC,CAAC,EACZ,CAC1B,IAAM,EAAwB,EAAc,IAAK,GAAY,CAC3D,IAAM,EAAQ,EAAQ,KAAK,CAAM,EAC3B,EAAU,EAAW,EAAS,CAAM,EAC1C,MAAO,CACL,GAAI,EAAQ,GACZ,MAAO,EAAQ,MACf,KAAM,GAAS,MAAS,OACxB,QAAS,EAAQ,QAAQ,KAAK,GAAG,EACjC,YAAa,EAAQ,YACrB,OAAQ,EAAQ,OAChB,GAAI,EAAQ,CAAE,OAAM,EAAI,CAAC,EACzB,GAAI,EAAU,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,CAChD,CACF,CAAC,EACD,MAAO,CACL,CACE,GAAI,EACJ,MAAO,WACP,MAAO,GACP,OAAQ,YACR,SACA,GAAI,EAAS,CAAE,SAAQ,YAAa,OAAiB,EAAI,CAAC,CAC5D,CACF,CACF"}
1
+ {"version":3,"file":"planConfig.mjs","names":[],"sources":["../../../src/schemas/plan/planConfig.ts"],"sourcesContent":["/**\n * Plan mode's slice of the `SPC e c` config panel.\n *\n * Plan mode reads `modes.planning` from disk on every activation, so an edit\n * here lands on the next time the mode is turned on with no restart and nothing\n * to invalidate.\n *\n * The descriptor table is the whole design: the panel renders it and the write\n * handler looks the key path back up in it, so adding a setting means adding a\n * row rather than a case.\n */\n\nimport {\n CONFIG_ACTION,\n type ConfigChoice,\n type ConfigField,\n type ConfigSection,\n} from '@agimon-ai/doompi-extension-contracts/config';\nimport type { PlanningModeConfig } from './config.ts';\nimport { PLAN_CONFIG_SECTION_ID, PLAN_SETTING_SHAPES, type PlanSettingShape } from '../../types/planSettings.ts';\n\nexport { PLAN_CONFIG_SECTION_ID };\nconst SECTION_ORDER = 30;\n/** Selecting this clears the setting, which is how the file spells \"inherit\". */\nconst INHERIT_CHOICE_ID = 'inherit';\n\n/**\n * A planning setting as the terminal panel needs it: the shared shape, plus how\n * to read the value out of a loaded config. The reader stays here because it\n * touches the parsed config types, which the browser half never sees.\n */\nexport interface PlanSettingDescriptor extends PlanSettingShape {\n readonly read: (config: PlanningModeConfig | undefined) => string | undefined;\n}\n\nconst READERS: Readonly<Record<string, (config: PlanningModeConfig | undefined) => string | undefined>> = {\n 'main.model': (config) => config?.main?.model,\n 'main.thinking': (config) => config?.main?.thinking,\n 'subagents.model': (config) => config?.subagents?.model,\n 'subagents.thinking': (config) => config?.subagents?.thinking,\n plansdirectory: (config) => config?.plansDirectory,\n};\n\nexport const PLAN_SETTINGS: readonly PlanSettingDescriptor[] = PLAN_SETTING_SHAPES.map((shape) => ({\n ...shape,\n read: READERS[shape.id]!,\n}));\n\nexport function planSettingByFieldId(fieldId: string): PlanSettingDescriptor | undefined {\n return PLAN_SETTINGS.find((setting) => setting.id === fieldId);\n}\n\n/** A model this session could actually switch to, as the panel offers it. */\nexport interface PlanModelChoice {\n readonly provider: string;\n readonly id: string;\n}\n\n/**\n * The models the session can reach, plus the entry that clears the setting.\n *\n * Offered as a list because a model spec is `provider/id` and typing one from\n * memory is how a setting ends up naming a model this session cannot use. The\n * inherit entry CLEARS rather than writing a value: unset is what \"use the\n * session's own model\" means in the file.\n */\nfunction modelChoices(models: readonly PlanModelChoice[]): ConfigChoice[] {\n const inherit: ConfigChoice = {\n id: INHERIT_CHOICE_ID,\n label: PLAN_SETTING_SHAPES.find((shape) => shape.models === true)?.placeholder ?? INHERIT_CHOICE_ID,\n detail: 'Leaves the setting unset.',\n action: CONFIG_ACTION.clear,\n };\n return [\n inherit,\n ...models.map((model): ConfigChoice => ({\n id: `${model.provider}/${model.id}`,\n label: model.id,\n group: model.provider,\n detail: model.provider,\n action: CONFIG_ACTION.set,\n })),\n ];\n}\n\nfunction choicesFor(\n setting: PlanSettingDescriptor,\n models: readonly PlanModelChoice[],\n): { choices: ConfigChoice[]; kind: 'enum' | 'choice' } | undefined {\n // `set` rather than a bespoke action: choosing a level is the same write as\n // typing one, so it shares the handler.\n if (setting.options) {\n return {\n kind: 'enum',\n choices: setting.options.map((option) => ({ id: option, label: option, action: CONFIG_ACTION.set })),\n };\n }\n // A model setting with nothing to offer stays free text rather than opening\n // an empty list: a session with no authenticated provider can still be edited.\n if (!setting.models || models.length === 0) return undefined;\n return { kind: 'choice', choices: modelChoices(models) };\n}\n\nexport function planConfigSections(\n config: PlanningModeConfig | undefined,\n notice?: string,\n models: readonly PlanModelChoice[] = [],\n): readonly ConfigSection[] {\n const fields: ConfigField[] = PLAN_SETTINGS.map((setting) => {\n const value = setting.read(config);\n const offered = choicesFor(setting, models);\n return {\n id: setting.id,\n label: setting.label,\n kind: offered?.kind ?? ('text' as const),\n keyPath: setting.keyPath.join('.'),\n placeholder: setting.placeholder,\n detail: setting.detail,\n ...(value ? { value } : {}),\n ...(offered ? { choices: offered.choices } : {}),\n };\n });\n return [\n {\n id: PLAN_CONFIG_SECTION_ID,\n title: 'planning',\n order: SECTION_ORDER,\n detail: 'plan mode',\n fields,\n ...(notice ? { notice, noticeLevel: 'error' as const } : {}),\n },\n ];\n}\n"],"mappings":"4KAsBA,MAEM,EAAoB,UAWpB,EAAoG,CACxG,aAAe,GAAW,GAAQ,MAAM,MACxC,gBAAkB,GAAW,GAAQ,MAAM,SAC3C,kBAAoB,GAAW,GAAQ,WAAW,MAClD,qBAAuB,GAAW,GAAQ,WAAW,SACrD,eAAiB,GAAW,GAAQ,cACtC,EAEa,EAAkD,EAAoB,IAAK,IAAW,CACjG,GAAG,EACH,KAAM,EAAQ,EAAM,GACtB,EAAE,EAEF,SAAgB,EAAqB,EAAoD,CACvF,OAAO,EAAc,KAAM,GAAY,EAAQ,KAAO,CAAO,CAC/D,CAgBA,SAAS,EAAa,EAAoD,CAOxE,MAAO,CACL,CANA,GAAI,EACJ,MAAO,EAAoB,KAAM,GAAU,EAAM,SAAW,EAAI,CAAC,EAAE,aAAe,EAClF,OAAQ,4BACR,OAAQ,EAAc,KAGhB,EACN,GAAG,EAAO,IAAK,IAAyB,CACtC,GAAI,GAAG,EAAM,SAAS,GAAG,EAAM,KAC/B,MAAO,EAAM,GACb,MAAO,EAAM,SACb,OAAQ,EAAM,SACd,OAAQ,EAAc,GACxB,EAAE,CACJ,CACF,CAEA,SAAS,EACP,EACA,EACkE,CAGlE,GAAI,EAAQ,QACV,MAAO,CACL,KAAM,OACN,QAAS,EAAQ,QAAQ,IAAK,IAAY,CAAE,GAAI,EAAQ,MAAO,EAAQ,OAAQ,EAAc,GAAI,EAAE,CACrG,EAIE,MAAC,EAAQ,QAAU,EAAO,SAAW,GACzC,MAAO,CAAE,KAAM,SAAU,QAAS,EAAa,CAAM,CAAE,CACzD,CAEA,SAAgB,EACd,EACA,EACA,EAAqC,CAAC,EACZ,CAC1B,IAAM,EAAwB,EAAc,IAAK,GAAY,CAC3D,IAAM,EAAQ,EAAQ,KAAK,CAAM,EAC3B,EAAU,EAAW,EAAS,CAAM,EAC1C,MAAO,CACL,GAAI,EAAQ,GACZ,MAAO,EAAQ,MACf,KAAM,GAAS,MAAS,OACxB,QAAS,EAAQ,QAAQ,KAAK,GAAG,EACjC,YAAa,EAAQ,YACrB,OAAQ,EAAQ,OAChB,GAAI,EAAQ,CAAE,OAAM,EAAI,CAAC,EACzB,GAAI,EAAU,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,CAChD,CACF,CAAC,EACD,MAAO,CACL,CACE,GAAI,EACJ,MAAO,WACP,MAAO,GACP,OAAQ,YACR,SACA,GAAI,EAAS,CAAE,SAAQ,YAAa,OAAiB,EAAI,CAAC,CAC5D,CACF,CACF"}