@aragon-agent/cli 0.4.3 → 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.
- package/CHANGELOG.md +131 -0
- package/README.md +227 -3
- package/dist/agent/agent-mode.d.ts +53 -0
- package/dist/agent/agent-mode.js +69 -0
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +154 -1
- package/dist/agent/plan-prompt.d.ts +18 -0
- package/dist/agent/plan-prompt.js +93 -0
- package/dist/agent/reducer.d.ts +14 -1
- package/dist/agent/reducer.js +9 -0
- package/dist/agent/system-prompt.d.ts +15 -0
- package/dist/agent/system-prompt.js +8 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +183 -20
- package/dist/commands/builtins.js +60 -0
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +71 -4
- package/dist/config/app-paths.js +139 -8
- package/dist/config/cli-commands.d.ts +43 -0
- package/dist/config/cli-commands.js +196 -0
- package/dist/config/env.d.ts +7 -1
- package/dist/config/env.js +45 -1
- package/dist/config/load.d.ts +8 -0
- package/dist/config/load.js +59 -2
- package/dist/config/migrate-home.d.ts +63 -0
- package/dist/config/migrate-home.js +165 -0
- package/dist/config/migrate-legacy-state.d.ts +10 -0
- package/dist/config/migrate-legacy-state.js +15 -6
- package/dist/config/schema.d.ts +61 -0
- package/dist/config/schema.js +83 -0
- package/dist/config/store.d.ts +30 -12
- package/dist/config/store.js +47 -35
- package/dist/logging/cli-commands.d.ts +25 -0
- package/dist/logging/cli-commands.js +221 -0
- package/dist/logging/file-sink.d.ts +114 -0
- package/dist/logging/file-sink.js +326 -0
- package/dist/logging/install.d.ts +163 -0
- package/dist/logging/install.js +302 -0
- package/dist/logging/levels.d.ts +40 -0
- package/dist/logging/levels.js +59 -0
- package/dist/logging/logger.d.ts +110 -0
- package/dist/logging/logger.js +237 -0
- package/dist/logging/redact.d.ts +28 -0
- package/dist/logging/redact.js +90 -0
- package/dist/logging/secret-registry.d.ts +30 -0
- package/dist/logging/secret-registry.js +55 -0
- package/dist/skills/paths.d.ts +2 -4
- package/dist/skills/paths.js +7 -9
- package/dist/tools/human-input.d.ts +179 -0
- package/dist/tools/human-input.js +227 -0
- package/dist/tools/index.d.ts +35 -3
- package/dist/tools/index.js +96 -9
- package/dist/tools/plan-tools.d.ts +64 -0
- package/dist/tools/plan-tools.js +198 -0
- package/dist/ui/App.d.ts +4 -1
- package/dist/ui/App.js +182 -11
- package/dist/ui/Composer.d.ts +4 -1
- package/dist/ui/Composer.js +26 -7
- package/dist/ui/ModeChip.d.ts +29 -0
- package/dist/ui/ModeChip.js +15 -0
- package/dist/ui/PromptInput.d.ts +8 -1
- package/dist/ui/PromptInput.js +12 -2
- package/dist/ui/StatusBar.d.ts +15 -0
- package/dist/ui/StatusBar.js +10 -2
- package/dist/ui/entries/ToolPreview.d.ts +2 -0
- package/dist/ui/entries/ToolPreview.js +64 -0
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +8 -0
- package/dist/ui/layout/wrap-rows.d.ts +24 -0
- package/dist/ui/layout/wrap-rows.js +74 -0
- package/dist/ui/overlays/HelpOverlay.js +13 -0
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +50 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +117 -0
- package/dist/ui/overlays/QuestionOverlay.d.ts +30 -0
- package/dist/ui/overlays/QuestionOverlay.js +193 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +8 -1
- package/dist/ui/use-startup-notices.d.ts +15 -0
- package/dist/ui/use-startup-notices.js +38 -0
- package/package.json +3 -2
- package/runtime/insecure-tls-warning.cjs +62 -0
- package/runtime/insecure-tls-warning.d.cts +5 -0
package/dist/agent/controller.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/dist/agent/reducer.d.ts
CHANGED
|
@@ -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;
|
package/dist/agent/reducer.js
CHANGED
|
@@ -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