@agimon-ai/doompi-plan 0.0.1-alpha.72 → 0.0.1-alpha.75

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 (152) hide show
  1. package/package.json +15 -14
  2. package/src/types/planApi.ts +19 -32
  3. package/dist/_virtual/_rolldown/runtime.cjs +0 -1
  4. package/dist/api-contracts.cjs +0 -1
  5. package/dist/api-contracts.d.cts +0 -2
  6. package/dist/api-contracts.d.mts +0 -2
  7. package/dist/api-contracts.mjs +0 -1
  8. package/dist/config.cjs +0 -1
  9. package/dist/config.d.cts +0 -2
  10. package/dist/config.d.mts +0 -2
  11. package/dist/config.mjs +0 -1
  12. package/dist/controllers/planApi.cjs +0 -2
  13. package/dist/controllers/planApi.cjs.map +0 -1
  14. package/dist/controllers/planApi.mjs +0 -2
  15. package/dist/controllers/planApi.mjs.map +0 -1
  16. package/dist/controllers/planMode.cjs +0 -7
  17. package/dist/controllers/planMode.cjs.map +0 -1
  18. package/dist/controllers/planMode.d.cts +0 -91
  19. package/dist/controllers/planMode.d.cts.map +0 -1
  20. package/dist/controllers/planMode.d.mts +0 -91
  21. package/dist/controllers/planMode.d.mts.map +0 -1
  22. package/dist/controllers/planMode.mjs +0 -7
  23. package/dist/controllers/planMode.mjs.map +0 -1
  24. package/dist/controllers/planServerSession.cjs +0 -4
  25. package/dist/controllers/planServerSession.cjs.map +0 -1
  26. package/dist/controllers/planServerSession.mjs +0 -4
  27. package/dist/controllers/planServerSession.mjs.map +0 -1
  28. package/dist/extensions/pi.cjs +0 -2
  29. package/dist/extensions/pi.cjs.map +0 -1
  30. package/dist/extensions/pi.d.cts +0 -5
  31. package/dist/extensions/pi.d.cts.map +0 -1
  32. package/dist/extensions/pi.d.mts +0 -5
  33. package/dist/extensions/pi.d.mts.map +0 -1
  34. package/dist/extensions/pi.mjs +0 -2
  35. package/dist/extensions/pi.mjs.map +0 -1
  36. package/dist/extensions/server.cjs +0 -2
  37. package/dist/extensions/server.cjs.map +0 -1
  38. package/dist/extensions/server.d.cts +0 -5
  39. package/dist/extensions/server.d.cts.map +0 -1
  40. package/dist/extensions/server.d.mts +0 -5
  41. package/dist/extensions/server.d.mts.map +0 -1
  42. package/dist/extensions/server.mjs +0 -2
  43. package/dist/extensions/server.mjs.map +0 -1
  44. package/dist/fable-flow.cjs +0 -1
  45. package/dist/fable-flow.d.cts +0 -2
  46. package/dist/fable-flow.d.mts +0 -2
  47. package/dist/fable-flow.mjs +0 -1
  48. package/dist/index.cjs +0 -1
  49. package/dist/index.d.cts +0 -5
  50. package/dist/index.d.mts +0 -5
  51. package/dist/index.mjs +0 -1
  52. package/dist/log-sink-telemetry.cjs +0 -1
  53. package/dist/log-sink-telemetry.d.cts +0 -2
  54. package/dist/log-sink-telemetry.d.mts +0 -2
  55. package/dist/log-sink-telemetry.mjs +0 -1
  56. package/dist/plan-config.cjs +0 -1
  57. package/dist/plan-config.d.cts +0 -3
  58. package/dist/plan-config.d.mts +0 -3
  59. package/dist/plan-config.mjs +0 -1
  60. package/dist/plan-mode.cjs +0 -1
  61. package/dist/plan-mode.d.cts +0 -3
  62. package/dist/plan-mode.d.mts +0 -3
  63. package/dist/plan-mode.mjs +0 -1
  64. package/dist/prompts.cjs +0 -1
  65. package/dist/prompts.d.cts +0 -2
  66. package/dist/prompts.d.mts +0 -2
  67. package/dist/prompts.mjs +0 -1
  68. package/dist/schemas/apiContracts.cjs +0 -2
  69. package/dist/schemas/apiContracts.cjs.map +0 -1
  70. package/dist/schemas/apiContracts.d.cts +0 -53
  71. package/dist/schemas/apiContracts.d.cts.map +0 -1
  72. package/dist/schemas/apiContracts.d.mts +0 -53
  73. package/dist/schemas/apiContracts.d.mts.map +0 -1
  74. package/dist/schemas/apiContracts.mjs +0 -2
  75. package/dist/schemas/apiContracts.mjs.map +0 -1
  76. package/dist/schemas/plan/config.cjs +0 -2
  77. package/dist/schemas/plan/config.cjs.map +0 -1
  78. package/dist/schemas/plan/config.d.cts +0 -13
  79. package/dist/schemas/plan/config.d.cts.map +0 -1
  80. package/dist/schemas/plan/config.d.mts +0 -13
  81. package/dist/schemas/plan/config.d.mts.map +0 -1
  82. package/dist/schemas/plan/config.mjs +0 -2
  83. package/dist/schemas/plan/config.mjs.map +0 -1
  84. package/dist/schemas/plan/planConfig.cjs +0 -2
  85. package/dist/schemas/plan/planConfig.cjs.map +0 -1
  86. package/dist/schemas/plan/planConfig.d.cts +0 -23
  87. package/dist/schemas/plan/planConfig.d.cts.map +0 -1
  88. package/dist/schemas/plan/planConfig.d.mts +0 -23
  89. package/dist/schemas/plan/planConfig.d.mts.map +0 -1
  90. package/dist/schemas/plan/planConfig.mjs +0 -2
  91. package/dist/schemas/plan/planConfig.mjs.map +0 -1
  92. package/dist/services/fableFlow/index.cjs +0 -3
  93. package/dist/services/fableFlow/index.cjs.map +0 -1
  94. package/dist/services/fableFlow/index.d.cts +0 -67
  95. package/dist/services/fableFlow/index.d.cts.map +0 -1
  96. package/dist/services/fableFlow/index.d.mts +0 -67
  97. package/dist/services/fableFlow/index.d.mts.map +0 -1
  98. package/dist/services/fableFlow/index.mjs +0 -3
  99. package/dist/services/fableFlow/index.mjs.map +0 -1
  100. package/dist/services/logSinkTelemetry/index.cjs +0 -2
  101. package/dist/services/logSinkTelemetry/index.cjs.map +0 -1
  102. package/dist/services/logSinkTelemetry/index.d.cts +0 -33
  103. package/dist/services/logSinkTelemetry/index.d.cts.map +0 -1
  104. package/dist/services/logSinkTelemetry/index.d.mts +0 -33
  105. package/dist/services/logSinkTelemetry/index.d.mts.map +0 -1
  106. package/dist/services/logSinkTelemetry/index.mjs +0 -2
  107. package/dist/services/logSinkTelemetry/index.mjs.map +0 -1
  108. package/dist/services/optionalTeamServices/index.cjs +0 -2
  109. package/dist/services/optionalTeamServices/index.cjs.map +0 -1
  110. package/dist/services/optionalTeamServices/index.mjs +0 -2
  111. package/dist/services/optionalTeamServices/index.mjs.map +0 -1
  112. package/dist/services/planPointer/index.cjs +0 -2
  113. package/dist/services/planPointer/index.cjs.map +0 -1
  114. package/dist/services/planPointer/index.mjs +0 -2
  115. package/dist/services/planPointer/index.mjs.map +0 -1
  116. package/dist/services/prompts/index.cjs +0 -3
  117. package/dist/services/prompts/index.cjs.map +0 -1
  118. package/dist/services/prompts/index.d.cts +0 -32
  119. package/dist/services/prompts/index.d.cts.map +0 -1
  120. package/dist/services/prompts/index.d.mts +0 -32
  121. package/dist/services/prompts/index.d.mts.map +0 -1
  122. package/dist/services/prompts/index.mjs +0 -3
  123. package/dist/services/prompts/index.mjs.map +0 -1
  124. package/dist/types/planApi.cjs +0 -2
  125. package/dist/types/planApi.cjs.map +0 -1
  126. package/dist/types/planApi.d.cts +0 -17
  127. package/dist/types/planApi.d.cts.map +0 -1
  128. package/dist/types/planApi.d.mts +0 -17
  129. package/dist/types/planApi.d.mts.map +0 -1
  130. package/dist/types/planApi.mjs +0 -2
  131. package/dist/types/planApi.mjs.map +0 -1
  132. package/dist/types/planPointer.d.cts +0 -26
  133. package/dist/types/planPointer.d.cts.map +0 -1
  134. package/dist/types/planPointer.d.mts +0 -26
  135. package/dist/types/planPointer.d.mts.map +0 -1
  136. package/dist/types/planSettings.cjs +0 -2
  137. package/dist/types/planSettings.cjs.map +0 -1
  138. package/dist/types/planSettings.d.cts +0 -27
  139. package/dist/types/planSettings.d.cts.map +0 -1
  140. package/dist/types/planSettings.d.mts +0 -27
  141. package/dist/types/planSettings.d.mts.map +0 -1
  142. package/dist/types/planSettings.mjs +0 -2
  143. package/dist/types/planSettings.mjs.map +0 -1
  144. package/src/extensions/web.ts +0 -62
  145. package/src/web/api/planApi.ts +0 -82
  146. package/src/web/components/PlanActivitySection.tsx +0 -63
  147. package/src/web/components/PlanPanel.tsx +0 -201
  148. package/src/web/components/PlanReviewPrompt.tsx +0 -80
  149. package/src/web/components/PlanToolMessage.tsx +0 -43
  150. package/src/web/lib/planSettings.ts +0 -31
  151. package/src/web/lib/planToolRender.ts +0 -242
  152. package/src/web/tsconfig.json +0 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-plan",
3
- "version": "0.0.1-alpha.72",
3
+ "version": "0.0.1-alpha.75",
4
4
  "description": "Reviewable Pi planning mode that removes file-editing tools and persists implementation plans.",
5
5
  "keywords": [
6
6
  "ai",
@@ -27,16 +27,13 @@
27
27
  },
28
28
  "files": [
29
29
  "dist",
30
- "src/web",
31
- "src/extensions/web.ts",
32
30
  "llms.txt",
33
31
  "src/prompts",
34
32
  "src/types/planApi.ts",
35
33
  "src/types/planSettings.ts",
36
34
  "README.md",
37
35
  "LICENSE",
38
- "package.json",
39
- "!src/web/**/*.stories.tsx"
36
+ "package.json"
40
37
  ],
41
38
  "type": "module",
42
39
  "main": "./dist/index.cjs",
@@ -63,6 +60,9 @@
63
60
  "import": "./dist/extensions/server.mjs",
64
61
  "require": "./dist/extensions/server.cjs"
65
62
  },
63
+ "./extensions/web": {
64
+ "import": "./dist/extensions/web.mjs"
65
+ },
66
66
  "./fable-flow": {
67
67
  "types": "./dist/fable-flow.d.mts",
68
68
  "import": "./dist/fable-flow.mjs",
@@ -95,17 +95,18 @@
95
95
  "registry": "https://registry.npmjs.org/"
96
96
  },
97
97
  "dependencies": {
98
- "@agimon-ai/doompi-config": "0.0.1-alpha.71",
99
- "@agimon-ai/doompi-core": "0.0.1-alpha.72",
100
- "@agimon-ai/doompi-minor-mode": "0.0.1-alpha.72",
101
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.69",
102
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.30",
103
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.32",
98
+ "@agimon-ai/doompi-config": "0.0.1-alpha.74",
99
+ "@agimon-ai/doompi-core": "0.0.1-alpha.75",
100
+ "@agimon-ai/doompi-minor-mode": "0.0.1-alpha.75",
101
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.72",
102
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.33",
103
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.35",
104
104
  "@deepseek-ai/cordis": "4.0.2",
105
105
  "hono": "4.13.7",
106
106
  "typebox": "1.3.30"
107
107
  },
108
108
  "devDependencies": {
109
+ "@agimon-ai/doompi-build": "0.0.1-alpha.4",
109
110
  "@earendil-works/pi-coding-agent": "0.85.1",
110
111
  "@tanstack/react-store": "0.11.1",
111
112
  "@tanstack/store": "0.11.1",
@@ -125,7 +126,7 @@
125
126
  "node": ">=22.19.0"
126
127
  },
127
128
  "doompiServer": {
128
- "entry": "./src/extensions/server.ts",
129
+ "entry": "./generated/server.ts",
129
130
  "dist": "./dist/extensions/server.mjs",
130
131
  "scopes": [
131
132
  "session"
@@ -138,7 +139,7 @@
138
139
  "doompiWeb": {
139
140
  "pluginId": "plan",
140
141
  "channels": [],
141
- "client": "./src/extensions/web.ts",
142
+ "client": "./dist/extensions/web.mjs",
142
143
  "scopes": [
143
144
  "global",
144
145
  "workspace",
@@ -153,7 +154,7 @@
153
154
  "scripts": {
154
155
  "build": "tsdown",
155
156
  "test": "vitest --run",
156
- "typecheck": "tsc --noEmit && tsc --noEmit -p src/web/tsconfig.json",
157
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json",
157
158
  "lint": "oxlint . && oxfmt . --check",
158
159
  "fixcode": "oxlint . --fix && oxfmt ."
159
160
  }
@@ -1,10 +1,11 @@
1
- import { sessionApiPath } from '@agimon-ai/doompi-core/web';
2
1
  /**
3
2
  * The wire vocabulary this package's API shares with whatever calls it.
4
3
  *
5
4
  * It lives under src/types because that is the one server root a browser
6
- * bundle may read: the cockpit plugin builds its URLs from these and the
7
- * routes answer them, so neither half can drift from the other.
5
+ * bundle may read: the routes answer with these shapes and the cockpit plugin
6
+ * reads them, so neither half can drift from the other. The paths themselves
7
+ * are not here; they are declared once in `./apiRoutes`, which the Hono app
8
+ * and the generated client both read.
8
9
  *
9
10
  * Two routes, and neither takes a path. A session has one current plan, so
10
11
  * `current` answers it and `content` takes the manual save back; the file they
@@ -16,9 +17,6 @@ import { sessionApiPath } from '@agimon-ai/doompi-core/web';
16
17
  /** Where a host mounts this package's API; the segment after /api/plugins/. */
17
18
  export const API_BASE_PATH = 'plans';
18
19
 
19
- /** Query parameter the cockpit hub reads to pick which session server to proxy to. */
20
- export const SESSION_QUERY_PARAM = 'session';
21
-
22
20
  /**
23
21
  * Footer status key the session publishes once a plan exists, which is what
24
22
  * makes the cockpit's activity group appear at all. It outlives plan mode: the
@@ -30,7 +28,21 @@ export const PLAN_STATUS_KEY = 'doom-plan-document';
30
28
  export const PLAN_REVIEW_TITLE = 'Plan complete. What would you like to do?';
31
29
  export const EXIT_PLAN_MODE_CHOICE = 'Exit plan mode and start implementation';
32
30
  export const CONTINUE_PLANNING_CHOICE = 'Continue planning';
33
- export const PLAN_REVIEW_OPTIONS = [EXIT_PLAN_MODE_CHOICE, CONTINUE_PLANNING_CHOICE] as const;
31
+ export const EXIT_PLAN_DECISION = 'exit';
32
+ export const CONTINUE_PLAN_DECISION = 'continue';
33
+
34
+ /**
35
+ * The label the reader sees paired with the decision the tool acts on. A headless
36
+ * client answers with `value`, so a facet never has to recognise the wording; Pi's
37
+ * own `ui.select` carries labels only, so its half maps the label back itself.
38
+ */
39
+ export const PLAN_REVIEW_CHOICES = [
40
+ { label: EXIT_PLAN_MODE_CHOICE, value: EXIT_PLAN_DECISION },
41
+ { label: CONTINUE_PLANNING_CHOICE, value: CONTINUE_PLAN_DECISION },
42
+ ] as const;
43
+
44
+ /** The same options as bare labels, which is all Pi's select and the composer prompt need. */
45
+ export const PLAN_REVIEW_OPTIONS = PLAN_REVIEW_CHOICES.map((choice) => choice.label);
34
46
 
35
47
  /** Between the plan's title and the stamp that marks a rewrite. */
36
48
  const STATUS_SEPARATOR = ' · ';
@@ -64,31 +76,6 @@ export function parsePlanStatus(raw: string | undefined): PlanStatusView | undef
64
76
  return { title: text.slice(0, cut).trim(), stamp: text.slice(cut + STATUS_SEPARATOR.length).trim() };
65
77
  }
66
78
 
67
- /** The current plan, relative to the API's own mount. */
68
- export function currentPath(): string {
69
- return '/current';
70
- }
71
-
72
- /** The manual save, relative to the API's own mount. */
73
- export function contentPath(): string {
74
- return '/content';
75
- }
76
-
77
- /**
78
- * The absolute URL a page fetches, through the hub. The whole query is built
79
- * here, session parameter included, so a caller never appends a second '?'.
80
- */
81
- export function currentUrl(sessionId: string): string {
82
- const search = new URLSearchParams({ [SESSION_QUERY_PARAM]: sessionId });
83
- return `${sessionApiPath(sessionId)}/plugins/${API_BASE_PATH}${currentPath()}?${search.toString()}`;
84
- }
85
-
86
- /** The absolute URL a page puts a manual save to. */
87
- export function contentUrl(sessionId: string): string {
88
- const search = new URLSearchParams({ [SESSION_QUERY_PARAM]: sessionId });
89
- return `${sessionApiPath(sessionId)}/plugins/${API_BASE_PATH}${contentPath()}?${search.toString()}`;
90
- }
91
-
92
79
  /**
93
80
  * What write_plan recorded about this session's plan.
94
81
  *
@@ -1 +0,0 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
@@ -1 +0,0 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./schemas/apiContracts.cjs");exports.apiContracts=e.apiContracts,exports.default=e.apiContracts;
@@ -1,2 +0,0 @@
1
- import { apiContracts } from "./schemas/apiContracts.cjs";
2
- export { apiContracts, apiContracts as default };
@@ -1,2 +0,0 @@
1
- import { apiContracts } from "./schemas/apiContracts.mjs";
2
- export { apiContracts, apiContracts as default };
@@ -1 +0,0 @@
1
- import{apiContracts as e}from"./schemas/apiContracts.mjs";export{e as apiContracts,e as default};
package/dist/config.cjs DELETED
@@ -1 +0,0 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./schemas/plan/config.cjs");let t=require("@agimon-ai/doompi-config");exports.getHarnessState=t.getHarnessState,exports.globalDoomConfigPath=t.globalDoomConfigPath,exports.loadDoomConfig=e.loadDoomConfig,exports.loadHarnessState=t.loadHarnessState,exports.mergeDoomConfigs=e.mergeDoomConfigs,exports.parseDoomConfig=e.parseDoomConfig,exports.parsePlanningModeConfig=t.parsePlanningModeConfig,exports.repositoryDoomConfigPath=t.repositoryDoomConfigPath,exports.resolvePlanningPlansDirectory=t.resolvePlanningPlansDirectory;
package/dist/config.d.cts DELETED
@@ -1,2 +0,0 @@
1
- import { DoomConfig, PlanningAgentConfig, PlanningModeConfig, PlanningThinkingLevel, getHarnessState, globalDoomConfigPath, loadDoomConfig, loadHarnessState, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory } from "./schemas/plan/config.cjs";
2
- export { DoomConfig, type PlanningAgentConfig, type PlanningModeConfig, type PlanningThinkingLevel, getHarnessState, globalDoomConfigPath, loadDoomConfig, loadHarnessState, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
package/dist/config.d.mts DELETED
@@ -1,2 +0,0 @@
1
- import { DoomConfig, PlanningAgentConfig, PlanningModeConfig, PlanningThinkingLevel, getHarnessState, globalDoomConfigPath, loadDoomConfig, loadHarnessState, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory } from "./schemas/plan/config.mjs";
2
- export { DoomConfig, type PlanningAgentConfig, type PlanningModeConfig, type PlanningThinkingLevel, getHarnessState, globalDoomConfigPath, loadDoomConfig, loadHarnessState, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
package/dist/config.mjs DELETED
@@ -1 +0,0 @@
1
- import{getHarnessState as e,globalDoomConfigPath as t,loadDoomConfig as n,loadHarnessState as r,mergeDoomConfigs as i,parseDoomConfig as a,parsePlanningModeConfig as o,repositoryDoomConfigPath as s,resolvePlanningPlansDirectory as c}from"./schemas/plan/config.mjs";export{e as getHarnessState,t as globalDoomConfigPath,n as loadDoomConfig,r as loadHarnessState,i as mergeDoomConfigs,a as parseDoomConfig,o as parsePlanningModeConfig,s as repositoryDoomConfigPath,c as resolvePlanningPlansDirectory};
@@ -1,2 +0,0 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/planPointer/index.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.PlanPointerService({env:process.env}),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
@@ -1 +0,0 @@
1
- {"version":3,"file":"planApi.cjs","names":["createHash","fs","Hono","PlanPointerService","currentPath","contentPath","API_BASE_PATH"],"sources":["../../src/controllers/planApi.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs/promises';\n\nimport type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-core/package-api';\nimport { Hono } from 'hono';\n\nimport { PlanPointerService } from '../services/planPointer';\nimport {\n API_BASE_PATH,\n contentPath,\n currentPath,\n type PlanDetailView,\n type PlanSaveRequest,\n type PlanSaveView,\n} from '../types/planApi';\nimport type { PlanPointerPort } from '../types/planPointer';\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 PlanPointerService({ env: process.env });\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":"wOAoCA,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,mBAAmB,CAAE,IAAK,QAAQ,GAAI,CAAC,EAC1E,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"}
@@ -1,2 +0,0 @@
1
- import{PlanPointerService as e}from"../services/planPointer/index.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({env:process.env}),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
@@ -1 +0,0 @@
1
- {"version":3,"file":"planApi.mjs","names":[],"sources":["../../src/controllers/planApi.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs/promises';\n\nimport type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-core/package-api';\nimport { Hono } from 'hono';\n\nimport { PlanPointerService } from '../services/planPointer';\nimport {\n API_BASE_PATH,\n contentPath,\n currentPath,\n type PlanDetailView,\n type PlanSaveRequest,\n type PlanSaveView,\n} from '../types/planApi';\nimport type { PlanPointerPort } from '../types/planPointer';\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 PlanPointerService({ env: process.env });\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":"mQAoCA,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,EAAmB,CAAE,IAAK,QAAQ,GAAI,CAAC,EAC1E,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,7 +0,0 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../schemas/plan/config.cjs"),n=require("../services/fableFlow/index.cjs"),r=require("../schemas/plan/planConfig.cjs"),i=require("../services/logSinkTelemetry/index.cjs"),a=require("../services/optionalTeamServices/index.cjs"),o=require("../services/planPointer/index.cjs"),s=require("../services/prompts/index.cjs"),c=require("../types/planApi.cjs");let l=require("@agimon-ai/doompi-config"),ee=require("node:buffer"),u=require("node:crypto"),d=require("node:fs");d=e.__toESM(d,1);let te=require("node:os");te=e.__toESM(te,1);let ne=require("node:path");ne=e.__toESM(ne,1);let f=require("@agimon-ai/doompi-config/configWriter"),p=require("@agimon-ai/doompi-core/author-tools"),m=require("@agimon-ai/doompi-core/config"),re=require("@agimon-ai/doompi-core/narration"),h=require("@agimon-ai/doompi-core/tool-surface"),g=require("@agimon-ai/doompi-core/ui-hub"),_=require("@agimon-ai/doompi-core/voice-tools"),v=require("@agimon-ai/doompi-minor-mode");const ie=`agent-harness-plan-mode`,ae=`agent-harness-plan-document`,oe=`complete_plan`,se=`write_plan`,y=`bash`,b=`find`,x=`grep`,S=`read`,ce=`subagent`,le=`task`,ue=`record_debug_evidence`,de=`run_fable_plan`,C=`implementation-plan`,w=`@agimon-ai/doompi-plan`,fe=`activate`,pe=`deactivate`,T=`warning`,me=`info`,he=`Refused to write the plan because its destination is not a regular file.`,E=`plan.trigger`,ge=`plan.model`,D=`plan.flavor`,_e=`not_found`,ve=`unauthenticated`,ye=`restore_failed`,be=new Set,xe=5e3,Se=`exit`,Ce=`continue`,we=[c.PLAN_REVIEW_TITLE,` 1. ${c.EXIT_PLAN_MODE_CHOICE}`,` 2. ${c.CONTINUE_PLANNING_CHOICE}`,` Type something.`].join(`
2
- `),O=`${c.PLAN_REVIEW_TITLE} Options: 1, ${c.EXIT_PLAN_MODE_CHOICE}; 2, ${c.CONTINUE_PLANNING_CHOICE}. Please choose one option. You may also answer in your own words.`,Te=(0,re.createNarrationRequest)(O),k=[`open_authoring_file`,...p.AUTHOR_FACADE_TOOL_NAMES],Ee=new Set([`add_directory`,`ask_user_question`,y,oe,b,x,`intercom`,`ls`,v.MINOR_MODE_TOOL_NAME,...k,..._.VOICE_MODE_TOOL_NAMES,S,`search_external_files`,ce,`subagent_supervisor`,`subagent_wait`,le,se]),De=new Set([`edit`,`write`,`mcp`]),Oe=[S,y,x,b,`ls`,`mcp`],A=`leader`,ke=`voice_tool`,Ae=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],je=[`idle`,`draft`,`review`,`completed`,`failed`,`cancelled`,`interrupted`],Me=`@agimon-ai/doompi-plan`,Ne={type:`string`,enum:[`normal`,`debug`,`fable`]},Pe={type:`object`,properties:{flavor:Ne},required:[`flavor`],additionalProperties:!1},Fe={type:`object`,properties:{},additionalProperties:!1},Ie={type:`object`,properties:{active:{type:`boolean`},flavor:{anyOf:[{...Ne},{type:`null`}]},changed:{type:`boolean`}},required:[`active`,`flavor`,`changed`],additionalProperties:!1};function Le(e){return(e.match(/^#\s+(.+?)\s*$/m)?.[1]??C).normalize(`NFKD`).replace(/[\u0300-\u036f]/g,``).toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,64)||C}function Re(e){let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleTimeString()}function ze(e){return`${encodeURIComponent(e).slice(0,48)||`session`}--${(0,u.createHash)(`sha256`).update(e).digest(`hex`)}`}function Be(e=new Date,t=(0,u.randomUUID)()){return`${e.toISOString().replace(/[-:]/g,``).replace(/\.\d{3}Z$/,`Z`)}--${t}`}function Ve(e,t){for(let n=e.length-1;n>=0;--n){let r=e[n];if(!r||typeof r!=`object`)continue;let i=r;if(i.type!==`message`||i.message?.role!==`assistant`||!Array.isArray(i.message.content))continue;let a=i.message.content.findIndex(e=>{if(!e||typeof e!=`object`)return!1;let n=e;return n.type===`toolCall`&&n.id===t&&n.name===se});if(!(a<0))return i.message.content.slice(0,a).flatMap(e=>{if(!e||typeof e!=`object`)return[];let t=e;return t.type===`text`&&typeof t.text==`string`?[t.text]:[]}).join(`
3
-
4
- `).trim()||void 0}}function He(e){return e.reason instanceof Error?e.reason:Error(`Plan write cancelled.`)}async function j(e,t){if(t.aborted)throw He(t);let n,r=new Promise((e,r)=>{n=()=>r(He(t)),t.addEventListener(`abort`,n,{once:!0})});try{return await Promise.race([e(),r])}finally{n&&t.removeEventListener(`abort`,n)}}function M(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function N(e){return typeof e==`string`}function P(e){if(M(e)&&N(e.provider)&&N(e.id)&&!(!e.provider||!e.id||e.provider.length>128||e.id.length>128))return{provider:e.provider,id:e.id}}function F(e){return N(e)&&Ae.includes(e)?e:void 0}function I(e){return N(e)&&je.includes(e)?e:void 0}function L(e){if(Array.isArray(e)&&!e.some(e=>!N(e)))return e.map(e=>e)}function Ue(e){if(!M(e))return;let t=L(e.tools),n=F(e.thinking);if(!t||n===void 0)return;let r=e.model===void 0?void 0:P(e.model);if(e.model===void 0||r)return{tools:t,...r?{model:r}:{},thinking:n}}function We(e){return e===`normal`||e===`debug`||e===`fable`?e:void 0}function R(e){if(e.version!==2)return;let t=e.activeFlavor===void 0?void 0:We(e.activeFlavor);if(e.activeFlavor!==void 0&&!t)return;let n=e.originalSnapshot===void 0?void 0:Ue(e.originalSnapshot);if(e.originalSnapshot!==void 0&&!n)return;let r=e.planId===void 0?void 0:N(e.planId)?e.planId:void 0;if(e.planId!==void 0&&r===void 0)return;let i=e.interruptedFableStage===void 0?void 0:I(e.interruptedFableStage);if(e.interruptedFableStage===void 0||i)return{version:2,...t?{activeFlavor:t}:{},...n?{originalSnapshot:n}:{},...r?{planId:r}:{},...i?{interruptedFableStage:i}:{}}}function z(e){if(e.enabled!==!0&&e.enabled!==!1)return;let t=e.toolsBeforePlanMode===void 0?void 0:L(e.toolsBeforePlanMode);if(e.toolsBeforePlanMode!==void 0&&!t)return;let n=e.modelBeforePlanMode===void 0?void 0:P(e.modelBeforePlanMode);if(e.modelBeforePlanMode!==void 0&&!n)return;let r=e.thinkingBeforePlanMode===void 0?void 0:F(e.thinkingBeforePlanMode);if(e.thinkingBeforePlanMode!==void 0&&r===void 0)return;let i=e.planId===void 0?void 0:N(e.planId)?e.planId:void 0;if(e.planId!==void 0&&i===void 0)return;let a=t&&r!==void 0?{tools:t,...n?{model:n}:{},thinking:r}:void 0;return{version:2,...e.enabled?{activeFlavor:`normal`}:{},...a?{originalSnapshot:a}:{},...i?{planId:i}:{}}}function Ge(e){if(M(e))return R(e)??z(e)}function Ke(e){if(!M(e)||!N(e.content)||!N(e.path)||!N(e.sessionId)||!N(e.title)||!N(e.writtenAt))return;let t=e.planId===void 0?void 0:N(e.planId)?e.planId:void 0;if(e.planId===void 0||t!==void 0)return{content:e.content,path:e.path,sessionId:e.sessionId,...t?{planId:t}:{},title:e.title,writtenAt:e.writtenAt}}function B(e,t,n){if(!N(e))throw Error(`${t} must be a string.`);let r=e.trim();if(n&&!r)throw Error(`${t} must not be empty.`);if(ee.Buffer.byteLength(r,`utf8`)>4096)throw Error(`${t} exceeds the bounded debug evidence limit.`);return r}function V(e,t){if(!Array.isArray(e)||e.length>32)throw Error(`${t} must be a bounded string array.`);return e.map((e,n)=>B(e,`${t}[${n}]`,!1))}function H(e,t){return e===void 0?``:B(e,t,!1)}function U(e,t){return e===void 0?[]:V(e,t)}function W(e,t,n){let r=new Set(t),i=Object.keys(e).find(e=>!r.has(e));if(i)throw Error(`${n} contains unsupported field '${i}'.`)}function qe(e){if(!M(e))throw Error(`Debug evidence must be an object.`);W(e,[`issue`,`expectedBehavior`,`reproductionAttempt`,`actualBehavior`,`logs`,`correlatedTraceEvidence`,`processOutput`,`browserConsoleEvidence`,`correlationIds`,`timestamps`,`verifiedFacts`,`hypotheses`,`unavailableEvidence`],`Debug evidence`);let t={issue:B(e.issue,`issue`,!0),expectedBehavior:H(e.expectedBehavior,`expectedBehavior`),reproductionAttempt:H(e.reproductionAttempt,`reproductionAttempt`),actualBehavior:H(e.actualBehavior,`actualBehavior`),logs:U(e.logs,`logs`),correlatedTraceEvidence:U(e.correlatedTraceEvidence,`correlatedTraceEvidence`),processOutput:U(e.processOutput,`processOutput`),browserConsoleEvidence:U(e.browserConsoleEvidence,`browserConsoleEvidence`),correlationIds:U(e.correlationIds,`correlationIds`),timestamps:U(e.timestamps,`timestamps`),verifiedFacts:U(e.verifiedFacts,`verifiedFacts`),hypotheses:U(e.hypotheses,`hypotheses`),unavailableEvidence:U(e.unavailableEvidence,`unavailableEvidence`)};if(ee.Buffer.byteLength(JSON.stringify(t),`utf8`)>32768)throw Error(`Debug evidence exceeds the bounded packet size.`);return t}function Je(e,t){return n=>n.filter(n=>n===oe||n===se?e!==void 0:n===de?e===`fable`:n===ue?e===`debug`:t.has(n)?e===void 0||e===`debug`:e===void 0||!De.has(n))}function G(e){if(e.output=!1,e.progress=!1,e.worktree=!1,Array.isArray(e.parallel))for(let t of e.parallel)G(t);else e.parallel&&G(e.parallel)}function Ye(e){if(e.action!==void 0){e.action===`run`&&(e.artifacts=!1);return}e.artifacts=!1,e.output=!1,e.share=!1,e.progress=!1,e.worktree=!1,delete e.chainDir,delete e.outputSchema,delete e.sessionDir;for(let t of e.tasks??[])G(t);for(let t of e.chain??[])G(t)}function Xe(e,t){if(!e?.model&&!e?.thinking)return;let n=e.model??(t?`${t.provider}/${t.id}`:void 0);if(n)return e.thinking?`${n.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/,``)}:${e.thinking}`:n}function Ze(e,t){if(e.action===`run`)for(let n of e.requests??[])n.model=t}function Qe(e,t){if(e.action===`assign`)for(let n of e.assignments??[])n.model=t}function $e(e){return new Set([`append-step`,`create`,`delete`,`disable`,`eject`,`enable`,`grant-spawn-budget`,`reset`,`schedule`,`schedule-cancel`,`update`,`watchdog.configure`]).has(e??``)}function et(e,t){let n=e.indexOf(`/`);if(n>0)return t.modelRegistry.find(e.slice(0,n),e.slice(n+1));let r=t.modelRegistry.getAvailable().filter(t=>t.id===e);return r.find(e=>e.provider===t.model?.provider)??(r.length===1?r[0]:void 0)}function tt(e=process.env,n=process.cwd()){return t.loadDoomConfig((0,l.loadHarnessState)(e).state.root??n).modes?.planning}function nt(e){let t={key:`p`,label:`plan`,detail:`read-only planning modes`,order:60};return[e?{id:`plan.exit`,path:[t,{key:`e`,label:`exit`,detail:`restore and exit`,tone:`exit`}],action:{name:`plan.exit`}}:{id:`plan.normal`,path:[t,{key:`e`,label:`enter`,detail:`read-only planning`}],action:{name:`plan.normal`}},{id:`plan.debug`,path:[t,{key:`d`,label:`debug`,detail:`adaptive debug planning`}],action:{name:`plan.debug`}},{id:`plan.fable`,path:[t,{key:`f`,label:`fable`,detail:`repository-aware draft`}],action:{name:`plan.fable`}}]}function K(e,t=tt,u=i.createPlanTelemetry(),p={}){let b=!0,x=0,S,C,O,k,De,Ae,je,Ne=()=>{let e=je?.modelRegistry;return e?e.getAvailable().filter(t=>e.hasConfiguredAuth(t)).map(e=>({provider:e.provider,id:e.id})):[]},N=!1,P,F,I,L,Ue,R,z,B,V=!1,H=!1,U=!1,W,G,K=`idle`,q,J,Y,rt,it=Promise.resolve(),at=p.doomIntegrations??!0,ot=p.planPointers??new o.PlanPointerService({env:process.env}),st=new Set(p.debugDiagnosticTools??be),ct,lt=()=>Je(N?P:void 0,st),ut=()=>{ct?.update(lt())},dt=new AbortController,ft=p.fableBroker??{start:(e,t)=>{let n=R;if(!n)throw Error(`The session-bound Fable broker is unavailable.`);return n.start(e,t)},cancel:(e,t)=>{R?.cancel({requester:`@agimon-ai/doompi-plan`,operationId:e,reason:t})}},pt=()=>p.fableBroker!==void 0||R!==void 0;function X(e=x){if(!b||e!==x)throw Error(`Plan runtime is disposed or stale.`)}async function mt(){let e=W;if(!e||!e.modes.list().some(e=>e.descriptor.source===_.DOOM_VOICE_SOURCE&&e.descriptor.id===_.DOOM_VOICE_AUTO_MODE_ID&&e.state.activation===`active`))return!1;try{return await e.narration.request(Te),!0}catch(e){return u.recordWarning(i.PLAN_EVENT.planReviewCompleted,e,{"plan.outcome":`narration_failed`}),!1}}function ht(){return{content:[{type:`text`,text:`${we}\n\nThe choices were spoken through autonomous voice. Stop now and wait for the user's next message; it will arrive as an ordinary user message.`}],details:{answers:[],awaitingResponse:!0,cancelled:!1,delivery:`voice`,voicePrompt:we},terminate:!0}}function gt(){return S??=(async()=>{b=!1,x+=1,dt.abort(Error(`Plan runtime is shutting down.`));let e=J,t=F,n=[];rt?.cancel(`Plan runtime is shutting down.`);let r=[it,...Y?[Y]:[]],i=await Promise.allSettled(r);for(let e of i)e.status===`rejected`&&n.push(e.reason);if(N&&t&&e)try{await Tt(e,t,!0)&&ct?.update(Je(void 0,st))}catch(e){n.push(e)}let a=[()=>xt(e),()=>L?.dispose(),()=>De?.(),()=>k?.dispose(),()=>O?.dispose(),()=>C?.dispose()];for(let e of a)try{e()}catch(e){n.push(e)}L=void 0,W=void 0,De=void 0,k=void 0,O=void 0,C=void 0,Y=void 0,J=void 0,U=!1,N=!1,P=void 0,F=void 0,I=void 0;try{await u.shutdown()}catch(e){n.push(e)}if(n.length>0)throw AggregateError(n,`Plan runtime cleanup failed.`)})(),S}function _t(){b&&O?.update(nt(N))}function vt(){let e=P,t=e===`fable`&&K!==`idle`?` · ${K}`:``;return{activation:N?`active`:`inactive`,condition:K===`failed`?`failed`:K===`interrupted`?`degraded`:`ready`,...N&&e?{detail:`${e}${t} - read only`,color:T,modelContextVariant:e}:{},actions:[{id:fe,enabled:!0},...N?[{id:pe,enabled:!0}]:[{id:pe,enabled:!1,disabledReason:`Plan mode is inactive.`}]]}}function yt(e,t){try{return e.ui.theme.fg(T,t)}catch{return t}}function Z(e){if(!b)return;_t();let t=P,n=N&&t?`plan:${t}`:void 0;e.ui.setStatus(`plan-mode`,n?yt(e,n):void 0),C?.publish(vt())}function bt(e,t){try{ot.write(e.sessionManager.getSessionId(),{path:t.path,title:t.title,writtenAt:t.writtenAt,...t.planId===void 0?{}:{planId:t.planId}})}catch(e){u.recordWarning(i.PLAN_EVENT.planPointerFailed,`The plan pointer was not recorded.`,{"plan.reason":e instanceof Error?e.name:`unknown`})}e.hasUI&&(e.ui.setStatus(c.PLAN_STATUS_KEY,c.formatPlanStatus(t.title,Re(t.writtenAt))),H=!0)}function xt(e){if(e){try{ot.clear(e.sessionManager.getSessionId())}catch{}H&&(H=!1,e.hasUI&&e.ui.setStatus(c.PLAN_STATUS_KEY,void 0))}}function St(){if(!b||!at||!N||!P||!J)return;let e=[...Oe],t={owner:w,allowedTools:e,requiredTools:[y],allowMcpTools:!0,allowedExternalProfiles:P===`fable`?[n.FABLE_PLAN_PROFILE]:[],denyExtensions:!1};L?L.update(t):L=Ue?.register(t)}function Q(){b&&e.appendEntry(ie,{version:2,...N&&P?{activeFlavor:P}:{},...F?{originalSnapshot:{tools:[...F.tools],...F.model?{model:{...F.model}}:{},thinking:F.thinking}}:{},...B?{planId:B}:{},...q?{interruptedFableStage:q}:{}})}function Ct(e,t,n){u.recordWarning(i.PLAN_EVENT.modelResolveFailed,`Planning model ${n}: ${t}`,{[ge]:t,"plan.model.phase":e,"plan.model.reason":n})}async function wt(t){let n=x;X(n);let r=I?.main;if(r){if(r.model){let i=et(r.model,t);if(!i)t.ui.notify(`Planning model not found: ${r.model}`,T),Ct(`apply`,r.model,_e);else{let a=await e.setModel(i);X(n),a||(t.ui.notify(`Planning model is unavailable without authentication: ${r.model}`,T),Ct(`apply`,r.model,ve))}}X(n),r.thinking&&e.setThinkingLevel(r.thinking)}}async function Tt(t,n,r=!1){let a=x;if(r||X(a),!n)return!1;if(n.model){let o=`${n.model.provider}/${n.model.id}`,s=t.modelRegistry.find(n.model.provider,n.model.id);if(!s)return t.ui.notify(`Previous model not found: ${o}`,T),Ct(`restore`,o,_e),!1;try{let n=await e.setModel(s);if(r||X(a),!n)return t.ui.notify(`Previous model is unavailable without authentication: ${o}`,T),Ct(`restore`,o,ve),!1}catch(e){return r||X(a),t.ui.notify(`Previous model could not be restored: ${o}`,T),Ct(`restore`,o,ye),u.recordError(i.PLAN_EVENT.modelResolveFailed,e,{[ge]:o,"plan.model.phase":`restore`,"plan.model.reason":ye}),!1}}return r||X(a),e.setThinkingLevel(n.thinking),!0}function Et(e){N&&P?e.ui.notify(`Already in plan:${P}.`,me):e.ui.notify(`Plan mode is inactive.`,me)}function $(e){let t=x,n=async()=>{X(t);let n=await e();return X(t),n},r=it.then(n,n);return it=r.then(()=>void 0,()=>void 0),r}async function Dt(e){let t=x;if(X(t),!Y&&!K.match(/^(draft|review)$/u))return;(K===`draft`||K===`review`)&&(q=K),rt?.cancel(e);let n=Y;if(n)try{await n,X(t)}catch(e){X(t),u.recordError(i.PLAN_EVENT.modeDisabled,e,{[D]:`fable`,"plan.fable.reason":`cancel_failed`})}X(t),Y=void 0,q&&(K=`interrupted`),J&&Z(J),Q()}async function Ot(n,r,a,o){let s=x;if(X(s),J=n,N){if(P===r){Et(n);return}P===`fable`&&await Dt(`Leaving Fable for plan:${r}.`),X(s),P=r,St(),ut(),Z(n),Q(),u.recordEvent(i.PLAN_EVENT.modeEnabled,{[E]:a,[D]:r,"plan.tool.count":e.getActiveTools().length});return}let c;try{c=t()}catch(e){throw u.recordError(i.PLAN_EVENT.configLoadFailed,e,{[E]:a}),e}o?B??=Be():(z=void 0,V=!1,U=!1,B=Be(),q=void 0,K=`idle`),I=c,F??={tools:[...e.getActiveTools()],...n.model?{model:{provider:n.model.provider,id:n.model.id}}:{},thinking:e.getThinkingLevel()},N=!0,P=r,ut(),St(),await wt(n),X(s),Z(n),Q(),u.recordEvent(i.PLAN_EVENT.modeEnabled,{[E]:a,[D]:r,"plan.tool.count":e.getActiveTools().length,...I?.main?.model?{[ge]:I.main.model}:{},...I?.main?.thinking?{"plan.thinking":I.main.thinking}:{}})}async function kt(e,t){let n=x;if(X(n),!N)return Et(e),!1;await Dt(`Plan mode is exiting.`),X(n);let r=await Tt(e,F);if(X(n),!r)return e.ui.notify(`Previous model was not restored. Plan mode remains read-only.`,T),Z(e),Q(),u.recordWarning(i.PLAN_EVENT.modeDisabled,`Plan mode restoration failed.`,{[E]:t,[D]:P??`unknown`,"plan.restored":!1}),!1;let a=!!z;return ct?.update(Je(void 0,st)),L?.dispose(),L=void 0,N=!1,P=void 0,I=void 0,U=!1,F=void 0,G=void 0,K=`idle`,q=void 0,Z(e),Q(),u.recordEvent(i.PLAN_EVENT.modeDisabled,{[E]:t,[D]:`normal`,"plan.written":a,"plan.restored":!0}),!0}rt=n.createFablePlanFlow({broker:ft,isAuthorized:()=>b&&N&&P===`fable`&&pt(),onStage:e=>{b&&(K=e,(e===`completed`||e===`failed`||e===`cancelled`)&&(q=void 0),J&&Z(J),N&&Q())},onError:e=>{b&&u.recordError(i.PLAN_EVENT.modeEnabled,e,{[D]:`fable`,"plan.fable.reason":`flow_error`})}});async function At(e,t){if(X(),u.recordEvent(i.PLAN_EVENT.planReviewCompleted,{"plan.outcome":e===Se?`exited`:`continued`}),e===Se){let e=await $(()=>kt(t,`plan_approved`));return{content:[{type:`text`,text:e?`The user approved exiting plan mode. Full tool access is restored. Begin implementing the approved plan.`:`The previous model could not be restored. Plan mode remains read-only.`}],details:{exited:e}}}return V=!1,{content:[{type:`text`,text:`The user chose to continue planning. Remain read-only and refine the plan.`}],details:{exited:!1}}}return{services:[e=>{if(e.inject([h.DOOM_TOOL_SURFACE_SERVICE],e=>{let t=(0,h.readDoomToolSurface)(e);if(!t)return;let n=t.register({source:w,restrict:lt()});return ct=n,()=>{ct===n&&(ct=void 0,n.dispose())}}),e.inject([a.DOOM_FABLE_PLAN_SERVICE],e=>{let t=a.readDoomFablePlanService(e);if(t)return R=t,()=>{R===t&&(R=void 0,Y&&rt?.cancel(`The Team Fable service was unloaded.`))}}),e.inject([a.DOOM_SUBAGENT_POLICY_SERVICE],e=>{let t=a.readDoomSubagentPolicyService(e);if(t)return Ue=t,St(),()=>{Ue===t&&(L?.dispose(),L=void 0,Ue=void 0)}}),at&&(e.inject([v.DOOM_MINOR_MODE_CATALOG_SERVICE,re.DOOM_NARRATION_SERVICE],e=>{let t={modes:(0,v.requireMinorModeCatalog)(e),narration:(0,re.requireDoomNarrationService)(e)};return W=t,()=>{W===t&&(W=void 0)}}),e.inject([v.DOOM_MINOR_MODE_CATALOG_SERVICE],e=>{let t=(0,v.registerMinorModeOwner)((0,v.requireMinorModeCatalog)(e),{descriptor:{source:w,id:`plan`,label:`Plan`,description:`Read-only planning with normal, debug, and Fable flavors.`,order:40,actions:[{id:fe,label:`Activate`,description:`Activate plan mode or switch its planning flavor.`,contexts:[`tui`,`headless`],parameters:[{name:`flavor`,label:`Flavor`,kind:`enum`,required:!0,choices:[{value:`normal`,label:`Normal`},{value:`debug`,label:`Debug`},{value:`fable`,label:`Fable`}]}]},{id:pe,label:`Deactivate`,description:`Exit plan mode and restore the previous agent configuration.`,contexts:[`tui`,`headless`],parameters:[]}]},initialState:vt(),async handleAction(e,t,n){if(!b)throw Error(`Plan runtime is disposed.`);if(e===fe){let e=t.flavor;if(e!==`normal`&&e!==`debug`&&e!==`fable`)throw Error(`A valid plan flavor is required.`);return await $(()=>Ot(n.context,e,A,!1)),{message:`Plan mode is active with the ${e} flavor.`}}if(e===pe)return{message:await $(()=>kt(n.context,A))?`Plan mode deactivated.`:`Plan mode remains active.`};throw Error(`Unknown plan mode action: ${e}`)},onError:e=>void u.recordError(i.PLAN_EVENT.configLoadFailed,e)});return C=t,()=>{t.dispose(),C===t&&(C=void 0)}}),e.inject([g.DOOM_UI_HUB_SERVICE],e=>{let n=(0,g.requireDoomUiHub)(e),i=n.registerLeader({source:w,bindings:nt(N)}),a=n.registerConfig({source:w,listSections:()=>{let e=Ne();try{return r.planConfigSections(t(),Ae,e)}catch(t){return r.planConfigSections(void 0,t instanceof Error?t.message:String(t),e)}},handlers:{[m.CONFIG_ACTION.set]:async({fieldId:e,value:t})=>{let n=r.planSettingByFieldId(e);n&&t&&(Ae=void 0,await(0,f.setDoomConfigValue)((0,l.globalDoomConfigPath)(),[...n.keyPath],t))},[m.CONFIG_ACTION.clear]:async({fieldId:e})=>{let t=r.planSettingByFieldId(e);t&&(Ae=void 0,await(0,f.unsetDoomConfigValue)((0,l.globalDoomConfigPath)(),[...t.keyPath]))}},onError:e=>{b&&(Ae=e instanceof Error?e.message:String(e))}});return O=i,k=a,()=>{a.dispose(),i.dispose(),k===a&&(k=void 0),O===i&&(O=void 0)}})),at){let t=e=>{if(X(),e.aborted)throw e.reason instanceof Error?e.reason:Error(`Plan voice transition was cancelled.`)},n={descriptor:{source:Me,id:`plan-activate`,name:`activate_plan`,label:`Activate Plan`,description:`Activate or switch the serialized read-only planning mode flavor.`,order:10,inputSchema:Pe,resultSchema:Ie},execute(e,n){let r=We(M(e)?e.flavor:void 0);if(!r)throw Error(`A valid plan flavor is required.`);return t(n.signal),$(async()=>{t(n.signal);let e=N,i=P;return await Ot(n.context,r,ke,!1),{active:N,flavor:P??r,changed:e!==N||i!==P}})}},r={descriptor:{source:Me,id:`plan-exit`,name:`exit_plan`,label:`Exit Plan`,description:`Exit read-only planning mode and restore the previous agent configuration.`,order:20,inputSchema:Fe,resultSchema:Ie},execute(e,n){return t(n.signal),$(async()=>{t(n.signal);let e=N,r=await kt(n.context,ke);return{active:N,flavor:P??null,changed:e!==N||r}})}};e.inject([_.DOOM_VOICE_TOOLS_SERVICE],e=>{let t=(0,_.requireDoomVoiceToolsService)(e),i=t.register(n),a=t.register(r);return()=>{a.dispose(),i.dispose()}})}at&&e.inject([g.DOOM_UI_HUB_SERVICE],e=>{let t=(0,g.requireDoomUiHub)(e).registerLeaderActions({source:w,handlers:{"plan.normal":()=>{let e=b?J:void 0;return e?$(()=>Ot(e,`normal`,A,!1)):void 0},"plan.exit":()=>{let e=b?J:void 0;return e?$(async()=>{await kt(e,A)}):void 0},"plan.debug":()=>{let e=b?J:void 0;return e?$(()=>Ot(e,`debug`,A,!1)):void 0},"plan.fable":()=>{let e=b?J:void 0;return e?$(()=>Ot(e,`fable`,A,!1)):void 0}},onError:(e,t)=>{b&&(J?.hasUI&&J.ui.notify(`Leader action ${t} failed. Plan mode remains read-only.`,T),u.recordError(i.PLAN_EVENT.modeEnabled,e,{[E]:A,"plan.action":t}))}});return De=t,()=>{t(),De===t&&(De=void 0)}})}],onStop:gt,onDispose:gt,tools:[{name:ue,label:`Record Debug Evidence`,description:`Record optional bounded debug evidence while planning a repository fix.`,promptSnippet:`Record the issue and any relevant reproduction, logs, traces, process output, or browser evidence`,parameters:{type:`object`,properties:{issue:{type:`string`},expectedBehavior:{type:`string`},reproductionAttempt:{type:`string`},actualBehavior:{type:`string`},logs:{type:`array`,items:{type:`string`}},correlatedTraceEvidence:{type:`array`,items:{type:`string`}},processOutput:{type:`array`,items:{type:`string`}},browserConsoleEvidence:{type:`array`,items:{type:`string`}},correlationIds:{type:`array`,items:{type:`string`}},timestamps:{type:`array`,items:{type:`string`}},verifiedFacts:{type:`array`,items:{type:`string`}},hypotheses:{type:`array`,items:{type:`string`}},unavailableEvidence:{type:`array`,items:{type:`string`}}},required:[`issue`],additionalProperties:!1},async execute(e,t){if(X(),!N||P!==`debug`)return{content:[{type:`text`,text:`Debug planning is not active.`}],details:{recorded:!1}};try{G=qe(t)}catch(e){throw u.recordWarning(i.PLAN_EVENT.modeEnabled,e,{[D]:`debug`,"plan.debug.reason":`invalid_evidence`}),e}return J&&Z(J),{content:[{type:`text`,text:`Debug evidence recorded as optional planning context.`}],details:{recorded:!0}}}},{name:de,label:`Run Fable Plan`,description:`Send a bounded, sanitized planning packet to the local Fable broker for one repository-aware draft.`,promptSnippet:`Run the local Fable draft with the bounded planning packet`,parameters:{type:`object`,properties:{goal:{type:`array`,items:{type:`string`}},constraints:{type:`array`,items:{type:`string`}},decisions:{type:`array`,items:{type:`string`}},verifiedFindings:{type:`array`,items:{type:`object`}},inferredFindings:{type:`array`,items:{type:`string`}},unresolvedQuestions:{type:`array`,items:{type:`string`}},currentPlan:{type:`string`}},additionalProperties:!1},async execute(e,t,n){let r=x;if(X(r),!N||P!==`fable`)return{content:[{type:`text`,text:`Fable planning is not active.`}],details:{started:!1}};if(!pt())return{content:[{type:`text`,text:`The local Fable broker is unavailable. Fable planning remains disabled.`}],details:{started:!1,errorCode:`broker_unavailable`}};let i=rt;if(!i)throw Error(`Fable planning flow is unavailable.`);let a=i.run(t,n);Y=a;try{let e=await a;return X(r),{content:[{type:`text`,text:e.draft?`Fable draft:\n${e.draft}`:`Fable planning ${e.status}: ${e.errorCode??`no output`}.`}],details:{...e,started:!0}}}finally{b&&r===x&&Y===a&&(Y=void 0)}}},{name:se,label:`Write Plan`,description:`After presenting the complete implementation plan as Markdown in chat, save that visible plan to a unique Markdown file in the configured plans directory.`,promptSnippet:`Save the implementation plan already presented in chat to the configured plans directory`,parameters:{type:`object`,properties:{},additionalProperties:!1},async execute(t,n,r,a,o){let s=x;if(X(s),!N)return{content:[{type:`text`,text:`Plan mode is disabled. Use normal file tools instead.`}],details:{written:!1}};let c=Ve(o.sessionManager.getBranch(),t);if(!c){let e=Error(`Present the complete implementation plan as visible Markdown in chat before calling write_plan.`);throw u.recordError(i.PLAN_EVENT.writePlanFailed,e,{"plan.reason":`missing_visible_plan`}),e}let f=Date.now(),p=(0,l.getHarnessState)().root??o.cwd,m=(0,l.resolvePlanningPlansDirectory)(I?.plansDirectory,p,te.default.homedir()),re=ze(o.sessionManager.getSessionId());B??=Be();let h=z?.planId===B&&ne.default.dirname(z.path)===m?z.path:void 0,g=Le(c),_=B,v=h??ne.default.join(m,`${g}--${_}.md`),ie=new AbortController,oe=Error(`Writing ${v} timed out after ${xe}ms.`),se=setTimeout(()=>ie.abort(oe),xe),y=AbortSignal.any([ie.signal,dt.signal,...r?[r]:[]]),S=`checking`,ce=()=>{a?.({content:[{type:`text`,text:`${S===`checking`?`Checking`:`Writing`} ${v}...`}],details:{phase:S,path:v,startedAt:f}})},le=(e,t)=>{u.recordWarning(i.PLAN_EVENT.writePlanUnsafePath,t,{"plan.phase":S,"plan.refusal":e})};try{ce();try{await j(()=>d.default.promises.mkdir(m,{recursive:!0,mode:448}),y)}catch(e){throw y.aborted?He(y):Error(`Could not create the configured plans directory ${m}.`,{cause:e})}let t=await j(()=>d.default.promises.lstat(m),y),n=await j(()=>d.default.promises.realpath(m),y);if(t.isSymbolicLink()||!t.isDirectory())return le(`unsafe_plans_directory`,`Refused to write the plan because the configured path is unsafe.`),{content:[{type:`text`,text:`Refused to write the plan because the configured path is unsafe.`}],details:{written:!1,path:v,phase:S,durationMs:Date.now()-f}};X(s),S=`writing`,ce();let r;try{for(let e=0;e<3;e+=1)try{let e=h?0:d.default.constants.O_EXCL;r=await j(()=>d.default.promises.open(v,d.default.constants.O_CREAT|d.default.constants.O_WRONLY|d.default.constants.O_NONBLOCK|d.default.constants.O_NOFOLLOW|e,384),y);break}catch(t){if(!(!h&&t instanceof Error&&`code`in t&&String(t.code)===`EEXIST`)||e===2)throw t;_=Be(),B=_,v=ne.default.join(m,`${g}--${_}.md`),Q()}if(!r)throw Error(`Could not allocate a unique plan filename in ${m}.`);let e=await j(()=>d.default.promises.realpath(m),y),t=await j(()=>r.stat(),y);if(e!==n||!t.isFile()||t.nlink!==1)return le(`invalid_destination`,he),{content:[{type:`text`,text:he}],details:{written:!1,path:v,phase:S,durationMs:Date.now()-f}};await j(()=>r.truncate(0),y),await j(()=>r.writeFile(c,{encoding:`utf8`,signal:y}),y)}catch(e){if(e instanceof Error&&`code`in e&&[`EISDIR`,`ELOOP`,`ENXIO`].includes(String(e.code)))return le(`invalid_destination`,he),{content:[{type:`text`,text:he}],details:{written:!1,path:v,phase:S,durationMs:Date.now()-f}};throw e}finally{await r?.close()}return X(s),z={content:c,path:v,sessionId:re,planId:_,title:g,writtenAt:new Date().toISOString()},e.appendEntry(ae,z),bt(o,z),V=!0,U=!1,u.recordEvent(i.PLAN_EVENT.planWritten,{"plan.written":!0,"plan.bytes":ee.Buffer.byteLength(c,`utf8`),"plan.duration_ms":Date.now()-f,"plan.revised":!!h}),{content:[{type:`text`,text:`Wrote implementation plan to ${v}.`}],details:{written:!0,path:v,phase:S,durationMs:Date.now()-f}}}catch(e){if(!b||s!==x)throw He(dt.signal);let t={"plan.phase":S,"plan.duration_ms":Date.now()-f};if(ie.signal.aborted&&!r?.aborted){let n=Error(`write_plan timed out during ${S} for ${v}.`,{cause:e});throw u.recordError(i.PLAN_EVENT.writePlanTimedOut,n,{...t,"plan.timeout_ms":xe}),n}throw u.recordError(i.PLAN_EVENT.writePlanFailed,e,{...t,"plan.cancelled":r?.aborted??!1}),e}finally{clearTimeout(se)}}},{name:oe,label:`Complete Plan`,description:`Present the saved implementation plan for exit-or-continue approval. Interactive sessions use the normal selector; autonomous voice narrates a plain-text handoff and waits for the next user message.`,promptSnippet:`Request explicit exit-or-continue approval after the saved plan has been presented`,promptGuidelines:[`After write_plan succeeds, call complete_plan without a decision so it can present the review question.`,`When autonomous voice returns an awaiting-response result, stop. Interpret the next ordinary user message, then call complete_plan again with decision "exit" or "continue".`,`Never pass decision "${Se}" without the user explicitly choosing "${c.EXIT_PLAN_MODE_CHOICE}".`],parameters:{type:`object`,properties:{decision:{type:`string`,enum:[Se,Ce]}},additionalProperties:!1},async execute(e,t,n,r,a){let o=x;if(X(o),!N)return{content:[{type:`text`,text:`Plan mode is already disabled.`}],details:{exited:!1}};if(!V)return{content:[{type:`text`,text:`Present the complete plan in chat and save it with write_plan before requesting approval.`}],details:{exited:!1}};if(!a.hasUI)return u.recordEvent(i.PLAN_EVENT.planReviewCompleted,{"plan.outcome":`no_ui`}),{content:[{type:`text`,text:`The plan is complete, but this run cannot ask the user interactively. Remain in plan mode until the user uses SPC p e.`}],details:{exited:!1}};let s=M(t)?t.decision:void 0,l=s===Se||s===Ce;if(U){if(!l)throw Error(`The narrated plan review is waiting for an explicit exit or continue decision.`);return U=!1,At(s,a)}if(s!==void 0)throw Error(`Call complete_plan without a decision before submitting a narrated plan-review answer.`);if(await mt())return X(o),U=!0,ht();let ee=await a.ui.select(c.PLAN_REVIEW_TITLE,[...c.PLAN_REVIEW_OPTIONS]);return X(o),At(ee===`Exit plan mode and start implementation`?Se:Ce,a)}}],events:{tool_call:async(e,t)=>{if(!b||!N)return;let n=P===`debug`&&(e.toolName===ue||st.has(e.toolName))||P===`fable`&&e.toolName===de;if(!Ee.has(e.toolName)&&!n)return{block:!0,reason:`Plan mode blocks the ${e.toolName} tool. Use SPC p e to restore access.`};let r=Xe(I?.subagents,t.model);if(e.toolName===le){r&&Qe(e.input,r);return}if(e.toolName!==ce)return;let i=e.input;if($e(i.action))return{block:!0,reason:`Plan mode blocks subagent management action '${i.action}'. Use SPC p e first.`};Ye(i),r&&Ze(i,r)},before_agent_start:async(e,t)=>{if(!b)return;let n=[];if(N){let e=(0,l.getHarnessState)().root??t.cwd,r=(0,l.resolvePlanningPlansDirectory)(I?.plansDirectory,e,te.default.homedir());n.push(s.buildPlanModeBasePrompt(r),s.buildFlavorPlanningPrompt(P,r,G,K))}if(z){let e=z.content;try{e=await d.default.promises.readFile(z.path,`utf8`)}catch{e=z.content}n.push(`[CURRENT PLAN]\nSource: ${z.path}\n\n${e}`)}if(n.length!==0)return{systemPrompt:`${e.systemPrompt}\n\n${n.join(`
5
-
6
- `)}`}},context:async e=>{if(b)return{messages:e.messages.filter(e=>e.customType!==`agent-harness-plan-mode-context`)}},session_start:async(e,t)=>{if(!b)return;je=t;let n=++x,r=t.sessionManager.getEntries(),i=r.flatMap(e=>e.type===`custom`&&e.customType===ie?[Ge(e.data)]:[]).filter(e=>e!==void 0).at(-1),a=ze(t.sessionManager.getSessionId()),o=r.flatMap(e=>e.type===`custom`&&e.customType===ae?[Ke(e.data)]:[]).filter(e=>e!==void 0).at(-1),s=F;if(L?.dispose(),L=void 0,z=o?.sessionId===a?o:void 0,z?bt(t,z):xt(t),B=i?.planId??z?.planId,V=!1,U=!1,I=void 0,G=void 0,N=!1,P=void 0,_t(),F=i?.originalSnapshot,q=i?.interruptedFableStage,K=q?`interrupted`:`idle`,J=t,i?.activeFlavor)await Ot(t,i.activeFlavor,`session_restore`,!0),X(n);else{let e=i?.originalSnapshot??s;e&&(await Tt(t,e),X(n)),ut(),X(n),F=void 0,Z(t)}}}}}exports.WRITE_PLAN_TIMEOUT_MS=xe,exports.configurePlanningSubagentInput=Ze,exports.configurePlanningTaskInput=Qe,exports.constrainSubagentInput=Ye,exports.createPlanIdentifier=Be,exports.createPlanModeRuntime=K,exports.isBlockedSubagentManagementAction=$e,exports.loadPlanningModeConfig=tt,exports.parseDebugEvidencePacket=qe,exports.parsePersistedPlanState=Ge,exports.planSessionIdentifier=ze,exports.planTitleSlug=Le,exports.planToolRestriction=Je,exports.planningSubagentModel=Xe,exports.visiblePlanForToolCall=Ve;
7
- //# sourceMappingURL=planMode.cjs.map