@aragon-agent/cli 0.6.39 → 0.6.42
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 +109 -0
- package/README.md +126 -20
- package/dist/agent/controller.d.ts +139 -14
- package/dist/agent/controller.js +299 -40
- package/dist/agent/model-profile-settings.js +12 -1
- package/dist/agent/plan-prompt.d.ts +1 -3
- package/dist/agent/plan-prompt.js +13 -7
- package/dist/agent/reducer.d.ts +29 -1
- package/dist/agent/reducer.js +34 -6
- package/dist/agent/skill-triage-prompt.d.ts +42 -0
- package/dist/agent/skill-triage-prompt.js +74 -0
- package/dist/agent/system-prompt.d.ts +33 -2
- package/dist/agent/system-prompt.js +12 -4
- package/dist/cli.js +21 -5
- package/dist/commands/builtins.js +28 -185
- package/dist/commands/skills.js +86 -1
- package/dist/commands/subagent-command.d.ts +30 -0
- package/dist/commands/subagent-command.js +39 -0
- package/dist/commands/team-command.d.ts +49 -0
- package/dist/commands/team-command.js +259 -0
- package/dist/compaction/command.js +16 -3
- package/dist/compaction/compactor.d.ts +25 -0
- package/dist/compaction/compactor.js +111 -10
- package/dist/compaction/digest.d.ts +24 -1
- package/dist/compaction/digest.js +139 -17
- package/dist/compaction/limits.d.ts +21 -0
- package/dist/compaction/limits.js +21 -0
- package/dist/compaction/reason-text.d.ts +36 -0
- package/dist/compaction/reason-text.js +78 -0
- package/dist/compaction/types.d.ts +82 -14
- package/dist/compaction/wiring.d.ts +13 -8
- package/dist/compaction/wiring.js +1 -0
- package/dist/config/load.js +50 -3
- package/dist/config/model-kits.d.ts +2 -1
- package/dist/config/schema.d.ts +82 -6
- package/dist/config/schema.js +107 -9
- package/dist/config/store.js +10 -4
- package/dist/proc/log-mirror.d.ts +21 -4
- package/dist/proc/log-mirror.js +29 -7
- package/dist/skills/service.d.ts +41 -0
- package/dist/skills/service.js +62 -0
- package/dist/skills/triage.d.ts +194 -0
- package/dist/skills/triage.js +393 -0
- package/dist/team/dispatch-gate.d.ts +86 -0
- package/dist/team/dispatch-gate.js +75 -0
- package/dist/team/forced-dispatch.d.ts +95 -0
- package/dist/team/forced-dispatch.js +208 -0
- package/dist/team/forced-mode.d.ts +116 -0
- package/dist/team/forced-mode.js +156 -0
- package/dist/team/forced-prompt.d.ts +48 -0
- package/dist/team/forced-prompt.js +84 -0
- package/dist/team/limits.d.ts +61 -1
- package/dist/team/limits.js +69 -1
- package/dist/team/model.d.ts +5 -5
- package/dist/team/model.js +5 -5
- package/dist/team/normalize.d.ts +15 -0
- package/dist/team/normalize.js +21 -4
- package/dist/team/prompt.d.ts +6 -3
- package/dist/team/prompt.js +9 -5
- package/dist/team/task-tool.js +11 -7
- package/dist/tools/human-input.d.ts +35 -2
- package/dist/tools/human-input.js +38 -0
- package/dist/tools/plan-tools.d.ts +20 -12
- package/dist/tools/plan-tools.js +24 -17
- package/dist/ui/App.js +130 -11
- package/dist/ui/StatusBar.d.ts +15 -0
- package/dist/ui/StatusBar.js +2 -0
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +11 -1
- package/dist/ui/entries/CompactionCard.js +65 -4
- package/dist/ui/layout/status-layout.d.ts +27 -0
- package/dist/ui/layout/status-layout.js +27 -1
- package/dist/ui/overlays/HelpOverlay.js +8 -4
- package/dist/ui/overlays/SettingsScreen.d.ts +14 -0
- package/dist/ui/overlays/SettingsScreen.js +39 -5
- package/dist/ui/overlays/TeamFanoutPage.d.ts +51 -0
- package/dist/ui/overlays/TeamFanoutPage.js +90 -0
- package/dist/ui/overlays/kit-settings-state.js +3 -2
- package/dist/ui/overlays/model-profile-state.d.ts +20 -2
- package/dist/ui/overlays/model-profile-state.js +12 -0
- package/dist/ui/overlays/team-fanout-state.d.ts +106 -0
- package/dist/ui/overlays/team-fanout-state.js +155 -0
- package/package.json +81 -81
package/dist/ui/App.js
CHANGED
|
@@ -50,6 +50,11 @@ import { acceptanceChip, acceptanceRung, buildTodoRailLayout } from './layout/to
|
|
|
50
50
|
import { buildTeamPanelLayout } from './layout/team-panel.js';
|
|
51
51
|
import { isLiveTeamCard, lastTeamEntry, rosterCounts } from './team-status.js';
|
|
52
52
|
import { advanceBudget, decideFollowThrough, emptyBudget, } from '../todo/follow-through.js';
|
|
53
|
+
// THE THIRD LAYER OF FORCED TEAM MODE (team-command-forced-dispatch §3.7): the
|
|
54
|
+
// decision and its two strings live in a pure module, so `App` gains wiring and
|
|
55
|
+
// no policy. Imported beside the todo decision it shares one timer with.
|
|
56
|
+
import { decideForcedDispatch, FORCED_NUDGE_NOTICE } from '../team/dispatch-gate.js';
|
|
57
|
+
import { FORCED_NUDGE_GRACE_MS } from '../team/limits.js';
|
|
53
58
|
import { useTerminalSize } from './layout/useTerminalSize.js';
|
|
54
59
|
import { useHeightStore } from './use-height-store.js';
|
|
55
60
|
import { useRenderGovernor } from './use-render-governor.js';
|
|
@@ -435,6 +440,32 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
435
440
|
const updateToastPushed = useRef(false);
|
|
436
441
|
const budgetRef = useRef(emptyBudget());
|
|
437
442
|
const followTimer = useRef(null);
|
|
443
|
+
/**
|
|
444
|
+
* The forced-dispatch gate's three counters (team-command-forced-dispatch
|
|
445
|
+
* §3.7). REFS, for the reason the follow-through block above records: every
|
|
446
|
+
* field added to `ViewState` is a field `restoreEntries`, `/clear`, `/reset`
|
|
447
|
+
* and the session round-trip each have to have an opinion about, and none of
|
|
448
|
+
* them has an opinion about a counter that lives for one turn.
|
|
449
|
+
*
|
|
450
|
+
* `toolsUsed` and `dispatchAttempted` are cleared at `agent_start`; `nudges`
|
|
451
|
+
* is NOT - see its own comment.
|
|
452
|
+
*/
|
|
453
|
+
const toolsUsedRef = useRef(0);
|
|
454
|
+
const dispatchAttemptedRef = useRef(false);
|
|
455
|
+
/**
|
|
456
|
+
* Fallback nudges spent. CLEARED BY A USER SUBMIT, NEVER BY `agent_start`
|
|
457
|
+
* (P0-2 / I-10).
|
|
458
|
+
*
|
|
459
|
+
* The nudge itself starts a NEW TURN, so a reset at `agent_start` would zero
|
|
460
|
+
* this counter at the top of every continuation and "at most one per turn"
|
|
461
|
+
* would become "one per turn, forever" - an unbounded loop of N-child
|
|
462
|
+
* dispatches that only ever fires while the model is being disobedient, which
|
|
463
|
+
* is precisely the state no manual test reproduces. The one reset lives in
|
|
464
|
+
* `submitMessage` under `opts.userInitiated !== false`, beside the
|
|
465
|
+
* `recordPrompt` call that answers the same question ("did a human ask for
|
|
466
|
+
* this?").
|
|
467
|
+
*/
|
|
468
|
+
const nudgesRef = useRef(0);
|
|
438
469
|
/** True while the run in flight was started by the grace timer, not the user. */
|
|
439
470
|
const autoContinuationRef = useRef(false);
|
|
440
471
|
/**
|
|
@@ -526,8 +557,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
526
557
|
* Disarm an armed continuation. Idempotent; NEVER touches the budget — a
|
|
527
558
|
* cancelled continuation was never issued, so it must not be charged (AC-18).
|
|
528
559
|
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
560
|
+
* ONE SLOT: `Esc`, `submitMessage`, the reset/restore port, the effect cleanup
|
|
561
|
+
* and both follow-through arms (todo, then the gate) all cancel before arming.
|
|
531
562
|
*/
|
|
532
563
|
const cancelFollowThrough = useCallback(() => {
|
|
533
564
|
if (followTimer.current) {
|
|
@@ -639,6 +670,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
639
670
|
if (event.type === 'agent_start') {
|
|
640
671
|
interactionPhase.current = 'running';
|
|
641
672
|
manualMetrics.current = createRunMetrics();
|
|
673
|
+
// THE TWO PER-TURN COUNTERS ONLY (I-10). `nudgesRef` is deliberately
|
|
674
|
+
// absent: a nudge continuation arrives here too, and clearing it would
|
|
675
|
+
// delete the bound that keeps a disobedient model from looping.
|
|
676
|
+
toolsUsedRef.current = 0;
|
|
677
|
+
dispatchAttemptedRef.current = false;
|
|
642
678
|
}
|
|
643
679
|
if (event.type === 'agent_end') {
|
|
644
680
|
interactionPhase.current = 'idle';
|
|
@@ -654,6 +690,17 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
654
690
|
if (action.type === 'notice' && action.level === 'error') {
|
|
655
691
|
endReasonRef.current.errored = true;
|
|
656
692
|
}
|
|
693
|
+
// THE GATE'S TWO FACTS, counted where the reducer's own actions already
|
|
694
|
+
// flow (no second subscription to keep in step). `toolExecStart` carries
|
|
695
|
+
// the tool NAME, so a `task` call that was REFUSED - team off, dispatch
|
|
696
|
+
// busy, no key - still counts as an attempt (P1-7): the upstream already
|
|
697
|
+
// said no, and urging a re-dispatch would reproduce that refusal one
|
|
698
|
+
// turn later.
|
|
699
|
+
if (action.type === 'toolExecStart') {
|
|
700
|
+
toolsUsedRef.current += 1;
|
|
701
|
+
if (action.toolName === 'task')
|
|
702
|
+
dispatchAttemptedRef.current = true;
|
|
703
|
+
}
|
|
657
704
|
if (action.type === 'textDelta' || action.type === 'thinkingDelta') {
|
|
658
705
|
pending.current.push(action);
|
|
659
706
|
if (!flushTimer.current) {
|
|
@@ -688,13 +735,36 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
688
735
|
const todos = controller.getTodoSnapshot();
|
|
689
736
|
budgetRef.current = advanceBudget(budgetRef.current, todos, autoContinuationRef.current);
|
|
690
737
|
autoContinuationRef.current = false;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
738
|
+
// --- Forced-dispatch gate (team-command-forced-dispatch §3.7). --------
|
|
739
|
+
//
|
|
740
|
+
// THE THIRD LAYER, AND THE ONLY MECHANICAL ONE: if a whole turn went by
|
|
741
|
+
// with tools used and nothing dispatched, ask ONCE for a dispatch. It is
|
|
742
|
+
// computed BEFORE the todo decision and DOWN-GRADES that decision to
|
|
743
|
+
// `none` rather than returning early (P0-3 / I-9): the two statements below
|
|
744
|
+
// - the `endReasonRef` reset and `applyPendingMode()` - belong to EVERY
|
|
745
|
+
// turn, and a `return` here would silently defer a pending `plan -> build`
|
|
746
|
+
// by a whole turn and carry `errored` into the next one. It also keeps
|
|
747
|
+
// `cancelFollowThrough()`'s callers at three: the gate reuses the timer
|
|
748
|
+
// instead of adding a fourth canceller.
|
|
749
|
+
const forced = controller.getForcedTeam();
|
|
750
|
+
const gate = decideForcedDispatch({
|
|
751
|
+
armed: forced !== null,
|
|
752
|
+
count: forced?.count ?? 0,
|
|
753
|
+
dispatchAttempted: dispatchAttemptedRef.current,
|
|
754
|
+
toolsUsed: toolsUsedRef.current,
|
|
755
|
+
nudgesUsed: nudgesRef.current,
|
|
694
756
|
runEnd: { ...endReasonRef.current },
|
|
695
|
-
budget: budgetRef.current,
|
|
696
|
-
interactive: true,
|
|
697
757
|
});
|
|
758
|
+
const gated = gate.kind === 'nudge' && forced !== null;
|
|
759
|
+
const decision = gated
|
|
760
|
+
? { kind: 'none' }
|
|
761
|
+
: decideFollowThrough({
|
|
762
|
+
mode: controller.getTodoConfig().followThrough,
|
|
763
|
+
snapshot: todos,
|
|
764
|
+
runEnd: { ...endReasonRef.current },
|
|
765
|
+
budget: budgetRef.current,
|
|
766
|
+
interactive: true,
|
|
767
|
+
});
|
|
698
768
|
endReasonRef.current = { aborted: false, errored: false };
|
|
699
769
|
if (decision.kind === 'notify') {
|
|
700
770
|
dispatch({ type: 'notice', level: decision.level, text: decision.text });
|
|
@@ -739,6 +809,22 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
739
809
|
submitRef.current(decision.message, { userInitiated: false, todoPolicy: 'continue' });
|
|
740
810
|
}, decision.graceMs);
|
|
741
811
|
}
|
|
812
|
+
// THE GATE'S OWN CONTINUATION: after the todo branch (which it has
|
|
813
|
+
// already silenced) and BEFORE `applyPendingMode()`, on the SAME timer and
|
|
814
|
+
// with the same re-checks. `level: 'warn'` and not `'error'` - the error
|
|
815
|
+
// level sets `endReasonRef.errored`, which would make the next turn's gate
|
|
816
|
+
// read "the run failed" and switch itself off for the rest of the session.
|
|
817
|
+
if (gated && gate.kind === 'nudge') {
|
|
818
|
+
nudgesRef.current += 1;
|
|
819
|
+
dispatch({ type: 'notice', level: 'warn', text: FORCED_NUDGE_NOTICE });
|
|
820
|
+
cancelFollowThrough(); // I-9 / I-3: at most one armed continuation, ever
|
|
821
|
+
followTimer.current = setTimeout(() => {
|
|
822
|
+
followTimer.current = null;
|
|
823
|
+
if (controller.isRunning() || controller.isCompactionBusy?.())
|
|
824
|
+
return;
|
|
825
|
+
submitRef.current(gate.message, { userInitiated: false, todoPolicy: 'continue' });
|
|
826
|
+
}, FORCED_NUDGE_GRACE_MS);
|
|
827
|
+
}
|
|
742
828
|
// A deferred `plan -> build` becomes real here and nowhere else (§3.2).
|
|
743
829
|
const applied = controller.applyPendingMode();
|
|
744
830
|
if (applied) {
|
|
@@ -1014,6 +1100,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1014
1100
|
// correct value (P0-1).
|
|
1015
1101
|
dispatch({ type: 'compactionEnd', record: event.record });
|
|
1016
1102
|
break;
|
|
1103
|
+
case 'phase':
|
|
1104
|
+
// THE STAGE LINE ON THE LIVE CARD (context-compaction-never-fires C).
|
|
1105
|
+
// Dispatch is unconditional; the reducer is what ignores a phase that
|
|
1106
|
+
// arrives for a card the settle has already closed.
|
|
1107
|
+
dispatch({ type: 'compactionPhase', index: event.index, phase: event.phase });
|
|
1108
|
+
break;
|
|
1017
1109
|
case 'usage': {
|
|
1018
1110
|
// Compaction spend is REAL SPEND at the SUMMARIZER's price (§6.4 /
|
|
1019
1111
|
// AC-15), which may be neither the lead's nor the fast tier's — so the
|
|
@@ -1950,8 +2042,15 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1950
2042
|
// composer's hint fade. An auto-continuation demonstrates nothing about what
|
|
1951
2043
|
// the user has learned, so suppressing the fade is correct rather than
|
|
1952
2044
|
// incidental — which is why the parameter is named for the CAUSE (P2-8).
|
|
1953
|
-
if (opts.userInitiated !== false)
|
|
2045
|
+
if (opts.userInitiated !== false) {
|
|
1954
2046
|
recordPrompt(message);
|
|
2047
|
+
// THE ONE PLACE THE NUDGE BUDGET IS REFILLED (P0-2 / I-10). Same predicate
|
|
2048
|
+
// as `recordPrompt`, and for the same reason: the question is "did a human
|
|
2049
|
+
// ask for this?", and only a human may hand the gate another attempt. A
|
|
2050
|
+
// nudge continuation arrives here with `userInitiated: false`, so it cannot
|
|
2051
|
+
// refill its own budget.
|
|
2052
|
+
nudgesRef.current = 0;
|
|
2053
|
+
}
|
|
1955
2054
|
// Mirrors where the reducer clears its own copies, in the same action
|
|
1956
2055
|
// (C-11): a new turn's end reason must not inherit the previous one's.
|
|
1957
2056
|
endReasonRef.current = { aborted: false, errored: false };
|
|
@@ -2960,7 +3059,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2960
3059
|
// the save handler cannot disagree about what `(inherit)` means.
|
|
2961
3060
|
...fastSettingsFrom(cfg.fast),
|
|
2962
3061
|
...compactionSettingsFrom(cfg.compaction),
|
|
2963
|
-
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, draft: settingsDraft,
|
|
3062
|
+
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, draft: settingsDraft,
|
|
3063
|
+
// THE MISSING LINK (P1-5): without it the `Team` page cannot render
|
|
3064
|
+
// `on (next launch)` for a `--no-team` session, because the subpage is
|
|
3065
|
+
// mounted with `{...props}` and an undeclared prop arrives as
|
|
3066
|
+
// `undefined`. A construction-time snapshot, so reading it per frame is
|
|
3067
|
+
// honest (I-7).
|
|
3068
|
+
teamRegistered: controller.isTeamRegistered(), probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
|
|
2964
3069
|
// Read from `cfg` (render scope, re-read every frame), so `/retry off`
|
|
2965
3070
|
// while the screen is open shows the new value on the next paint.
|
|
2966
3071
|
retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'agents' ? (
|
|
@@ -3070,6 +3175,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
3070
3175
|
railRows: native ? (nativeBudget?.band.railRows ?? 0) : (fullscreenRailVisible ? railRows : 0),
|
|
3071
3176
|
width: native ? (nativeBudget?.band.railCols ?? 0) : railWidth,
|
|
3072
3177
|
});
|
|
3178
|
+
// THE FORCED-MODE CHIP'S SOURCE (team-command-forced-dispatch §3.8 / D-9),
|
|
3179
|
+
// read from the controller on every frame: `armForcedTeam()` writes no
|
|
3180
|
+
// `ViewState`, so a mirror would have to be kept in step by hand - which is the
|
|
3181
|
+
// double-truth this design deletes elsewhere. It is handed to `StatusBar`, which
|
|
3182
|
+
// plans the primary row from its own props (a copy on `statusInput` above would
|
|
3183
|
+
// be a field nothing renders).
|
|
3184
|
+
const forcedTeam = controller.getForcedTeam();
|
|
3073
3185
|
const statusInput = {
|
|
3074
3186
|
columns: viewCols, phase: state.runPhase, pendingCount: state.pendingSteering.length,
|
|
3075
3187
|
context: state.context, usageTotal: state.usageTotal, thinkingLevel: cfg.thinkingLevel,
|
|
@@ -3138,7 +3250,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
3138
3250
|
// partially stale. The window and its `known` flag now travel INSIDE
|
|
3139
3251
|
// it, resolved by the meter - which is what makes the user's
|
|
3140
3252
|
// `contextWindow` override reach the bar.
|
|
3141
|
-
context: state.context, status: state.status, elapsedMs: elapsedMs, thinkingLevel: cfg.thinkingLevel, tokPerSec: tokPerSec, theme: theme, caps: caps, scrolledLines: scrolledLines, redrawNonce: redrawNonce, agentMode: state.agentMode, pendingAgentMode: state.pendingAgentMode, ecoRung: governor.rung, ...(state.team
|
|
3253
|
+
context: state.context, status: state.status, elapsedMs: elapsedMs, thinkingLevel: cfg.thinkingLevel, tokPerSec: tokPerSec, theme: theme, caps: caps, scrolledLines: scrolledLines, redrawNonce: redrawNonce, agentMode: state.agentMode, pendingAgentMode: state.pendingAgentMode, ...(forcedTeam ? { forcedTeam } : {}), ecoRung: governor.rung, ...(state.team
|
|
3142
3254
|
? {
|
|
3143
3255
|
// `rosterCounts`, NOT a fourth inline phase filter (A-1): the chip and
|
|
3144
3256
|
// the roster header above it must never disagree.
|
|
@@ -3153,7 +3265,14 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
3153
3265
|
? { todoActive: { done: state.todos.doneCount, total: state.todos.total } }
|
|
3154
3266
|
: {}), ...(acceptanceActive ? { acceptanceActive } : {}), ...(liveServices > 0 ? { servicesActive: { live: liveServices } } : {}), ...(state.fast?.live ? { fastActive: { inFlight: state.fast.inFlight } } : {}),
|
|
3155
3267
|
// Manual compaction starts before its next snapshot is published.
|
|
3156
|
-
|
|
3268
|
+
// THE AUTO-PAUSED CHIP RIDES HERE AND NOWHERE ELSE (never-fires C /
|
|
3269
|
+
// analysis 3.3.1): `selfDisabled` used to live only in `/compact status`,
|
|
3270
|
+
// a command nobody types while the gauge sits at 95 %. `StatusBar` plans
|
|
3271
|
+
// the primary row from its own props, so a second copy of the flag on
|
|
3272
|
+
// `statusInput` would be a field nothing renders - and a fact with two
|
|
3273
|
+
// sources is a fact that drifts.
|
|
3274
|
+
compactionActive: { inFlight: statusInput.compacting ?? false,
|
|
3275
|
+
selfDisabled: state.compaction?.selfDisabled === true }, ...(controller.isCompactionEnabled()
|
|
3157
3276
|
? {
|
|
3158
3277
|
gaugeMarks: {
|
|
3159
3278
|
warn: cfg.compaction.warnThreshold * 100,
|
package/dist/ui/StatusBar.d.ts
CHANGED
|
@@ -65,6 +65,20 @@ export interface StatusBarProps {
|
|
|
65
65
|
} | null;
|
|
66
66
|
agentMode?: AgentMode;
|
|
67
67
|
pendingAgentMode?: AgentMode | null;
|
|
68
|
+
/**
|
|
69
|
+
* Forced team mode is armed, with the count it was armed with
|
|
70
|
+
* (team-command-forced-dispatch §3.8 / D-9). ABSENT = not armed, and every
|
|
71
|
+
* existing caller renders the row it always did.
|
|
72
|
+
*
|
|
73
|
+
* IT RIDES THIS COMPONENT'S PROPS AND NOT THE APP'S `statusInput`, by the rule
|
|
74
|
+
* the `compactionActive` chip records where `App` builds it: this component
|
|
75
|
+
* plans the primary row from its OWN props, so a chip left on `statusInput`
|
|
76
|
+
* would be a field nothing renders - which is exactly how the only persistently
|
|
77
|
+
* visible signal of `/team` (D-9) can ship without ever appearing on screen.
|
|
78
|
+
*/
|
|
79
|
+
forcedTeam?: {
|
|
80
|
+
count: number;
|
|
81
|
+
};
|
|
68
82
|
teamActive?: {
|
|
69
83
|
running: number;
|
|
70
84
|
total: number;
|
|
@@ -92,6 +106,7 @@ export interface StatusBarProps {
|
|
|
92
106
|
};
|
|
93
107
|
compactionActive?: {
|
|
94
108
|
inFlight: boolean;
|
|
109
|
+
selfDisabled?: boolean;
|
|
95
110
|
};
|
|
96
111
|
gaugeMarks?: {
|
|
97
112
|
warn: number;
|
package/dist/ui/StatusBar.js
CHANGED
|
@@ -39,7 +39,9 @@ export function StatusBar(props) {
|
|
|
39
39
|
interruptPhase: props.interruptPhase,
|
|
40
40
|
waitingForConfirmation: props.waitingForConfirmation, stopping: props.stopping,
|
|
41
41
|
compacting: props.compactionActive?.inFlight, retryActive: props.retryActive,
|
|
42
|
+
compactionPaused: props.compactionActive?.selfDisabled === true,
|
|
42
43
|
mode: props.agentMode, pendingMode: props.pendingAgentMode, servicesActive: props.servicesActive,
|
|
44
|
+
...(props.forcedTeam ? { forcedTeam: props.forcedTeam } : {}),
|
|
43
45
|
teamActive: props.teamActive, todoActive: props.todoActive,
|
|
44
46
|
...(props.acceptanceActive ? { acceptanceActive: props.acceptanceActive } : {}),
|
|
45
47
|
ecoRung: props.ecoRung, fastActive: props.fastActive, scrolledLines: props.scrolledLines,
|
package/dist/ui/Transcript.js
CHANGED
|
@@ -137,7 +137,7 @@ function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion,
|
|
|
137
137
|
case 'fast':
|
|
138
138
|
return frame(entry.status === 'failed' ? glyphs.toolError : glyphs.teamAgent, fastCardColor(entry.status, theme), _jsx(FastCard, { reviewIndex: entry.reviewIndex, model: entry.model, status: entry.status, text: entry.text, detail: entry.detail, turn: entry.turn, durationMs: entry.durationMs, live: entry.live, reducedMotion: reducedMotion, theme: theme, caps: caps }));
|
|
139
139
|
case 'compaction':
|
|
140
|
-
return frame(entry.applied || entry.live ? glyphs.compaction : glyphs.toolError, compactionCardColor(entry, theme), _jsx(CompactionCard, { index: entry.index, decision: entry.decision, memoryVersion: entry.memoryVersion, trigger: entry.trigger, mode: entry.mode, applied: entry.applied, reason: entry.reason, messagesBefore: entry.messagesBefore, messagesAfter: entry.messagesAfter, tokensBefore: entry.tokensBefore, tokensAfter: entry.tokensAfter, summary: entry.summary, model: entry.model, durationMs: entry.durationMs, tailRelief: entry.tailRelief,
|
|
140
|
+
return frame(entry.applied || entry.live ? glyphs.compaction : glyphs.toolError, compactionCardColor(entry, theme), _jsx(CompactionCard, { index: entry.index, decision: entry.decision, memoryVersion: entry.memoryVersion, trigger: entry.trigger, mode: entry.mode, applied: entry.applied, reason: entry.reason, messagesBefore: entry.messagesBefore, messagesAfter: entry.messagesAfter, tokensBefore: entry.tokensBefore, tokensAfter: entry.tokensAfter, summary: entry.summary, model: entry.model, durationMs: entry.durationMs, tailRelief: entry.tailRelief, phase: entry.phase, keepRelaxed: entry.keepRelaxed, retainedTurns: entry.retainedTurns, retainedMessages: entry.retainedMessages, keepRecentTurnsUsed: entry.keepRecentTurnsUsed, elidedMessages: entry.elidedMessages,
|
|
141
141
|
// ONLY WHILE LIVE, and only when the clock has ticked at least once:
|
|
142
142
|
// `nowSec` is `undefined` until then, and a settled card must keep
|
|
143
143
|
// rendering `durationMs` rather than a figure that would drift.
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import React from 'react';
|
|
24
24
|
import type { Theme } from '../theme.js';
|
|
25
25
|
import type { TermCapabilities } from '../capabilities.js';
|
|
26
|
-
import type { CompactionDecision, CompactionMode, CompactionUiTrigger } from '../../compaction/types.js';
|
|
26
|
+
import type { CompactionDecision, CompactionMode, CompactionPhase, CompactionUiTrigger } from '../../compaction/types.js';
|
|
27
27
|
export interface CompactionCardProps {
|
|
28
28
|
decision?: CompactionDecision;
|
|
29
29
|
memoryVersion?: 2;
|
|
@@ -39,6 +39,16 @@ export interface CompactionCardProps {
|
|
|
39
39
|
summary?: string;
|
|
40
40
|
model: string;
|
|
41
41
|
durationMs?: number;
|
|
42
|
+
/** The live card's stage, so "compacting" also says WHAT (never-fires C). */
|
|
43
|
+
phase?: CompactionPhase;
|
|
44
|
+
/** Set when this cut existed only because `keepRecentTurns` was relaxed (B). */
|
|
45
|
+
keepRelaxed?: boolean;
|
|
46
|
+
retainedTurns?: number;
|
|
47
|
+
/** Messages the relaxed cut left verbatim, which is not the same as turns. */
|
|
48
|
+
retainedMessages?: number;
|
|
49
|
+
keepRecentTurnsUsed?: number;
|
|
50
|
+
/** Messages the digest left out and therefore cannot be quoted (A1). */
|
|
51
|
+
elidedMessages?: number;
|
|
42
52
|
/**
|
|
43
53
|
* Wall-clock since the card opened, LIVE ONLY
|
|
44
54
|
* (context-auto-compaction-hardening §3.6 / W5).
|
|
@@ -28,6 +28,7 @@ import { pickGlyphs } from '../glyphs.js';
|
|
|
28
28
|
import { railBorderProps } from '../layout/Gutter.js';
|
|
29
29
|
import { formatDuration, formatTokens } from '../../agent/usage.js';
|
|
30
30
|
import { COMPACTION_LIMITS } from '../../compaction/limits.js';
|
|
31
|
+
import { compactionPhaseLabel, compactionReasonLine } from '../../compaction/reason-text.js';
|
|
31
32
|
/**
|
|
32
33
|
* The card's colour.
|
|
33
34
|
*
|
|
@@ -56,12 +57,19 @@ function headline(props, arrow, dot) {
|
|
|
56
57
|
// second 45 this was the only changing information on the card, and there was
|
|
57
58
|
// none.
|
|
58
59
|
const elapsed = props.elapsedMs === undefined ? '' : `${dot}${formatDuration(props.elapsedMs)}`;
|
|
60
|
+
// THE STAGE, NOT JUST "COMPACTING" (never-fires C). Planning and digesting run
|
|
61
|
+
// before any model call, and a user watching a card for twenty seconds cannot
|
|
62
|
+
// tell "thinking hard" from "stuck in a gate" without it.
|
|
63
|
+
const stage = props.phase === undefined ? '' : `${dot}${compactionPhaseLabel(props.phase)}`;
|
|
59
64
|
return props.model
|
|
60
|
-
? `compacting context #${index}${dot}${props.model}${elapsed}`
|
|
61
|
-
: `compacting context #${index}${elapsed}`;
|
|
65
|
+
? `compacting context #${index}${dot}${props.model}${stage}${elapsed}`
|
|
66
|
+
: `compacting context #${index}${stage}${elapsed}`;
|
|
62
67
|
}
|
|
63
68
|
if (!props.applied || props.tokensAfter >= props.tokensBefore) {
|
|
64
|
-
|
|
69
|
+
// THE REASON IN WORDS (never-fires C). `digest_budget_exceeded: $.digest` was
|
|
70
|
+
// a code the user could not act on; `compactionReasonLine` renders the same
|
|
71
|
+
// fact as a clause plus, where there is one, the remedy.
|
|
72
|
+
const why = props.reason ? `: ${compactionReasonLine(props.reason)}` : '';
|
|
65
73
|
return `context not compacted #${index}${why}`;
|
|
66
74
|
}
|
|
67
75
|
const messages = `${props.messagesBefore} ${arrow} ${props.messagesAfter} messages`;
|
|
@@ -87,8 +95,15 @@ function headline(props, arrow, dot) {
|
|
|
87
95
|
*/
|
|
88
96
|
function detailLine(props) {
|
|
89
97
|
if (props.live) {
|
|
98
|
+
// THE ESCAPE, NOT THE STAGE (never-fires C, as corrected in review): the live
|
|
99
|
+
// headline already carries the stage, and a two-line card that names it twice
|
|
100
|
+
// is the one-fact-one-surface rule this file keeps having to restore. With no
|
|
101
|
+
// model resolved there is no call to cancel, so the whole line goes - the same
|
|
102
|
+
// rule the settled branch applies.
|
|
90
103
|
if (!props.model)
|
|
91
104
|
return '';
|
|
105
|
+
if (props.phase !== undefined)
|
|
106
|
+
return 'esc to cancel';
|
|
92
107
|
return `summarizing with ${props.model} - esc to cancel`;
|
|
93
108
|
}
|
|
94
109
|
if (!props.applied || props.tokensAfter >= props.tokensBefore)
|
|
@@ -104,6 +119,50 @@ function detailLine(props) {
|
|
|
104
119
|
const via = props.trigger === 'overflow' ? ' after a context overflow' : '';
|
|
105
120
|
return `summarized ${dropped} messages with ${props.model}${via}`;
|
|
106
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The relaxation disclosure (never-fires B).
|
|
124
|
+
*
|
|
125
|
+
* `planCompaction`'s second pass trades "the last N user turns are verbatim" for
|
|
126
|
+
* "a cut exists at all". That trade must be readable, because the difference
|
|
127
|
+
* between the two compactions is exactly what the user asked for: the tail is the
|
|
128
|
+
* part they care most about keeping word for word.
|
|
129
|
+
*/
|
|
130
|
+
function relaxedLine(props) {
|
|
131
|
+
if (props.live || !props.applied || props.keepRelaxed !== true)
|
|
132
|
+
return '';
|
|
133
|
+
const turns = props.retainedTurns ?? 0;
|
|
134
|
+
const wanted = props.keepRecentTurnsUsed;
|
|
135
|
+
const messages = props.retainedMessages;
|
|
136
|
+
// THE TAIL IS MESSAGES, NOT TURNS (review). The dominant relaxed shape is one
|
|
137
|
+
// prompt plus a hundred tool calls, so `retainedTurns` is 0 while forty messages
|
|
138
|
+
// were kept verbatim: a line reading "kept 0 recent turns" would report a total
|
|
139
|
+
// loss that did not happen. Whenever the count is available it leads, and the
|
|
140
|
+
// turn arithmetic follows as the explanation rather than as the headline.
|
|
141
|
+
if (messages !== undefined && wanted !== undefined) {
|
|
142
|
+
return `kept the last ${messages} messages - ${turns} of the ${wanted} requested turns, ` +
|
|
143
|
+
'since this run had too few user turns';
|
|
144
|
+
}
|
|
145
|
+
if (messages !== undefined)
|
|
146
|
+
return `kept the last ${messages} messages verbatim`;
|
|
147
|
+
return wanted === undefined
|
|
148
|
+
? `kept the last ${turns} turns - this run had too few user turns to keep more`
|
|
149
|
+
: `kept ${turns} of the ${wanted} requested turns - this run had too few user turns`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The digest-elision disclosure (never-fires A1).
|
|
153
|
+
*
|
|
154
|
+
* The digest never rejects for length any more; it keeps the first and last
|
|
155
|
+
* entries verbatim and elides the middle. The elided messages cannot be quoted by
|
|
156
|
+
* the summarizer, so the card says so rather than presenting a summary as if it
|
|
157
|
+
* had seen everything.
|
|
158
|
+
*/
|
|
159
|
+
function elidedLine(props) {
|
|
160
|
+
const n = props.elidedMessages ?? 0;
|
|
161
|
+
if (props.live || n <= 0)
|
|
162
|
+
return '';
|
|
163
|
+
return `elided ${n} ${n === 1 ? 'message' : 'messages'} from the summary input - ` +
|
|
164
|
+
'the middle is summarized without them';
|
|
165
|
+
}
|
|
107
166
|
/**
|
|
108
167
|
* The relief disclosure, on a settled card that ALSO summarized.
|
|
109
168
|
*
|
|
@@ -139,6 +198,8 @@ function CompactionCardImpl(props) {
|
|
|
139
198
|
const head = headline(props, glyphs.arrowRight, dot);
|
|
140
199
|
const detail = detailLine(props);
|
|
141
200
|
const relief = reliefLine(props);
|
|
201
|
+
const relaxed = relaxedLine(props);
|
|
202
|
+
const elided = elidedLine(props);
|
|
142
203
|
const showMemory = props.memoryVersion !== 2 || expanded === true;
|
|
143
204
|
const rows = summary && showMemory ? summaryRows(summary, expanded === true) : [];
|
|
144
205
|
const totalRows = summary ? summary.split('\n').filter((l) => l.trim().length > 0).length : 0;
|
|
@@ -150,7 +211,7 @@ function CompactionCardImpl(props) {
|
|
|
150
211
|
// the whole run, losing the status colour along with the motion.
|
|
151
212
|
const animate = live && reducedMotion !== true && caps.unicode;
|
|
152
213
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.accent, bold: true, children: props.trigger === 'manual' ? 'Manual compaction' : props.trigger === 'overflow'
|
|
153
|
-
? 'Overflow: threshold reached' : 'Automatic: threshold reached' }), _jsxs(Text, { color: live ? color : theme.muted, wrap: caps.unicode ? 'truncate' : 'wrap', children: [animate ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { type: "dots" }), ' '] })) : live ? (`${glyphs.spinnerStill} `) : (''), head] })] }), props.memoryVersion === 2 && (_jsxs(Text, { color: theme.muted, children: ["Task memory v2", expanded ? '' : ' (ctrl+o to expand)'] })), detail.length > 0 && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, children: _jsx(Text, { color: color, wrap: caps.unicode ? 'truncate' : 'wrap', children: detail }) })), relief.length > 0 && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, children: _jsx(Text, { color: theme.noticeWarn, children: relief }) })), rows.length > 0 && (_jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: [rows.map((line, index) => (_jsx(Text, { color: theme.primary, children: line }, index))), hidden > 0 && (_jsxs(Text, { color: theme.muted, children: [glyphs.railEnd, " +", hidden, " more lines (ctrl+o to expand)"] })), hidden === 0 && expanded === true && totalRows > COMPACTION_LIMITS.cardTextRows && (_jsxs(Text, { color: theme.muted, children: [glyphs.railEnd, " ctrl+o to collapse"] }))] }))] }));
|
|
214
|
+
? 'Overflow: threshold reached' : 'Automatic: threshold reached' }), _jsxs(Text, { color: live ? color : theme.muted, wrap: caps.unicode ? 'truncate' : 'wrap', children: [animate ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { type: "dots" }), ' '] })) : live ? (`${glyphs.spinnerStill} `) : (''), head] })] }), props.memoryVersion === 2 && (_jsxs(Text, { color: theme.muted, children: ["Task memory v2", expanded ? '' : ' (ctrl+o to expand)'] })), detail.length > 0 && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, children: _jsx(Text, { color: color, wrap: caps.unicode ? 'truncate' : 'wrap', children: detail }) })), relief.length > 0 && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, children: _jsx(Text, { color: theme.noticeWarn, children: relief }) })), relaxed.length > 0 && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, children: _jsx(Text, { color: theme.muted, children: relaxed }) })), elided.length > 0 && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, children: _jsx(Text, { color: theme.muted, children: elided }) })), rows.length > 0 && (_jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: [rows.map((line, index) => (_jsx(Text, { color: theme.primary, children: line }, index))), hidden > 0 && (_jsxs(Text, { color: theme.muted, children: [glyphs.railEnd, " +", hidden, " more lines (ctrl+o to expand)"] })), hidden === 0 && expanded === true && totalRows > COMPACTION_LIMITS.cardTextRows && (_jsxs(Text, { color: theme.muted, children: [glyphs.railEnd, " ctrl+o to collapse"] }))] }))] }));
|
|
154
215
|
}
|
|
155
216
|
/**
|
|
156
217
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
@@ -31,6 +31,16 @@ export interface StatusLayoutInput {
|
|
|
31
31
|
spinner?: string;
|
|
32
32
|
waitingForConfirmation?: boolean;
|
|
33
33
|
stopping?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Auto-compaction paused itself after repeated no-progress attempts
|
|
36
|
+
* (context-compaction-never-fires C).
|
|
37
|
+
*
|
|
38
|
+
* A SEPARATE FIELD FROM `compacting`, and it renders as a SUFFIX on the context
|
|
39
|
+
* gauge rather than as a phase word: the fact belongs next to the percentage it
|
|
40
|
+
* explains ("Context 95% auto-paused"), and the phase slot must keep answering
|
|
41
|
+
* "is the agent running".
|
|
42
|
+
*/
|
|
43
|
+
compactionPaused?: boolean;
|
|
34
44
|
compacting?: boolean;
|
|
35
45
|
retryActive?: {
|
|
36
46
|
attempt: number;
|
|
@@ -43,6 +53,23 @@ export interface StatusLayoutInput {
|
|
|
43
53
|
servicesActive?: {
|
|
44
54
|
live: number;
|
|
45
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Forced team mode is ARMED, with the fan-out count it was armed with
|
|
58
|
+
* (team-command-forced-dispatch §3.8 / D-9).
|
|
59
|
+
*
|
|
60
|
+
* THE ONLY PERSISTENTLY VISIBLE SIGNAL that `/team` happened. The directive is
|
|
61
|
+
* one turn, the prompt block is invisible, and the panel only appears while a
|
|
62
|
+
* dispatch runs - so without this chip the answer to "why is my agent
|
|
63
|
+
* dispatching?" is "remember what you typed". It is read at RENDER time from
|
|
64
|
+
* `controller.getForcedTeam()`, never mirrored into `ViewState`: a mirror is a
|
|
65
|
+
* second copy that can disagree, the exact defect §2.4 is about.
|
|
66
|
+
*
|
|
67
|
+
* ABSENT means "not armed" and keeps every existing caller's plan BYTE-
|
|
68
|
+
* IDENTICAL (the same discipline `reserveRight` records below).
|
|
69
|
+
*/
|
|
70
|
+
forcedTeam?: {
|
|
71
|
+
count: number;
|
|
72
|
+
};
|
|
46
73
|
teamActive?: {
|
|
47
74
|
running: number;
|
|
48
75
|
total: number;
|
|
@@ -53,6 +53,22 @@ function contextPercent(context) {
|
|
|
53
53
|
const mark = context.source === 'estimate' || context.deltaTokens > 0 ? '~' : '';
|
|
54
54
|
return mark + (context.pct > 999 ? '>999' : Math.round(context.pct)) + '%';
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The words that ride on the gauge when auto-compaction has given up (C).
|
|
58
|
+
*
|
|
59
|
+
* IT RIDES ON THE GAUGE RATHER THAN OCCUPYING ITS OWN FIELD. "Auto-paused" is the
|
|
60
|
+
* only fact that explains a stuck percentage, so the two belong in one field for
|
|
61
|
+
* the same reason `/context` prints them on adjacent lines. A second field would
|
|
62
|
+
* also be the first thing the width ladder drops, and this is precisely the state
|
|
63
|
+
* that must survive a narrow terminal.
|
|
64
|
+
*
|
|
65
|
+
* `paused` at the tight rungs: the full phrase costs twelve cells the row does
|
|
66
|
+
* not have at rung 3 and beyond, and dropping the fact entirely would be worse
|
|
67
|
+
* than abbreviating it.
|
|
68
|
+
*/
|
|
69
|
+
function pausedSuffix(full) {
|
|
70
|
+
return full ? ' auto-paused' : ' paused';
|
|
71
|
+
}
|
|
56
72
|
export function shortDuration(ms) {
|
|
57
73
|
if (!Number.isFinite(ms) || ms <= 0)
|
|
58
74
|
return '0s';
|
|
@@ -204,11 +220,21 @@ export function planPrimaryStatusFields(input) {
|
|
|
204
220
|
const label = (notification ? undefined : feedbackLabel(input, form))
|
|
205
221
|
?? (full ? labels[1] : rung >= 4 ? labels[0].slice(0, 4) : labels[0]);
|
|
206
222
|
const modeText = modeFieldText(input.mode, input.pendingMode, full);
|
|
223
|
+
// IMMEDIATELY AFTER `mode`, and kept at EVERY rung: both describe a state
|
|
224
|
+
// the USER armed, so the row's emphasis budget goes on them and nothing
|
|
225
|
+
// else (`accent`). It shortens to `T4` instead of disappearing because a
|
|
226
|
+
// chip that vanishes on a narrow terminal is indistinguishable from a
|
|
227
|
+
// disarmed session - the one question it exists to answer.
|
|
228
|
+
const forcedText = input.forcedTeam
|
|
229
|
+
? (rung >= 3 ? `T${input.forcedTeam.count}` : `Team ${input.forcedTeam.count}`)
|
|
230
|
+
: null;
|
|
207
231
|
return statusLinePlan([
|
|
208
232
|
statusField('phase', glyph + (rung >= 5 ? '' : ' ') + label, phaseTone),
|
|
209
233
|
...(notice ? [statusField('feedback', notice, feedbackTone)] : []),
|
|
210
234
|
...(modeText ? [statusField('mode', modeText, 'accent')] : []),
|
|
211
|
-
|
|
235
|
+
...(forcedText ? [statusField('forcedTeam', forcedText, 'accent')] : []),
|
|
236
|
+
statusField('context', (full ? 'Context ' : 'C') + pct
|
|
237
|
+
+ (input.compactionPaused === true ? pausedSuffix(full) : '')),
|
|
212
238
|
statusField('thinking', full ? 'Think ' + (thinkingCodes[input.thinkingLevel] ? input.thinkingLevel : '?')
|
|
213
239
|
: (rung >= 2 ? '' : 'Th:') + code),
|
|
214
240
|
statusField('speed', speed + (full ? ' tok/s' : rung >= 3 ? '/s' : 't/s')),
|
|
@@ -121,7 +121,7 @@ const COMMANDS = [
|
|
|
121
121
|
['/theme <name>', 'auto | warm | cool | light'],
|
|
122
122
|
['/thinking <level>', 'Set thinking level'],
|
|
123
123
|
['/max-tokens [n|auto]', 'Output token cap; no argument reports the effective one'],
|
|
124
|
-
['/team [on|off
|
|
124
|
+
['/team [on|off|<brief>]', 'Force team mode and start now: /team <brief> fans out'],
|
|
125
125
|
['/fast [on|off|model|review]', 'Fast model tier: status, switch, model, review cadence'],
|
|
126
126
|
['/kit [list|use <name>]', 'Agent kits: what is in effect, list them, use one for this session'],
|
|
127
127
|
[
|
|
@@ -179,12 +179,16 @@ const PLAN_ROWS = [
|
|
|
179
179
|
];
|
|
180
180
|
const TEAM_ROWS = [
|
|
181
181
|
['task', 'The tool the agent uses to run 2-5 subagents in parallel'],
|
|
182
|
-
['/team', '
|
|
183
|
-
['/team on | off', '
|
|
184
|
-
['/team
|
|
182
|
+
['/team <brief>', 'Force it: arms forced mode and starts that work as a turn'],
|
|
183
|
+
['/team on | off', 'Arm it without starting, or leave it; the capability is untouched'],
|
|
184
|
+
['/team', 'Report the forced state, capacity, the model, and where to change it'],
|
|
185
|
+
['-n <n> / --parts a,b', 'Fix the fan-out, or name the split (narrowing is disclosed)'],
|
|
186
|
+
['--read-only', 'Every child refuses writes and shell, for research-only fan-outs'],
|
|
187
|
+
['Team 4 (T4)', 'The status-bar chip - only while the session is armed'],
|
|
185
188
|
['--team / --no-team', 'Start a session with team mode on or off (also ARAGON_TEAM=0)'],
|
|
186
189
|
['team_send / team_wait', 'How subagents message each other; 6 each, one every 15s'],
|
|
187
190
|
['Ctrl+B / /agents', 'The board: every child, its full facts, its 24-event timeline'],
|
|
191
|
+
['Settings > Team', 'Team mode, width (1-10, 8 by default), concurrency, supervisor'],
|
|
188
192
|
];
|
|
189
193
|
const FAST_ROWS = [
|
|
190
194
|
['/fast', 'Status: on/off, the model, the cadence, and this session\'s totals'],
|
|
@@ -132,6 +132,20 @@ interface SettingsScreenProps {
|
|
|
132
132
|
* screen. Absent = omit the row entirely.
|
|
133
133
|
*/
|
|
134
134
|
retrySummary?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Whether `task` is in this SESSION's tool list, for the `Team` page's
|
|
137
|
+
* `on (next launch)` disclosure (team-command-forced-dispatch §3.9 / P1-5).
|
|
138
|
+
*
|
|
139
|
+
* WITHOUT THIS LINK THE DISCLOSURE CANNOT RENDER. The page is mounted with
|
|
140
|
+
* `{...props}`, so a prop the screen does not declare reaches it as `undefined`
|
|
141
|
+
* and `--no-team` users are silently told their saved `on` took effect. The
|
|
142
|
+
* value is a CONSTRUCTION-TIME snapshot (`--no-team` cannot be undone), which is
|
|
143
|
+
* why reading it every frame does not conflict with I-7.
|
|
144
|
+
*
|
|
145
|
+
* OPTIONAL, like `probe` / `retrySummary` above: every fixture builds this by
|
|
146
|
+
* literal, and absent honestly means "unknown, so do not disclose".
|
|
147
|
+
*/
|
|
148
|
+
teamRegistered?: boolean;
|
|
135
149
|
}
|
|
136
150
|
/**
|
|
137
151
|
* The derived, non-editable line under the five fast rows — the sibling of
|