@prestyj/cli 5.30.1 → 5.30.3

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 (245) hide show
  1. package/README.md +54 -38
  2. package/dist/app-sidecar/app-settings.d.ts +6 -2
  3. package/dist/app-sidecar/app-settings.js +39 -20
  4. package/dist/app-sidecar/app-settings.js.map +1 -1
  5. package/dist/app-sidecar/auth-routes.js +4 -1
  6. package/dist/app-sidecar/auth-routes.js.map +1 -1
  7. package/dist/app-sidecar/autopilot-runner.js +2 -5
  8. package/dist/app-sidecar/autopilot-runner.js.map +1 -1
  9. package/dist/app-sidecar/git-target.d.ts +17 -0
  10. package/dist/app-sidecar/git-target.js +31 -0
  11. package/dist/app-sidecar/git-target.js.map +1 -0
  12. package/dist/app-sidecar/goal-routes.d.ts +26 -0
  13. package/dist/app-sidecar/goal-routes.js +445 -0
  14. package/dist/app-sidecar/goal-routes.js.map +1 -0
  15. package/dist/app-sidecar/history-routes.d.ts +13 -0
  16. package/dist/app-sidecar/history-routes.js +36 -25
  17. package/dist/app-sidecar/history-routes.js.map +1 -1
  18. package/dist/app-sidecar/mcp-routes.js +1 -2
  19. package/dist/app-sidecar/mcp-routes.js.map +1 -1
  20. package/dist/app-sidecar/repo-polls.d.ts +5 -2
  21. package/dist/app-sidecar/repo-polls.js +20 -0
  22. package/dist/app-sidecar/repo-polls.js.map +1 -1
  23. package/dist/app-sidecar/route-context.d.ts +14 -0
  24. package/dist/app-sidecar/run-control-routes.js +2 -1
  25. package/dist/app-sidecar/run-control-routes.js.map +1 -1
  26. package/dist/app-sidecar/session-routes.js +41 -0
  27. package/dist/app-sidecar/session-routes.js.map +1 -1
  28. package/dist/app-sidecar/session-types.d.ts +2 -0
  29. package/dist/app-sidecar/workspace-routes.js +13 -95
  30. package/dist/app-sidecar/workspace-routes.js.map +1 -1
  31. package/dist/app-sidecar.js +237 -50
  32. package/dist/app-sidecar.js.map +1 -1
  33. package/dist/chat-agents/jiwa.js +2 -0
  34. package/dist/chat-agents/jiwa.js.map +1 -1
  35. package/dist/chat-agents/memory.js +2 -0
  36. package/dist/chat-agents/memory.js.map +1 -1
  37. package/dist/cli/auth.js +13 -9
  38. package/dist/cli/auth.js.map +1 -1
  39. package/dist/cli/shared.js +1 -1
  40. package/dist/cli/shared.js.map +1 -1
  41. package/dist/cli.js +8 -9
  42. package/dist/cli.js.map +1 -1
  43. package/dist/core/acceptance-checks.js +17 -4
  44. package/dist/core/acceptance-checks.js.map +1 -1
  45. package/dist/core/agent-notifications.d.ts +1 -1
  46. package/dist/core/agent-session/verification-tracker.d.ts +5 -1
  47. package/dist/core/agent-session/verification-tracker.js +25 -6
  48. package/dist/core/agent-session/verification-tracker.js.map +1 -1
  49. package/dist/core/agent-session.d.ts +83 -5
  50. package/dist/core/agent-session.js +174 -59
  51. package/dist/core/agent-session.js.map +1 -1
  52. package/dist/core/ask-user.d.ts +11 -0
  53. package/dist/core/ask-user.js +6 -1
  54. package/dist/core/ask-user.js.map +1 -1
  55. package/dist/core/auth-providers.d.ts +2 -80
  56. package/dist/core/auth-providers.js +4 -183
  57. package/dist/core/auth-providers.js.map +1 -1
  58. package/dist/core/autopilot-cycle.d.ts +2 -0
  59. package/dist/core/autopilot-cycle.js +21 -2
  60. package/dist/core/autopilot-cycle.js.map +1 -1
  61. package/dist/core/cache-diagnostics.d.ts +1 -1
  62. package/dist/core/cache-diagnostics.js +2 -1
  63. package/dist/core/cache-diagnostics.js.map +1 -1
  64. package/dist/core/checklist-prompt.d.ts +9 -2
  65. package/dist/core/checklist-prompt.js +26 -7
  66. package/dist/core/checklist-prompt.js.map +1 -1
  67. package/dist/core/checklist-snapshot.js +1 -1
  68. package/dist/core/checklist-snapshot.js.map +1 -1
  69. package/dist/core/checklist-store.d.ts +5 -1
  70. package/dist/core/checklist-store.js +35 -4
  71. package/dist/core/checklist-store.js.map +1 -1
  72. package/dist/core/compaction/tool-result-pruner.d.ts +10 -0
  73. package/dist/core/compaction/tool-result-pruner.js +12 -0
  74. package/dist/core/compaction/tool-result-pruner.js.map +1 -1
  75. package/dist/core/context-files.d.ts +8 -0
  76. package/dist/core/context-files.js +46 -0
  77. package/dist/core/context-files.js.map +1 -0
  78. package/dist/core/env-delta.d.ts +9 -1
  79. package/dist/core/env-delta.js +20 -0
  80. package/dist/core/env-delta.js.map +1 -1
  81. package/dist/core/event-bus.d.ts +1 -1
  82. package/dist/core/event-bus.js +39 -13
  83. package/dist/core/event-bus.js.map +1 -1
  84. package/dist/core/extensions/plugin-bundles.js +4 -2
  85. package/dist/core/extensions/plugin-bundles.js.map +1 -1
  86. package/dist/core/goals-store.d.ts +208 -0
  87. package/dist/core/goals-store.js +521 -0
  88. package/dist/core/goals-store.js.map +1 -0
  89. package/dist/core/hashline.d.ts +10 -8
  90. package/dist/core/hashline.js.map +1 -1
  91. package/dist/core/local-endpoint-store.js +15 -11
  92. package/dist/core/local-endpoint-store.js.map +1 -1
  93. package/dist/core/mcp/client.js +2 -0
  94. package/dist/core/mcp/client.js.map +1 -1
  95. package/dist/core/mcp/loopback.d.ts +2 -2
  96. package/dist/core/mcp/loopback.js +2 -2
  97. package/dist/core/mcp/shared-pool.js +4 -3
  98. package/dist/core/mcp/shared-pool.js.map +1 -1
  99. package/dist/core/persistent-shell.js +2 -1
  100. package/dist/core/persistent-shell.js.map +1 -1
  101. package/dist/core/process-manager.js +9 -1
  102. package/dist/core/process-manager.js.map +1 -1
  103. package/dist/core/project-discovery.d.ts +7 -0
  104. package/dist/core/project-discovery.js +9 -3
  105. package/dist/core/project-discovery.js.map +1 -1
  106. package/dist/core/project-health-scan.d.ts +109 -0
  107. package/dist/core/project-health-scan.js +673 -0
  108. package/dist/core/project-health-scan.js.map +1 -0
  109. package/dist/core/project-health-score.d.ts +41 -0
  110. package/dist/core/project-health-score.js +257 -0
  111. package/dist/core/project-health-score.js.map +1 -0
  112. package/dist/core/prompt-commands.js +44 -6
  113. package/dist/core/prompt-commands.js.map +1 -1
  114. package/dist/core/radio.d.ts +2 -3
  115. package/dist/core/radio.js.map +1 -1
  116. package/dist/core/session-manager.d.ts +7 -1
  117. package/dist/core/session-manager.js +17 -7
  118. package/dist/core/session-manager.js.map +1 -1
  119. package/dist/core/session-storage.d.ts +4 -0
  120. package/dist/core/session-storage.js +9 -3
  121. package/dist/core/session-storage.js.map +1 -1
  122. package/dist/core/steroids.d.ts +8 -0
  123. package/dist/core/steroids.js +19 -3
  124. package/dist/core/steroids.js.map +1 -1
  125. package/dist/core/subagent-manager.d.ts +51 -0
  126. package/dist/core/subagent-manager.js +114 -22
  127. package/dist/core/subagent-manager.js.map +1 -1
  128. package/dist/core/subagent-receipt.js +3 -4
  129. package/dist/core/subagent-receipt.js.map +1 -1
  130. package/dist/core/subagent-store.js +2 -1
  131. package/dist/core/subagent-store.js.map +1 -1
  132. package/dist/core/ui-adoption.js +2 -0
  133. package/dist/core/ui-adoption.js.map +1 -1
  134. package/dist/core/ui-registry.js +5 -4
  135. package/dist/core/ui-registry.js.map +1 -1
  136. package/dist/core/verification-evidence.d.ts +13 -2
  137. package/dist/core/verification-evidence.js +256 -3
  138. package/dist/core/verification-evidence.js.map +1 -1
  139. package/dist/core/verification-gate.js +4 -2
  140. package/dist/core/verification-gate.js.map +1 -1
  141. package/dist/core/verify-commands.js +2 -3
  142. package/dist/core/verify-commands.js.map +1 -1
  143. package/dist/goals-agent/goal-team.d.ts +2 -0
  144. package/dist/goals-agent/goal-team.js +83 -0
  145. package/dist/goals-agent/goal-team.js.map +1 -0
  146. package/dist/goals-agent/goals-agent.d.ts +90 -0
  147. package/dist/goals-agent/goals-agent.js +246 -0
  148. package/dist/goals-agent/goals-agent.js.map +1 -0
  149. package/dist/index.js +1 -1
  150. package/dist/index.js.map +1 -1
  151. package/dist/interactive.js +4 -3
  152. package/dist/interactive.js.map +1 -1
  153. package/dist/modes/agent-home-mode.js +1 -4
  154. package/dist/modes/agent-home-mode.js.map +1 -1
  155. package/dist/modes/subagent-worker-mode.d.ts +14 -0
  156. package/dist/modes/subagent-worker-mode.js +45 -9
  157. package/dist/modes/subagent-worker-mode.js.map +1 -1
  158. package/dist/system-prompt.d.ts +8 -0
  159. package/dist/system-prompt.js +41 -20
  160. package/dist/system-prompt.js.map +1 -1
  161. package/dist/tools/bash-read-evidence.d.ts +13 -0
  162. package/dist/tools/bash-read-evidence.js +48 -1
  163. package/dist/tools/bash-read-evidence.js.map +1 -1
  164. package/dist/tools/bash.d.ts +3 -1
  165. package/dist/tools/bash.js +8 -4
  166. package/dist/tools/bash.js.map +1 -1
  167. package/dist/tools/checklist.d.ts +5 -4
  168. package/dist/tools/checklist.js +17 -4
  169. package/dist/tools/checklist.js.map +1 -1
  170. package/dist/tools/edit.d.ts +1 -0
  171. package/dist/tools/edit.js +84 -1
  172. package/dist/tools/edit.js.map +1 -1
  173. package/dist/tools/grep.js +3 -2
  174. package/dist/tools/grep.js.map +1 -1
  175. package/dist/tools/index.d.ts +7 -1
  176. package/dist/tools/index.js +19 -8
  177. package/dist/tools/index.js.map +1 -1
  178. package/dist/tools/read-only-bash.js +3 -0
  179. package/dist/tools/read-only-bash.js.map +1 -1
  180. package/dist/tools/read-tracker.d.ts +6 -12
  181. package/dist/tools/read-tracker.js +21 -36
  182. package/dist/tools/read-tracker.js.map +1 -1
  183. package/dist/tools/read.js +2 -2
  184. package/dist/tools/read.js.map +1 -1
  185. package/dist/tools/subagent-control.js +22 -3
  186. package/dist/tools/subagent-control.js.map +1 -1
  187. package/dist/tools/write.js +110 -8
  188. package/dist/tools/write.js.map +1 -1
  189. package/dist/ui/App.js +20 -26
  190. package/dist/ui/App.js.map +1 -1
  191. package/dist/ui/components/ActivityIndicator.d.ts +5 -5
  192. package/dist/ui/components/InputArea.d.ts +2 -2
  193. package/dist/ui/components/InputArea.js +3 -5
  194. package/dist/ui/components/InputArea.js.map +1 -1
  195. package/dist/ui/components/ToolExecution.d.ts +2 -2
  196. package/dist/ui/components/ToolExecution.js +4 -3
  197. package/dist/ui/components/ToolExecution.js.map +1 -1
  198. package/dist/ui/hooks/useAgentLoop.js +7 -6
  199. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  200. package/dist/ui/hooks/useContextCompaction.js +38 -31
  201. package/dist/ui/hooks/useContextCompaction.js.map +1 -1
  202. package/dist/ui/sessions.js +1 -5
  203. package/dist/ui/sessions.js.map +1 -1
  204. package/dist/ui/utils/highlight.d.ts +0 -5
  205. package/dist/ui/utils/highlight.js +3 -49
  206. package/dist/ui/utils/highlight.js.map +1 -1
  207. package/dist/ui/utils/markdown-renderer.d.ts +6 -0
  208. package/dist/ui/utils/markdown-renderer.js +21 -5
  209. package/dist/ui/utils/markdown-renderer.js.map +1 -1
  210. package/dist/utils/format.d.ts +0 -4
  211. package/dist/utils/format.js +0 -7
  212. package/dist/utils/format.js.map +1 -1
  213. package/dist/utils/process.js +3 -3
  214. package/dist/utils/process.js.map +1 -1
  215. package/package.json +5 -8
  216. package/dist/ui/activity-phrases.d.ts +0 -10
  217. package/dist/ui/activity-phrases.js +0 -184
  218. package/dist/ui/activity-phrases.js.map +0 -1
  219. package/dist/ui/components/PlanApproval.d.ts +0 -7
  220. package/dist/ui/components/PlanApproval.js +0 -60
  221. package/dist/ui/components/PlanApproval.js.map +0 -1
  222. package/dist/ui/components/PlanBanner.d.ts +0 -5
  223. package/dist/ui/components/PlanBanner.js +0 -31
  224. package/dist/ui/components/PlanBanner.js.map +0 -1
  225. package/dist/ui/components/PlanProgress.d.ts +0 -6
  226. package/dist/ui/components/PlanProgress.js +0 -30
  227. package/dist/ui/components/PlanProgress.js.map +0 -1
  228. package/dist/ui/hooks/useMinDisplayTime.d.ts +0 -10
  229. package/dist/ui/hooks/useMinDisplayTime.js +0 -34
  230. package/dist/ui/hooks/useMinDisplayTime.js.map +0 -1
  231. package/dist/ui/hooks/useSessionManager.d.ts +0 -12
  232. package/dist/ui/hooks/useSessionManager.js +0 -43
  233. package/dist/ui/hooks/useSessionManager.js.map +0 -1
  234. package/dist/ui/hooks/useSlashCommands.d.ts +0 -6
  235. package/dist/ui/hooks/useSlashCommands.js +0 -11
  236. package/dist/ui/hooks/useSlashCommands.js.map +0 -1
  237. package/dist/ui/terminal-history-spacing.d.ts +0 -1
  238. package/dist/ui/terminal-history-spacing.js +0 -2
  239. package/dist/ui/terminal-history-spacing.js.map +0 -1
  240. package/dist/ui/utils/markdown-cache.d.ts +0 -10
  241. package/dist/ui/utils/markdown-cache.js +0 -58
  242. package/dist/ui/utils/markdown-cache.js.map +0 -1
  243. package/dist/ui/utils/token-to-ansi.d.ts +0 -8
  244. package/dist/ui/utils/token-to-ansi.js +0 -323
  245. package/dist/ui/utils/token-to-ansi.js.map +0 -1
@@ -6,10 +6,12 @@ import { SlashCommandRegistry } from "./slash-commands.js";
6
6
  import { type BranchInfo, type NolanTurnPayload, type AutopilotMarkerPayload, type AppMarkerPayload, type RunOutcome, type TurnMetricPayload } from "./session-manager.js";
7
7
  import { type Skill } from "./skills.js";
8
8
  import type { BackgroundProcess } from "./process-manager.js";
9
+ import { type ChildModelPick, type SubAgentSnapshot } from "./subagent-manager.js";
9
10
  import type { MCPElicitHandler } from "./mcp/index.js";
10
11
  import { type ContextLimits } from "./context-limits.js";
11
12
  import { type ImportForeignTranscriptResult } from "./foreign-session-import.js";
12
13
  import { type CacheExpiryStatus } from "./cache-expiry.js";
14
+ import { type AgentDefinition } from "./agents.js";
13
15
  import { type EnhanceResult } from "../utils/prompt-enhancer.js";
14
16
  import type { VerificationEvidence } from "./verification-evidence.js";
15
17
  /** A chat attachment (image / video / other file) prepared for the model. The
@@ -127,6 +129,45 @@ export interface AgentSessionOptions {
127
129
  * agent to skip planning and implement directly. Any other return accepts.
128
130
  */
129
131
  onEnterPlan?: (reason?: string) => boolean | void | Promise<boolean | void>;
132
+ /**
133
+ * Built-in tools to keep in the core set instead of the deferred
134
+ * tool_search catalog: for a specialist whose job is a deferred tool (GG
135
+ * Goals orchestrates with spawn_agent and friends). Fixed for the session.
136
+ */
137
+ coreTools?: readonly string[];
138
+ /**
139
+ * Default provider/model for the workers this session spawns, when their
140
+ * agent names no model of its own. Read at spawn time.
141
+ */
142
+ getChildModel?: () => ChildModelPick | undefined;
143
+ /**
144
+ * Wrap each built-in tool once, at registration. Must keep the tool's name,
145
+ * description and parameters (only `execute` may change), so the tool block
146
+ * stays prompt-cache stable. Used by EZ Goals to gate its own editing.
147
+ */
148
+ wrapTool?: (tool: AgentTool) => AgentTool;
149
+ /**
150
+ * Built-in tools this session never has, removed at registration (before
151
+ * tiering) and left out of the system prompt's Tools section. Unlike
152
+ * `allowedTools`, the rest of the tool set and its tiering are untouched.
153
+ * Fixed for the session, so the cached tool block never changes. Used by
154
+ * EZ Goals, which never edits or runs anything itself.
155
+ */
156
+ withoutTools?: readonly string[];
157
+ /**
158
+ * A fixed team of sub-agents, replacing agent discovery (project, global
159
+ * and bundled): the only agents spawn_agent and subagent can start. Used by
160
+ * EZ Goals, whose workers follow their own rules (goals-agent/goal-team.ts).
161
+ */
162
+ subAgents?: readonly AgentDefinition[];
163
+ /**
164
+ * Start every spawned child with this session's extra workspace roots too
165
+ * (less the folder it works in), so it can read and change all of them.
166
+ * EZ Goals' workers work across all of a goal's attached folders.
167
+ */
168
+ shareRootsWithSubAgents?: boolean;
169
+ /** The sub-agent a spawned child is when the spawn names none (one of `subAgents`). */
170
+ defaultSubAgent?: string;
130
171
  onExitPlan?: (planPath: string) => Promise<ToolExecuteResult>;
131
172
  /**
132
173
  * If provided, the session's tool set is filtered to ONLY these tool names
@@ -331,6 +372,9 @@ export declare class AgentSession {
331
372
  * for no benefit.
332
373
  */
333
374
  private activeLanguages;
375
+ /** Languages the model has already been given guidance for, via the system
376
+ * prompt or an appended language update. */
377
+ private announcedLanguages;
334
378
  /** Shared with the tool layer so plan-mode restrictions read live state. */
335
379
  private planModeRef;
336
380
  /** Path of the approved plan currently being implemented, or undefined. When
@@ -684,6 +728,15 @@ export declare class AgentSession {
684
728
  /** Clear the queue, returning the combined text (to restore to the composer).
685
729
  * Queued attachments are dropped on cancel — the composer only restores text. */
686
730
  drainQueue(): string;
731
+ /**
732
+ * Tell the agent a new fact at its next turn, as a short hidden message
733
+ * appended after the cached prompt (the same channel as a finished worker),
734
+ * so a mid-session change reaches it without re-rendering the prompt. A
735
+ * later note with the same id replaces one not yet delivered.
736
+ */
737
+ notify(id: string, text: string): void;
738
+ /** This session's sub-agents (workers), newest-state, including ones restored after a restart. */
739
+ listSubAgents(): SubAgentSnapshot[];
687
740
  /** Snapshot of background processes (bash run_in_background), newest-state. */
688
741
  listBackgroundProcesses(): BackgroundProcess[];
689
742
  /** Stop a background process by id. Returns a human-readable status string. */
@@ -736,6 +789,13 @@ export declare class AgentSession {
736
789
  ok: false;
737
790
  error: string;
738
791
  }>;
792
+ /**
793
+ * Name a root change in the prompt only while there is no conversation yet.
794
+ * Mid-conversation the env-delta note tells the model instead: the roots sit
795
+ * in the Environment section, and rewriting it would invalidate every cached
796
+ * message after it (a goal attaching a folder re-sent ~75k tokens).
797
+ */
798
+ private refreshPromptForRoots;
739
799
  /**
740
800
  * Names to advertise as available-on-demand. A tool the model already
741
801
  * promoted lives in `this.tools` and carries its own schema, so it drops out
@@ -758,16 +818,34 @@ export declare class AgentSession {
758
818
  * context, return contract and Environment), or the standard prompt.
759
819
  */
760
820
  private buildBasePrompt;
761
- /**
762
- * Add newly detected project languages. Returns true when the set grew and
763
- * the standard prompt therefore needs a rebuild. Custom and sub-agent
764
- * prompts never render packs, so detection is skipped for them.
765
- */
766
821
  /**
767
822
  * Bring the system prompt to the exact state the next provider request will
768
823
  * send. Shared by real runs and {@link prewarm} so both see one prefix.
824
+ *
825
+ * Languages are re-detected at each task boundary. Before the first reply
826
+ * nothing is cached worth keeping, so a new language simply rebuilds the
827
+ * prompt. After that the prompt is a cached prefix and is left alone: the
828
+ * new language is announced by {@link announceNewLanguages} instead.
769
829
  */
770
830
  private prepareSystemPromptForRequest;
831
+ private hasReply;
832
+ /**
833
+ * Give the model style packs for languages that appeared after its prompt
834
+ * was cached (e.g. the previous task scaffolded a TypeScript project), as a
835
+ * hidden message appended to the history rather than a prompt re-render that
836
+ * would throw away the whole cache. Runs just before a new user message is
837
+ * added, so the user's request stays the last thing the model reads and the
838
+ * update is saved in order with the rest of the conversation.
839
+ */
840
+ private announceNewLanguages;
841
+ /**
842
+ * Whether this session renders language packs: the standard prompt, and a
843
+ * code-writing child (a sub-agent prompt with project context). A verbatim
844
+ * custom prompt never does, nor do user-facing specialist agents (Motion,
845
+ * goals) or recon children without project context.
846
+ */
847
+ private languagesApply;
848
+ /** Add newly detected project languages. Returns true when the set grew. */
771
849
  private refreshActiveLanguages;
772
850
  /** Rebuild messages[0] from current plan-mode + approved-plan state. */
773
851
  private rebuildSystemPromptInPlace;
@@ -27,7 +27,7 @@ import { partitionToolsByTier } from "../tools/tool-tiers.js";
27
27
  import { formatImpactForVerification } from "./test-impact.js";
28
28
  import { buildProcessCompletionFollowUp } from "./process-gate.js";
29
29
  import { PLAN_SUBMISSION_NUDGE, shouldNudgePlanSubmission } from "./plan-submission-gate.js";
30
- import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
30
+ import { buildSubAgentCompletionFollowUp, } from "./subagent-manager.js";
31
31
  import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
32
32
  import { resolveResponsesLite, resolveStrictTools, } from "./codex-request-shape.js";
33
33
  import { subAgentDescription } from "../tools/subagent.js";
@@ -46,14 +46,14 @@ import { decidePlanStepCompaction, DEFAULT_CACHE_WRITE_READ_RATIO, PLAN_STEP_KEE
46
46
  import { extractPlanSteps, findCompletedMarkers } from "../utils/plan-steps.js";
47
47
  import { readFileSync } from "node:fs";
48
48
  import { clampThinkingForPlanMode } from "./thinking-level.js";
49
- import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
49
+ import { pruneStaleToolResults, shouldPruneToolResults } from "./compaction/tool-result-pruner.js";
50
50
  import { discoverAgents } from "./agents.js";
51
51
  import { enhancePrompt } from "../utils/prompt-enhancer.js";
52
52
  import { detectLanguages, detectProjectStack } from "./language-detector.js";
53
53
  import { buildLoopBreakMessage } from "./loop-breaker.js";
54
54
  import { buildRegroundingMessage, requestTextForRegrounding } from "./regrounding.js";
55
55
  import { buildSemanticLoopJudgePrompt, buildSemanticLoopMessage, parseSemanticLoopVerdict, SEMANTIC_LOOP_JUDGE_TIMEOUT_MS, withJudgeTimeout, } from "./semantic-loop-check.js";
56
- import { buildEnvDeltaMessage } from "./env-delta.js";
56
+ import { buildEnvDeltaMessage, buildLanguageUpdateMessage } from "./env-delta.js";
57
57
  import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
58
58
  import { AgentNotificationQueue } from "./agent-notifications.js";
59
59
  import { VerificationGate, VERIFICATION_STATE_KIND } from "./verification-gate.js";
@@ -220,6 +220,9 @@ export class AgentSession {
220
220
  * for no benefit.
221
221
  */
222
222
  activeLanguages = new Set();
223
+ /** Languages the model has already been given guidance for, via the system
224
+ * prompt or an appended language update. */
225
+ announcedLanguages = new Set();
223
226
  /** Shared with the tool layer so plan-mode restrictions read live state. */
224
227
  planModeRef = { current: false };
225
228
  /** Path of the approved plan currently being implemented, or undefined. When
@@ -309,7 +312,10 @@ export class AgentSession {
309
312
  // Session manager. Agent-specific roots keep chat and coder histories isolated.
310
313
  this.sessionManager = new SessionManager(this.opts.sessionRootDir ?? paths.sessionsDir);
311
314
  const projectCustomization = this.opts.projectCustomization !== false;
312
- if (projectCustomization) {
315
+ // A sub-agent worker never creates them: the session that owns the
316
+ // project does. EZ Goals' workers work in the user's attached folders,
317
+ // which mustn't gain empty .ezcoder folders as a side effect.
318
+ if (projectCustomization && !this.opts.subagentWorker) {
313
319
  // Ensure project-local .ezcoder directories exist.
314
320
  const localEzDir = path.join(this.cwd, ".ezcoder");
315
321
  await fs.mkdir(path.join(localEzDir, "skills"), { recursive: true });
@@ -330,16 +336,20 @@ export class AgentSession {
330
336
  }
331
337
  // Discover agents and create tools (with sub-agent support). Chat sessions
332
338
  // can retain bundled workers without loading project/global customization.
333
- const agents = projectCustomization
334
- ? await discoverAgents({
335
- globalAgentsDir: paths.agentsDir,
336
- projectDir: this.cwd,
337
- })
338
- : this.opts.globalSubagents
339
- ? await discoverAgents({ globalAgentsDir: paths.agentsDir })
340
- : [];
339
+ const agents = this.opts.subAgents
340
+ ? [...this.opts.subAgents]
341
+ : projectCustomization
342
+ ? await discoverAgents({
343
+ globalAgentsDir: paths.agentsDir,
344
+ projectDir: this.cwd,
345
+ })
346
+ : this.opts.globalSubagents
347
+ ? await discoverAgents({ globalAgentsDir: paths.agentsDir })
348
+ : [];
341
349
  const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker, recordBashReads, lspManager, testImpact, debugManager, subAgentManager, } = await createTools(this.cwd, {
342
350
  agents,
351
+ shareWorkspaceRoots: this.opts.shareRootsWithSubAgents,
352
+ defaultSubAgent: this.opts.defaultSubAgent,
343
353
  skills: this.skills,
344
354
  contextLimits: this.contextLimits,
345
355
  provider: this.provider,
@@ -381,6 +391,7 @@ export class AgentSession {
381
391
  getBaseUrl: () => this.baseUrl,
382
392
  getCacheKey: () => this.getPromptCacheKey(),
383
393
  getMaxPerModel: () => this.settingsManager.get("subagentMaxPerModel"),
394
+ getChildModel: this.opts.getChildModel,
384
395
  // A persistent child is already the single allowed fan-out level. Blocking
385
396
  // `subagent` here created a timeout sandwich: the nested call could consume
386
397
  // the child's entire 10-minute turn budget, discarding all nested results.
@@ -408,6 +419,16 @@ export class AgentSession {
408
419
  // a hallucinated call can't mutate the repo — and buildSystemPrompt below is
409
420
  // fed the same filtered names so the Tools section matches exactly.
410
421
  this.tools = this.opts.allowedTools ? tools.filter((t) => this.isToolAllowed(t.name)) : tools;
422
+ const without = new Set(this.opts.withoutTools ?? []);
423
+ if (without.size > 0)
424
+ this.tools = this.tools.filter((t) => !without.has(t.name));
425
+ // A host may wrap built-ins (EZ Goals holds back its own editing once a
426
+ // goal is underway). Same names and schemas, so the cached tool block and
427
+ // the system prompt's Tools section are unchanged.
428
+ if (this.opts.wrapTool) {
429
+ const wrap = this.opts.wrapTool;
430
+ this.tools = this.tools.map((t) => wrap(t));
431
+ }
411
432
  // Both delegation tools carry the agent roster; when both survive the
412
433
  // allow-list, `subagent` points at `spawn_agent`'s copy instead.
413
434
  if (this.tools.some((t) => t.name === "spawn_agent")) {
@@ -422,7 +443,14 @@ export class AgentSession {
422
443
  // catalog must hold the wrapped tool so a promoted spawn still loads
423
444
  // wait_agent.
424
445
  this.promoteFollowUpTools();
425
- const { core, deferred } = partitionToolsByTier(this.tools);
446
+ const partition = partitionToolsByTier(this.tools);
447
+ // A session can keep chosen built-ins in its core set (EZ Goals
448
+ // orchestrates, so its agent tools are always at hand). Fixed for the
449
+ // session, so the cached tool block stays byte-stable.
450
+ const keep = new Set(this.opts.coreTools ?? []);
451
+ const deferred = partition.deferred.filter((t) => !keep.has(t.name));
452
+ // Everything not deferred, in the original relative order.
453
+ const core = keep.size > 0 ? this.tools.filter((t) => !deferred.includes(t)) : partition.core;
426
454
  if (deferred.length > 0) {
427
455
  // Append-only: `core` preserves the original relative order and
428
456
  // tool_search is pushed after it, so the serialized tool block that
@@ -454,6 +482,9 @@ export class AgentSession {
454
482
  else {
455
483
  await this.connectMcpServers();
456
484
  }
485
+ // Detect languages before the first render so the opening prompt already
486
+ // carries their packs and only a genuinely new language needs an update.
487
+ this.refreshActiveLanguages();
457
488
  const basePrompt = await this.buildBasePrompt(false, undefined);
458
489
  this.baseSystemPrompt = basePrompt;
459
490
  this.messages = [{ role: "system", content: this.withSystemPromptTail(basePrompt) }];
@@ -786,6 +817,7 @@ export class AgentSession {
786
817
  if (slash?.kind === "template") {
787
818
  // Prompt templates remain human-originated: the user invoked the command.
788
819
  const userMessage = { role: "user", content: slash.fullPrompt, provenance };
820
+ await this.announceNewLanguages();
789
821
  this.messages.push(userMessage);
790
822
  await this.persistMessage(userMessage);
791
823
  this.lastPersistedIndex = this.messages.length;
@@ -802,6 +834,7 @@ export class AgentSession {
802
834
  }
803
835
  // Push user message
804
836
  const userMessage = { role: "user", content, provenance };
837
+ await this.announceNewLanguages();
805
838
  this.messages.push(userMessage);
806
839
  await this.persistMessage(userMessage);
807
840
  this.lastPersistedIndex = this.messages.length;
@@ -826,6 +859,7 @@ export class AgentSession {
826
859
  content: parts,
827
860
  provenance: { source: "human", kind: "prompt", visibility: "transcript" },
828
861
  };
862
+ await this.announceNewLanguages();
829
863
  this.messages.push(userMessage);
830
864
  await this.persistMessage(userMessage);
831
865
  this.lastPersistedIndex = this.messages.length;
@@ -1713,36 +1747,6 @@ export class AgentSession {
1713
1747
  if (!force) {
1714
1748
  if (!this.settingsManager.get("autoCompact"))
1715
1749
  return messages;
1716
- // Cheap stale-tool-output pruning before the expensive LLM
1717
- // compaction check. In-place mutation preserves anchors; drop the
1718
- // retained usage afterwards since it counted the pruned content.
1719
- const pruneResult = pruneStaleToolResults(messages);
1720
- if (pruneResult.pruned) {
1721
- this.cacheDiagnostics.noteEdit("tool_prune", pruneResult.freedTokens);
1722
- this.providerContext = null;
1723
- log("INFO", "compaction", "Pruned stale tool outputs", {
1724
- prunedResults: String(pruneResult.prunedResults),
1725
- compactedToolCalls: String(pruneResult.compactedToolCalls),
1726
- freedTokens: String(pruneResult.freedTokens),
1727
- });
1728
- }
1729
- if (Date.now() < this.compactionRetryAfter)
1730
- return messages;
1731
- // The turn's own usage also counted the pruned content — after a
1732
- // prune, fall back to estimating the (now smaller) history so the
1733
- // freed tokens actually defer the LLM compaction.
1734
- let usage = pruneResult.pruned ? undefined : transformOpts.usage;
1735
- let pendingMessages = transformOpts.pendingMessages;
1736
- if (!usage && this.providerContext) {
1737
- const anchorIndex = messages.lastIndexOf(this.providerContext.anchor);
1738
- if (anchorIndex >= 0) {
1739
- usage = this.providerContext.usage;
1740
- pendingMessages = messages.slice(anchorIndex + 1);
1741
- }
1742
- else {
1743
- this.providerContext = null;
1744
- }
1745
- }
1746
1750
  const contextWindow = getContextWindow(this.model, {
1747
1751
  provider: this.provider,
1748
1752
  accountId,
@@ -1755,10 +1759,48 @@ export class AgentSession {
1755
1759
  accountId,
1756
1760
  approvedPlanPath: this.approvedPlanPath,
1757
1761
  });
1758
- const activeTokens = calculateActiveContextTokens(messages, {
1759
- usage,
1760
- pendingMessages,
1761
- });
1762
+ // Provider usage when there is some, else an estimate of the history.
1763
+ const measureActiveTokens = () => {
1764
+ let usage = transformOpts.usage;
1765
+ let pendingMessages = transformOpts.pendingMessages;
1766
+ if (!usage && this.providerContext) {
1767
+ const anchorIndex = messages.lastIndexOf(this.providerContext.anchor);
1768
+ if (anchorIndex >= 0) {
1769
+ usage = this.providerContext.usage;
1770
+ pendingMessages = messages.slice(anchorIndex + 1);
1771
+ }
1772
+ else {
1773
+ this.providerContext = null;
1774
+ }
1775
+ }
1776
+ return calculateActiveContextTokens(messages, { usage, pendingMessages });
1777
+ };
1778
+ let activeTokens = measureActiveTokens();
1779
+ // Cheap stale-tool-output pruning before the expensive LLM
1780
+ // compaction check, once the context is big enough for the freed
1781
+ // tokens to be worth a cache rewrite. In-place mutation preserves
1782
+ // anchors; drop the retained usage afterwards since it counted the
1783
+ // pruned content.
1784
+ if (shouldPruneToolResults(activeTokens, policy.targetTokens)) {
1785
+ const pruneResult = pruneStaleToolResults(messages);
1786
+ if (pruneResult.pruned) {
1787
+ this.cacheDiagnostics.noteEdit("tool_prune", pruneResult.freedTokens);
1788
+ this.providerContext = null;
1789
+ log("INFO", "compaction", "Pruned stale tool outputs", {
1790
+ prunedResults: String(pruneResult.prunedResults),
1791
+ compactedToolCalls: String(pruneResult.compactedToolCalls),
1792
+ freedTokens: String(pruneResult.freedTokens),
1793
+ });
1794
+ // The turn's own usage also counted the pruned content — estimate
1795
+ // the (now smaller) history so the freed tokens actually defer
1796
+ // the LLM compaction.
1797
+ activeTokens = calculateActiveContextTokens(messages, {
1798
+ pendingMessages: transformOpts.pendingMessages,
1799
+ });
1800
+ }
1801
+ }
1802
+ if (Date.now() < this.compactionRetryAfter)
1803
+ return messages;
1762
1804
  // An approved plan runs as ONE run, so step boundaries are only
1763
1805
  // visible here, between model steps — not after the run ends.
1764
1806
  const planStep = this.observePlanStepProgress(messages, contextWindow, activeTokens);
@@ -2510,6 +2552,9 @@ export class AgentSession {
2510
2552
  // new session's /history, and get re-persisted into the new file by the
2511
2553
  // next compaction — the cross-session duplicate-marker propagation bug.
2512
2554
  this.markers.clear();
2555
+ // Detect languages before the first render so the opening prompt already
2556
+ // carries their packs and only a genuinely new language needs an update.
2557
+ this.refreshActiveLanguages();
2513
2558
  const basePrompt = await this.buildBasePrompt(false, undefined);
2514
2559
  this.baseSystemPrompt = basePrompt;
2515
2560
  this.messages = [{ role: "system", content: this.withSystemPromptTail(basePrompt) }];
@@ -2713,6 +2758,19 @@ export class AgentSession {
2713
2758
  .map((m) => m.text)
2714
2759
  .join("\n\n");
2715
2760
  }
2761
+ /**
2762
+ * Tell the agent a new fact at its next turn, as a short hidden message
2763
+ * appended after the cached prompt (the same channel as a finished worker),
2764
+ * so a mid-session change reaches it without re-rendering the prompt. A
2765
+ * later note with the same id replaces one not yet delivered.
2766
+ */
2767
+ notify(id, text) {
2768
+ this.notifications.enqueue("goal", id, text, { terminal: true });
2769
+ }
2770
+ /** This session's sub-agents (workers), newest-state, including ones restored after a restart. */
2771
+ listSubAgents() {
2772
+ return this.subAgentManager?.list() ?? [];
2773
+ }
2716
2774
  /** Snapshot of background processes (bash run_in_background), newest-state. */
2717
2775
  listBackgroundProcesses() {
2718
2776
  return this.processManager?.list() ?? [];
@@ -2811,7 +2869,7 @@ export class AgentSession {
2811
2869
  return relative.startsWith("..") || path.isAbsolute(relative);
2812
2870
  });
2813
2871
  this.additionalRoots.push(resolved);
2814
- await this.rebuildSystemPromptInPlace();
2872
+ await this.refreshPromptForRoots();
2815
2873
  return { ok: true, root: resolved };
2816
2874
  }
2817
2875
  /** Remove an exact root previously added with `/add-dir`. */
@@ -2822,9 +2880,20 @@ export class AgentSession {
2822
2880
  return { ok: false, error: `Not an additional workspace root: ${resolved}` };
2823
2881
  }
2824
2882
  this.additionalRoots.splice(index, 1);
2825
- await this.rebuildSystemPromptInPlace();
2883
+ await this.refreshPromptForRoots();
2826
2884
  return { ok: true, root: resolved };
2827
2885
  }
2886
+ /**
2887
+ * Name a root change in the prompt only while there is no conversation yet.
2888
+ * Mid-conversation the env-delta note tells the model instead: the roots sit
2889
+ * in the Environment section, and rewriting it would invalidate every cached
2890
+ * message after it (a goal attaching a folder re-sent ~75k tokens).
2891
+ */
2892
+ async refreshPromptForRoots() {
2893
+ if (this.messages.some((message) => message.role !== "system"))
2894
+ return;
2895
+ await this.rebuildSystemPromptInPlace();
2896
+ }
2828
2897
  /**
2829
2898
  * Names to advertise as available-on-demand. A tool the model already
2830
2899
  * promoted lives in `this.tools` and carries its own schema, so it drops out
@@ -2865,6 +2934,8 @@ export class AgentSession {
2865
2934
  return this.customSystemPrompt;
2866
2935
  const toolNames = this.tools.map((tool) => tool.name);
2867
2936
  const deferredToolNames = this.deferredToolNamesForPrompt(toolNames);
2937
+ // Whatever is rendered now is what the model has been told.
2938
+ this.announcedLanguages = new Set(this.activeLanguages);
2868
2939
  if (this.agentPrompt !== undefined) {
2869
2940
  return buildSubAgentSystemPrompt(this.agentPrompt, {
2870
2941
  cwd: this.cwd,
@@ -2874,29 +2945,73 @@ export class AgentSession {
2874
2945
  role: this.opts.agentRole,
2875
2946
  environment: this.recordRenderedEnvironment(),
2876
2947
  contextLimits: this.contextLimits,
2948
+ ...(this.languagesApply() ? { activeLanguages: this.activeLanguages } : {}),
2877
2949
  });
2878
2950
  }
2879
2951
  return buildSystemPrompt(this.cwd, this.skills, planMode, approvedPlanPath, toolNames, this.activeLanguages, this.provider, this.recordRenderedEnvironment(), deferredToolNames, this.contextLimits);
2880
2952
  }
2881
- /**
2882
- * Add newly detected project languages. Returns true when the set grew and
2883
- * the standard prompt therefore needs a rebuild. Custom and sub-agent
2884
- * prompts never render packs, so detection is skipped for them.
2885
- */
2886
2953
  /**
2887
2954
  * Bring the system prompt to the exact state the next provider request will
2888
2955
  * send. Shared by real runs and {@link prewarm} so both see one prefix.
2956
+ *
2957
+ * Languages are re-detected at each task boundary. Before the first reply
2958
+ * nothing is cached worth keeping, so a new language simply rebuilds the
2959
+ * prompt. After that the prompt is a cached prefix and is left alone: the
2960
+ * new language is announced by {@link announceNewLanguages} instead.
2889
2961
  */
2890
2962
  async prepareSystemPromptForRequest() {
2891
- // Languages are re-detected at each task boundary so a project scaffolded
2892
- // during the previous turn gets its packs; the prompt is rebuilt only when
2893
- // the set grows, keeping the cached prefix stable otherwise.
2894
- if (this.refreshActiveLanguages())
2963
+ if (this.refreshActiveLanguages() && !this.hasReply()) {
2895
2964
  await this.rebuildSystemPromptInPlace();
2965
+ }
2896
2966
  this.refreshSystemPromptTail();
2897
2967
  }
2968
+ hasReply() {
2969
+ return this.messages.some((m) => m.role === "assistant");
2970
+ }
2971
+ /**
2972
+ * Give the model style packs for languages that appeared after its prompt
2973
+ * was cached (e.g. the previous task scaffolded a TypeScript project), as a
2974
+ * hidden message appended to the history rather than a prompt re-render that
2975
+ * would throw away the whole cache. Runs just before a new user message is
2976
+ * added, so the user's request stays the last thing the model reads and the
2977
+ * update is saved in order with the rest of the conversation.
2978
+ */
2979
+ async announceNewLanguages() {
2980
+ if (!this.hasReply())
2981
+ return; // the next request rebuilds the prompt instead
2982
+ this.refreshActiveLanguages();
2983
+ const fresh = new Set([...this.activeLanguages].filter((id) => !this.announcedLanguages.has(id)));
2984
+ if (fresh.size === 0)
2985
+ return;
2986
+ for (const id of fresh)
2987
+ this.announcedLanguages.add(id);
2988
+ const update = buildLanguageUpdateMessage(fresh, this.cwd);
2989
+ if (!update)
2990
+ return;
2991
+ this.messages.push(update);
2992
+ await this.persistMessage(update);
2993
+ this.lastPersistedIndex = this.messages.length;
2994
+ log("INFO", "language", "Appended style packs for new languages", {
2995
+ added: [...fresh].join(","),
2996
+ });
2997
+ }
2998
+ /**
2999
+ * Whether this session renders language packs: the standard prompt, and a
3000
+ * code-writing child (a sub-agent prompt with project context). A verbatim
3001
+ * custom prompt never does, nor do user-facing specialist agents (Motion,
3002
+ * goals) or recon children without project context.
3003
+ */
3004
+ languagesApply() {
3005
+ if (this.customSystemPrompt)
3006
+ return false;
3007
+ if (this.agentPrompt === undefined)
3008
+ return true;
3009
+ return ((this.opts.agentRole ?? "subagent") === "subagent" &&
3010
+ (this.opts.agentContext ?? "project") === "project");
3011
+ }
3012
+ /** Add newly detected project languages. Returns true when the set grew. */
2898
3013
  refreshActiveLanguages() {
2899
- if (this.customSystemPrompt || this.agentPrompt !== undefined)
3014
+ if (!this.languagesApply())
2900
3015
  return false;
2901
3016
  let grew = false;
2902
3017
  try {