@aragon-agent/cli 0.5.0 → 0.5.1

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 (82) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +227 -3
  3. package/dist/agent/agent-mode.d.ts +53 -0
  4. package/dist/agent/agent-mode.js +69 -0
  5. package/dist/agent/controller.d.ts +69 -0
  6. package/dist/agent/controller.js +154 -1
  7. package/dist/agent/plan-prompt.d.ts +18 -0
  8. package/dist/agent/plan-prompt.js +93 -0
  9. package/dist/agent/reducer.d.ts +14 -1
  10. package/dist/agent/reducer.js +9 -0
  11. package/dist/agent/system-prompt.d.ts +15 -0
  12. package/dist/agent/system-prompt.js +8 -0
  13. package/dist/cli.d.ts +1 -1
  14. package/dist/cli.js +183 -20
  15. package/dist/commands/builtins.js +60 -0
  16. package/dist/commands/registry.d.ts +17 -0
  17. package/dist/config/app-paths.d.ts +71 -4
  18. package/dist/config/app-paths.js +139 -8
  19. package/dist/config/cli-commands.d.ts +43 -0
  20. package/dist/config/cli-commands.js +196 -0
  21. package/dist/config/env.d.ts +7 -1
  22. package/dist/config/env.js +45 -1
  23. package/dist/config/load.d.ts +8 -0
  24. package/dist/config/load.js +59 -2
  25. package/dist/config/migrate-home.d.ts +63 -0
  26. package/dist/config/migrate-home.js +165 -0
  27. package/dist/config/migrate-legacy-state.d.ts +10 -0
  28. package/dist/config/migrate-legacy-state.js +15 -6
  29. package/dist/config/schema.d.ts +61 -0
  30. package/dist/config/schema.js +83 -0
  31. package/dist/config/store.d.ts +30 -12
  32. package/dist/config/store.js +47 -35
  33. package/dist/logging/cli-commands.d.ts +25 -0
  34. package/dist/logging/cli-commands.js +221 -0
  35. package/dist/logging/file-sink.d.ts +114 -0
  36. package/dist/logging/file-sink.js +326 -0
  37. package/dist/logging/install.d.ts +163 -0
  38. package/dist/logging/install.js +302 -0
  39. package/dist/logging/levels.d.ts +40 -0
  40. package/dist/logging/levels.js +59 -0
  41. package/dist/logging/logger.d.ts +110 -0
  42. package/dist/logging/logger.js +237 -0
  43. package/dist/logging/redact.d.ts +28 -0
  44. package/dist/logging/redact.js +90 -0
  45. package/dist/logging/secret-registry.d.ts +30 -0
  46. package/dist/logging/secret-registry.js +55 -0
  47. package/dist/skills/paths.d.ts +2 -4
  48. package/dist/skills/paths.js +7 -9
  49. package/dist/tools/human-input.d.ts +179 -0
  50. package/dist/tools/human-input.js +227 -0
  51. package/dist/tools/index.d.ts +35 -3
  52. package/dist/tools/index.js +96 -9
  53. package/dist/tools/plan-tools.d.ts +64 -0
  54. package/dist/tools/plan-tools.js +198 -0
  55. package/dist/ui/App.d.ts +4 -1
  56. package/dist/ui/App.js +182 -11
  57. package/dist/ui/Composer.d.ts +4 -1
  58. package/dist/ui/Composer.js +26 -7
  59. package/dist/ui/ModeChip.d.ts +29 -0
  60. package/dist/ui/ModeChip.js +15 -0
  61. package/dist/ui/PromptInput.d.ts +8 -1
  62. package/dist/ui/PromptInput.js +12 -2
  63. package/dist/ui/StatusBar.d.ts +15 -0
  64. package/dist/ui/StatusBar.js +10 -2
  65. package/dist/ui/entries/ToolPreview.d.ts +2 -0
  66. package/dist/ui/entries/ToolPreview.js +64 -0
  67. package/dist/ui/glyphs.d.ts +9 -0
  68. package/dist/ui/glyphs.js +8 -0
  69. package/dist/ui/layout/wrap-rows.d.ts +24 -0
  70. package/dist/ui/layout/wrap-rows.js +74 -0
  71. package/dist/ui/overlays/HelpOverlay.js +13 -0
  72. package/dist/ui/overlays/PlanReviewOverlay.d.ts +50 -0
  73. package/dist/ui/overlays/PlanReviewOverlay.js +117 -0
  74. package/dist/ui/overlays/QuestionOverlay.d.ts +30 -0
  75. package/dist/ui/overlays/QuestionOverlay.js +193 -0
  76. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  77. package/dist/ui/overlays/SettingsScreen.js +8 -1
  78. package/dist/ui/use-startup-notices.d.ts +15 -0
  79. package/dist/ui/use-startup-notices.js +38 -0
  80. package/package.json +3 -2
  81. package/runtime/insecure-tls-warning.cjs +62 -0
  82. package/runtime/insecure-tls-warning.d.cts +5 -0
@@ -13,6 +13,8 @@ import { isAdapterProvider } from '../config/schema.js';
13
13
  import { makeGetApiKey } from '../config/load.js';
14
14
  import { updatePersistedConfig } from '../config/store.js';
15
15
  import { createBuiltinTools } from '../tools/index.js';
16
+ import { createPlanTools } from '../tools/plan-tools.js';
17
+ import { DENY_ALL_HUMAN_INPUT } from '../tools/human-input.js';
16
18
  import { createNodeSkillHost } from '../skills/node-host.js';
17
19
  import { SkillService } from '../skills/service.js';
18
20
  import { createSkillTools } from '../skills/tools.js';
@@ -33,9 +35,25 @@ export class AgentController {
33
35
  onSkillsChanged;
34
36
  /** Mutable session cwd — tools resolve relative paths against this. */
35
37
  cwd;
38
+ // -----------------------------------------------------------------------
39
+ // Plan mode (§3.1) — THE CONTROLLER OWNS THE MODE.
40
+ //
41
+ // It is the object the tools close over, so it is the only place that can
42
+ // answer "may this tool run?" at execute time. `ViewState.agentMode` is a
43
+ // MIRROR held for rendering, written only from what `setAgentMode` reports it
44
+ // adopted.
45
+ // -----------------------------------------------------------------------
46
+ effectiveMode;
47
+ pendingMode = null;
48
+ askRounds = 0;
49
+ /** Static: whether this session could ever render a question overlay. */
50
+ planInteractive;
36
51
  constructor(config, deps = {}) {
37
52
  this.config = config;
38
53
  this.cwd = config.cwd;
54
+ const humanInput = deps.humanInput ?? DENY_ALL_HUMAN_INPUT;
55
+ this.planInteractive = humanInput.neverPrompts !== true;
56
+ this.effectiveMode = config.startInPlanMode ? 'plan' : 'build';
39
57
  this.providerRegistry = initProviders();
40
58
  this.modelRegistry = new ModelRegistry(this.providerRegistry);
41
59
  this.skillsEnabled = config.skills.enabled;
@@ -62,15 +80,40 @@ export class AgentController {
62
80
  this.skills.discover();
63
81
  this.skills.reportUnknownForcedSkills();
64
82
  }
83
+ const confirm = deps.confirm;
65
84
  this.tools = createBuiltinTools({
66
85
  getCwd: () => this.cwd,
67
86
  confirmTools: config.confirmTools,
68
- confirm: deps.confirm,
87
+ // Routed through the same watchdog pause as the plan tools. This is a
88
+ // pre-existing bug being fixed in passing: a `--confirm` user who took
89
+ // longer than `idleTimeoutMs` over `Proceed? (y/N)` had their run killed
90
+ // by the watchdog, and nothing said why (R-P3).
91
+ ...(confirm ? { confirm: (req) => this.withHumanWait(() => confirm(req)) } : {}),
69
92
  // `--no-skills` yields an empty array, so the tool list is byte-identical
70
93
  // to the pre-Skills seven (invariant I-S1 / AC-10).
71
94
  skillTools: this.skillsEnabled
72
95
  ? createSkillTools(this.skills, deps.version, () => this.toolNames())
73
96
  : [],
97
+ // Registered ONCE per session, whatever the current mode, and never
98
+ // rebuilt on a toggle (§3.4). `Agent.setTools()` unregisters and
99
+ // re-registers entries in the live `ToolRegistry`, and `submit_plan`
100
+ // mutates the mode from INSIDE a tool execution — rebuilding the array at
101
+ // that moment would mutate the registry mid-iteration. Keeping the array
102
+ // immutable removes the hazard entirely; the mode-dependent behaviour
103
+ // lives in the gate, which is a pure function of `getMode()`.
104
+ //
105
+ // Empty in headless mode, so the tool list there is still exactly the 7
106
+ // (or 11 with skills) it is today.
107
+ planTools: createPlanTools({
108
+ gate: humanInput,
109
+ takeAskRound: () => this.takeAskRound(),
110
+ withHumanWait: (fn) => this.withHumanWait(fn),
111
+ onPlanApproved: () => this.setAgentMode('build', { force: true }),
112
+ }),
113
+ // Read at CALL time, never cached: the mode flips mid-run when the user
114
+ // approves a plan, and a cached 'plan' would refuse the work they just
115
+ // authorized.
116
+ agentMode: () => this.effectiveMode,
74
117
  // Omitted entirely when skills are off, which is what leaves the seven
75
118
  // built-ins unwrapped and AC-G17 provable by object identity.
76
119
  ...(this.skillsEnabled
@@ -99,6 +142,14 @@ export class AgentController {
99
142
  // Timeout invariant (R1): idle ≥ tool so long tool runs are not killed.
100
143
  toolTimeout: config.toolTimeoutMs,
101
144
  idleTimeout: config.idleTimeoutMs,
145
+ // A human wait is not a hung tool. 30 minutes by default, and the
146
+ // number only bites BECAUSE the plan tools race `ctx.signal` — the
147
+ // executor's timeout is cooperative, so an override is inert for any
148
+ // tool that does not listen (see API.md, "Blocking on a human").
149
+ toolTimeoutOverrides: {
150
+ ask_user: config.planModeHumanTimeoutMs,
151
+ submit_plan: config.planModeHumanTimeoutMs,
152
+ },
102
153
  },
103
154
  });
104
155
  }
@@ -139,6 +190,9 @@ export class AgentController {
139
190
  cwd: this.cwd,
140
191
  tools: this.tools,
141
192
  skillsBlock: this.skillsEnabled ? this.skills.catalogBlock() + this.skills.alwaysBlock() : '',
193
+ agentMode: this.effectiveMode,
194
+ planInteractive: this.planInteractive,
195
+ planMaxAskRounds: this.config.planModeMaxAskRounds,
142
196
  });
143
197
  }
144
198
  rebuildSystemPrompt() {
@@ -230,6 +284,11 @@ export class AgentController {
230
284
  // engine, which is what makes a per-turn scope implementable at all (FG9).
231
285
  if (this.skillsEnabled)
232
286
  this.skills.beginUserTurn();
287
+ // The ask-round budget is per USER TURN and resets here for the same reason
288
+ // the skill frame does. `steer()` deliberately does NOT reset it: a user
289
+ // steering "actually, use Postgres" into a running plan is answering the
290
+ // questions, not buying four more rounds of them.
291
+ this.askRounds = 0;
233
292
  return this.agent.prompt(text);
234
293
  }
235
294
  abort() {
@@ -251,6 +310,100 @@ export class AgentController {
251
310
  return this.agent.state.isRunning;
252
311
  }
253
312
  // -----------------------------------------------------------------------
313
+ // Plan mode (§3.1 / §3.2)
314
+ // -----------------------------------------------------------------------
315
+ getAgentMode() {
316
+ return this.effectiveMode;
317
+ }
318
+ /**
319
+ * Request a mode change and return what was ACTUALLY ADOPTED.
320
+ *
321
+ * Returning the adopted state rather than `void` is the whole point of this
322
+ * signature. The caller dispatches THIS value into the view, never the value
323
+ * it asked for — dispatching the request is the one way to make the badge lie
324
+ * about what the gate will do, and the API shape forecloses it (R-P7).
325
+ *
326
+ * The two directions are deliberately asymmetric:
327
+ *
328
+ * - `build -> plan` applies IMMEDIATELY. Tightening a permission mid-run is
329
+ * always safe; the user pressed the key *because* the agent is about to do
330
+ * something they want to stop.
331
+ * - `plan -> build` is DEFERRED to `agent_end`. Applying it live would mean a
332
+ * run the user launched under a read-only guarantee could start writing
333
+ * files because of one stray keypress.
334
+ * - `{ force: true }` is the single exception, used only by `submit_plan`'s
335
+ * approval path: the user has just read the plan and authorized exactly
336
+ * that work. That is an informed act; a stray Shift+Tab is not. If this
337
+ * code is ever refactored, that distinction is the thing to preserve.
338
+ */
339
+ setAgentMode(next, opts = {}) {
340
+ if (next === 'plan') {
341
+ this.effectiveMode = 'plan';
342
+ this.pendingMode = null;
343
+ }
344
+ else if (this.effectiveMode === 'build') {
345
+ // Already there. Clear any stale deferral rather than queueing a second.
346
+ this.pendingMode = null;
347
+ }
348
+ else if (opts.force || !this.isRunning()) {
349
+ this.effectiveMode = 'build';
350
+ this.pendingMode = null;
351
+ }
352
+ else {
353
+ this.pendingMode = 'build';
354
+ }
355
+ this.rebuildSystemPrompt();
356
+ return { effective: this.effectiveMode, pending: this.pendingMode };
357
+ }
358
+ /**
359
+ * Adopt a deferred `plan -> build` switch. Call from the App's `agent_end`
360
+ * handling. Returns `null` when nothing was pending, so the caller can skip
361
+ * both the dispatch and the toast.
362
+ */
363
+ applyPendingMode() {
364
+ if (!this.pendingMode)
365
+ return null;
366
+ this.effectiveMode = this.pendingMode;
367
+ this.pendingMode = null;
368
+ this.rebuildSystemPrompt();
369
+ return { effective: this.effectiveMode, pending: null };
370
+ }
371
+ /** Named read surface for `/plan status`, so no command reaches into internals. */
372
+ getPlanStatus() {
373
+ return {
374
+ effective: this.effectiveMode,
375
+ pending: this.pendingMode,
376
+ askRoundsUsed: this.askRounds,
377
+ maxAskRounds: this.config.planModeMaxAskRounds,
378
+ };
379
+ }
380
+ /** Spend one `ask_user` round. Called from the tool, never from the UI. */
381
+ takeAskRound() {
382
+ const max = this.config.planModeMaxAskRounds;
383
+ if (this.askRounds >= max) {
384
+ return { ok: false, used: this.askRounds, remaining: 0, max };
385
+ }
386
+ this.askRounds += 1;
387
+ return { ok: true, used: this.askRounds, remaining: max - this.askRounds, max };
388
+ }
389
+ /**
390
+ * Run `fn` with the idle watchdog paused.
391
+ *
392
+ * `try/finally` rather than two sequential calls: a throw inside the overlay
393
+ * path must not leave the watchdog disarmed for the rest of the run. Also used
394
+ * by the `--confirm` gate, which had exactly this bug — a user who took four
395
+ * minutes over `Proceed? (y/N)` had their run killed by the watchdog.
396
+ */
397
+ async withHumanWait(fn) {
398
+ this.agent.pauseIdleWatchdog();
399
+ try {
400
+ return await fn();
401
+ }
402
+ finally {
403
+ this.agent.resumeIdleWatchdog();
404
+ }
405
+ }
406
+ // -----------------------------------------------------------------------
254
407
  // Mutators
255
408
  // -----------------------------------------------------------------------
256
409
  setModel(provider, model, baseUrl) {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The `<plan_mode>` system-prompt block (plan-mode §8).
3
+ *
4
+ * Spliced into `buildSystemPrompt` ONLY when the mode is `plan`, exactly as
5
+ * `skillsBlock` already is, so a build-mode prompt stays byte-identical to the
6
+ * pre-feature output (invariant I-P1).
7
+ *
8
+ * ASCII ONLY — `agent/` is inside the glyph scanner's scope.
9
+ */
10
+ /** Bump when the wording changes, so a transcript can be dated. */
11
+ export declare const PLAN_MODE_BLOCK_VERSION = "v1-2026-07";
12
+ export interface PlanModeBlockOptions {
13
+ /** False under `-p` / piped stdin: no overlay can be rendered, so no tools. */
14
+ interactive: boolean;
15
+ /** `planModeMaxAskRounds`; substituted into the guidance so the two agree. */
16
+ maxAskRounds: number;
17
+ }
18
+ export declare function buildPlanModeBlock(options: PlanModeBlockOptions): string;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The `<plan_mode>` system-prompt block (plan-mode §8).
3
+ *
4
+ * Spliced into `buildSystemPrompt` ONLY when the mode is `plan`, exactly as
5
+ * `skillsBlock` already is, so a build-mode prompt stays byte-identical to the
6
+ * pre-feature output (invariant I-P1).
7
+ *
8
+ * ASCII ONLY — `agent/` is inside the glyph scanner's scope.
9
+ */
10
+ /** Bump when the wording changes, so a transcript can be dated. */
11
+ export const PLAN_MODE_BLOCK_VERSION = 'v1-2026-07';
12
+ /**
13
+ * The closing paragraph of the interactive block.
14
+ *
15
+ * THIS PARAGRAPH IS LOAD-BEARING, NOT BOILERPLATE (§3.9 / I-P9).
16
+ *
17
+ * `runLoopWithLifecycle` passes `systemPrompt: this.systemPrompt` BY VALUE into
18
+ * `runAgentLoop`, and every LLM call in the loop reads that same captured
19
+ * string. So the block cannot be rewritten mid-run — which matters most in the
20
+ * feature's headline flow: the user approves a plan, `submit_plan` flips the
21
+ * session to Build and returns "implement the plan", and the model is still
22
+ * holding a system prompt that says "you must not change anything on disk".
23
+ * The well-aligned resolution of that standoff is to refuse, which would break
24
+ * the whole point of the approval.
25
+ *
26
+ * Two halves must survive any edit: the read-only rule is SCOPED to "while PLAN
27
+ * MODE is active", and the approval result is NAMED as the authority that
28
+ * supersedes it. `plan-mode.test.ts` asserts both as a static regex, because
29
+ * the failure presents as "the model is being unhelpful today" rather than as a
30
+ * bug in this feature.
31
+ */
32
+ const SUPERSEDE_PARAGRAPH = [
33
+ '- This block is written once, when the run starts, and is NOT re-issued if the',
34
+ ' mode changes during the run. A submit_plan result reporting that the plan was',
35
+ ' APPROVED is therefore authoritative and supersedes the read-only rule above',
36
+ ' for the remainder of this run: you are in Build mode, the tools are available,',
37
+ ' and you should implement the plan in order without asking again.',
38
+ ].join('\n');
39
+ /** The headless replacement: there is no overlay, so there is nobody to ask. */
40
+ const HEADLESS_PARAGRAPH = [
41
+ '- There is no interactive user. Do not ask questions and do not call submit_plan;',
42
+ ' write the finished plan as your final message in markdown.',
43
+ ].join('\n');
44
+ export function buildPlanModeBlock(options) {
45
+ const { interactive, maxAskRounds } = options;
46
+ const askStep = interactive
47
+ ? [
48
+ '2. If a decision would change the work and you cannot settle it by reading, call',
49
+ ' ask_user with 3-5 multiple-choice questions in ONE call. Mark exactly one',
50
+ ` option recommended:true and put the reason in its description. You may ask up`,
51
+ ` to ${maxAskRounds} rounds; each round costs the user time, so make them count.`,
52
+ ]
53
+ : [
54
+ '2. If a decision would change the work and you cannot settle it by reading, state',
55
+ ' the assumption you are making and why, then continue. There is nobody to ask.',
56
+ ];
57
+ const submitStep = interactive
58
+ ? [
59
+ '3. Call submit_plan exactly once, with ordered steps a reviewer could check off.',
60
+ ' Name the real files you intend to touch. State the risks you actually found.',
61
+ ]
62
+ : [
63
+ '3. Finish with the full plan as your final message: ordered steps a reviewer could',
64
+ ' check off, the real files you intend to touch, and the risks you actually found.',
65
+ ];
66
+ const verdictRule = interactive
67
+ ? [
68
+ '- After submit_plan, wait for the verdict. If it is rejected, address the',
69
+ ' feedback and call submit_plan again.',
70
+ ]
71
+ : [];
72
+ return [
73
+ '<plan_mode>',
74
+ 'You are in PLAN MODE. The user toggled it with Shift+Tab. This is a research and',
75
+ 'design mode: you must not change anything on disk and must not run commands.',
76
+ '',
77
+ 'Work in this order:',
78
+ '1. Ground yourself in the real code. Use read_file, list_dir, glob, grep and any',
79
+ ' relevant skill. Never plan against a directory structure you have not read.',
80
+ ...askStep,
81
+ ...submitStep,
82
+ '',
83
+ 'Rules:',
84
+ '- While PLAN MODE is active, write_file, edit_file, bash, skill_install and',
85
+ ' skill_create are refused. Do not attempt to work around a refusal; finish the',
86
+ ' plan instead.',
87
+ '- Never ask a question the workspace already answers.',
88
+ '- Never invent a path. Cite files you have read.',
89
+ ...verdictRule,
90
+ interactive ? SUPERSEDE_PARAGRAPH : HEADLESS_PARAGRAPH,
91
+ '</plan_mode>',
92
+ ].join('\n');
93
+ }
@@ -11,9 +11,10 @@
11
11
  * synthesizes a generic error notice (a swallowed throw is never a no-op).
12
12
  */
13
13
  import type { AgentEvent, ModelCost, ToolResult, TokenUsage } from '@aragon-agent/core';
14
+ import type { AgentMode } from './agent-mode.js';
14
15
  export type NoticeLevel = 'info' | 'warn' | 'error';
15
16
  export type ToastLevel = NoticeLevel | 'success';
16
- export type Overlay = null | 'settings' | 'model' | 'help' | 'confirm';
17
+ export type Overlay = null | 'settings' | 'model' | 'help' | 'confirm' | 'question' | 'plan';
17
18
  export type ToolStatus = 'pending' | 'running' | 'done' | 'error';
18
19
  /** Ephemeral, auto-dismissed acknowledgement (spec §3.9). */
19
20
  export interface Toast {
@@ -72,6 +73,14 @@ export interface ViewState {
72
73
  toasts: Toast[];
73
74
  /** Which tool cards are expanded to their full stored preview (spec §3.7). */
74
75
  expandedToolIds: Record<string, true>;
76
+ /**
77
+ * MIRROR of the controller's effective mode, for rendering only (plan-mode
78
+ * §3.1). Written exclusively from what `controller.setAgentMode()` reports it
79
+ * adopted — never from what the UI asked for.
80
+ */
81
+ agentMode: AgentMode;
82
+ /** Set only by a DEFERRED `plan -> build`; drives the `PLAN -> BUILD` readout. */
83
+ pendingAgentMode: AgentMode | null;
75
84
  seq: number;
76
85
  streamingId?: string;
77
86
  turnProduced: boolean;
@@ -152,6 +161,10 @@ export type ViewAction = {
152
161
  } | {
153
162
  type: 'restoreEntries';
154
163
  entries: Entry[];
164
+ } | {
165
+ type: 'setAgentMode';
166
+ mode: AgentMode;
167
+ pending: AgentMode | null;
155
168
  };
156
169
  export declare const STORED_PREVIEW_CHARS = 8000;
157
170
  export declare const STORED_PREVIEW_LINES = 200;
@@ -23,6 +23,10 @@ export function initialViewState() {
23
23
  thinkingVisible: true,
24
24
  toasts: [],
25
25
  expandedToolIds: {},
26
+ // Seeded to the default posture; the App overrides it once on mount from
27
+ // `controller.getAgentMode()` so `--plan` renders on the first frame.
28
+ agentMode: 'build',
29
+ pendingAgentMode: null,
26
30
  seq: 0,
27
31
  streamingId: undefined,
28
32
  turnProduced: false,
@@ -376,6 +380,11 @@ export function viewReducer(state, action) {
376
380
  };
377
381
  case 'setOverlay':
378
382
  return { ...state, overlay: action.overlay };
383
+ // Pure mirror write. The App only ever dispatches what
384
+ // `controller.setAgentMode()` reported it ADOPTED, so this reducer has no
385
+ // deferral logic of its own to get out of step with (§3.1 / R-P7).
386
+ case 'setAgentMode':
387
+ return { ...state, agentMode: action.mode, pendingAgentMode: action.pending };
379
388
  case 'toggleThinking':
380
389
  return { ...state, thinkingVisible: !state.thinkingVisible };
381
390
  case 'pushToast': {
@@ -7,6 +7,7 @@
7
7
  * the available tools with their working directory.
8
8
  */
9
9
  import type { AgentTool } from '@aragon-agent/core';
10
+ import type { AgentMode } from './agent-mode.js';
10
11
  export interface SystemPromptParams {
11
12
  cwd: string;
12
13
  tools: AgentTool[];
@@ -21,6 +22,20 @@ export interface SystemPromptParams {
21
22
  * an unconditional extra guidance line would quietly break all three.
22
23
  */
23
24
  skillsBlock?: string;
25
+ /**
26
+ * The session's current mode. Absent or `'build'` produces a BYTE-IDENTICAL
27
+ * prompt to the pre-plan-mode output for a fixed `tools` array (I-P1) — the
28
+ * `<plan_mode>` block is spliced conditionally, exactly as `skillsBlock` is.
29
+ */
30
+ agentMode?: AgentMode;
31
+ /**
32
+ * Whether a human can actually be reached. Only read when `agentMode` is
33
+ * `'plan'`: the headless variant of the block tells the model to write the
34
+ * plan as its final message instead of calling tools it does not have.
35
+ */
36
+ planInteractive?: boolean;
37
+ /** `planModeMaxAskRounds`, substituted into the block so the two agree. */
38
+ planMaxAskRounds?: number;
24
39
  }
25
40
  /** Describe the active shell so the model does not emit incompatible syntax. */
26
41
  export declare function describeShell(): {
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import os from 'node:os';
10
10
  import process from 'node:process';
11
+ import { buildPlanModeBlock } from './plan-prompt.js';
11
12
  /** Describe the active shell so the model does not emit incompatible syntax. */
12
13
  export function describeShell() {
13
14
  const platform = process.platform;
@@ -30,6 +31,12 @@ export function buildSystemPrompt(params) {
30
31
  : `The bash tool runs commands through ${shell} ("/bin/sh -c"). Standard POSIX syntax is fine.`;
31
32
  const skillsBlock = params.skillsBlock ?? '';
32
33
  const hasSkills = skillsBlock.length > 0;
34
+ const planBlock = params.agentMode === 'plan'
35
+ ? buildPlanModeBlock({
36
+ interactive: params.planInteractive !== false,
37
+ maxAskRounds: params.planMaxAskRounds ?? 4,
38
+ })
39
+ : '';
33
40
  return [
34
41
  'You are AragonAgent, an autonomous coding assistant operating inside a terminal (TUI).',
35
42
  'You help the user accomplish software-engineering tasks by reasoning step by step and using the provided tools.',
@@ -42,6 +49,7 @@ export function buildSystemPrompt(params) {
42
49
  'Available tools:',
43
50
  toolList,
44
51
  ...(hasSkills ? ['', skillsBlock] : []),
52
+ ...(planBlock ? ['', planBlock] : []),
45
53
  '',
46
54
  'Operating guidance:',
47
55
  '- Prefer reading files and inspecting the workspace before making changes.',
package/dist/cli.d.ts CHANGED
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * The shebang is prepended post-build by scripts/prepend-shebang.mjs.
10
10
  */
11
- export {};
11
+ import '../runtime/insecure-tls-warning.cjs';