@aragon-agent/cli 0.6.7 → 0.6.8
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/README.md +72 -43
- package/dist/agent/controller.d.ts +10 -6
- package/dist/agent/controller.js +35 -48
- package/dist/agent/headless.d.ts +2 -1
- package/dist/agent/prompt-options.d.ts +15 -0
- package/dist/agent/prompt-options.js +1 -0
- package/dist/cli.js +83 -53
- package/dist/commands/builtins.js +3 -1
- package/dist/commands/registry.d.ts +2 -3
- package/dist/input/interrupt-gesture.d.ts +18 -0
- package/dist/input/interrupt-gesture.js +19 -0
- package/dist/input/pointer-router.d.ts +11 -0
- package/dist/input/pointer-router.js +14 -0
- package/dist/todo/store.d.ts +8 -13
- package/dist/todo/store.js +13 -14
- package/dist/todo/types.d.ts +2 -1
- package/dist/ui/App.d.ts +2 -0
- package/dist/ui/App.js +166 -128
- package/dist/ui/Composer.d.ts +11 -5
- package/dist/ui/Composer.js +7 -11
- package/dist/ui/PromptCaret.d.ts +11 -0
- package/dist/ui/PromptCaret.js +31 -0
- package/dist/ui/PromptInput.d.ts +9 -6
- package/dist/ui/PromptInput.js +34 -14
- package/dist/ui/StatusBar.d.ts +1 -0
- package/dist/ui/StatusBar.js +14 -8
- package/dist/ui/Transcript.js +1 -0
- package/dist/ui/composer-rows.js +50 -9
- package/dist/ui/frame-differ.js +2 -19
- package/dist/ui/frame-observer.d.ts +17 -0
- package/dist/ui/frame-observer.js +81 -0
- package/dist/ui/frame-parser.d.ts +7 -0
- package/dist/ui/frame-parser.js +18 -0
- package/dist/ui/layout/AppShell.d.ts +7 -52
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/ScrollIndicator.d.ts +4 -3
- package/dist/ui/layout/ScrollIndicator.js +4 -4
- package/dist/ui/layout/ScrollViewport.d.ts +13 -66
- package/dist/ui/layout/ScrollViewport.js +183 -185
- package/dist/ui/layout/budget.d.ts +3 -51
- package/dist/ui/layout/budget.js +4 -53
- package/dist/ui/layout/document-layout.d.ts +13 -0
- package/dist/ui/layout/document-layout.js +11 -0
- package/dist/ui/layout/follow-state.d.ts +1 -0
- package/dist/ui/layout/follow-state.js +1 -3
- package/dist/ui/layout/scroll-indicator.d.ts +1 -1
- package/dist/ui/layout/scroll-indicator.js +3 -2
- package/dist/ui/layout/scrollbar-geometry.d.ts +17 -0
- package/dist/ui/layout/scrollbar-geometry.js +13 -0
- package/dist/ui/layout/todo-layout.d.ts +1 -0
- package/dist/ui/layout/todo-layout.js +3 -2
- package/dist/ui/layout/viewport-geometry.d.ts +1 -0
- package/dist/ui/layout/viewport-geometry.js +1 -0
- package/dist/ui/layout/virtual-window.d.ts +1 -0
- package/dist/ui/layout/virtual-window.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +5 -2
- package/dist/ui/screen.d.ts +1 -1
- package/dist/ui/screen.js +4 -6
- package/dist/ui/scrollbar-controller.d.ts +30 -0
- package/dist/ui/scrollbar-controller.js +124 -0
- package/dist/ui/use-wheel-routing.d.ts +2 -0
- package/dist/ui/use-wheel-routing.js +6 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -45,6 +45,9 @@ import { createFrameDiffer } from './ui/frame-differ.js';
|
|
|
45
45
|
import { wrapStdoutForFrames } from './ui/stdout-frame-writer.js';
|
|
46
46
|
import { setFrameStatsProvider } from './commands/perf.js';
|
|
47
47
|
import { tryCreateStdinFilter } from './input/stdin-filter.js';
|
|
48
|
+
import { createScrollbarBridge } from './ui/scrollbar-controller.js';
|
|
49
|
+
import { createPointerRouter } from './input/pointer-router.js';
|
|
50
|
+
import { createFrameObserver } from './ui/frame-observer.js';
|
|
48
51
|
import { enterAltScreen, writeExitTranscript } from './ui/screen.js';
|
|
49
52
|
import { supportsWindowsVtInput } from './ui/win-vt-input.js';
|
|
50
53
|
import { forceWindowsVtInput } from './ui/win-vt-force.js';
|
|
@@ -476,13 +479,14 @@ function runInteractive(flags, extras = {}) {
|
|
|
476
479
|
const disposeStdinFilter = () => filter?.dispose();
|
|
477
480
|
// --- Drag-select (tui-selection-and-scroll-follow §4.4). ------------------
|
|
478
481
|
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
// controller, the differ's `decorate` hook and the startup notice must never
|
|
482
|
-
// disagree about whether drag-select is running. With it false the session is
|
|
483
|
-
// byte-identical to a pre-feature build — no extra escape on the wire (AC-8),
|
|
484
|
-
// no controller, no `decorate`, no mirror.
|
|
482
|
+
// Selection owns its controller, decoration and notice. Button motion follows
|
|
483
|
+
// mouseOn independently so --no-mouse-select still permits scrollbar dragging.
|
|
485
484
|
const wantSelect = mouseOn && config.mouseSelect;
|
|
485
|
+
let mouseCaptured = mouseOn;
|
|
486
|
+
const scrollbar = createScrollbarBridge(() => mouseCaptured);
|
|
487
|
+
const pointerRouter = mouseOn && filter ? createPointerRouter({
|
|
488
|
+
source: filter.source, handle: (event) => scrollbar.controller?.handle(event) ?? false,
|
|
489
|
+
}) : null;
|
|
486
490
|
const selectionBridge = {
|
|
487
491
|
controller: null,
|
|
488
492
|
onCopied: null,
|
|
@@ -535,7 +539,13 @@ function runInteractive(flags, extras = {}) {
|
|
|
535
539
|
// than printing zeroes that read like a writer doing nothing (§5.4).
|
|
536
540
|
if (frameWriter)
|
|
537
541
|
setFrameStatsProvider(frameWriter.stats);
|
|
542
|
+
const frameObserver = mode === 'fullscreen' ? createFrameObserver({
|
|
543
|
+
stdout: frameWriter?.stdout ?? process.stdout, terminal: process.stdout, scrollbar,
|
|
544
|
+
}) : null;
|
|
538
545
|
const disposeFrameWriter = () => {
|
|
546
|
+
frameObserver?.dispose();
|
|
547
|
+
pointerRouter?.dispose();
|
|
548
|
+
scrollbar.controller?.dispose();
|
|
539
549
|
if (!frameWriter)
|
|
540
550
|
return;
|
|
541
551
|
setFrameStatsProvider(null);
|
|
@@ -574,7 +584,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
574
584
|
// keep their terminal's own selection.
|
|
575
585
|
screen = enterAltScreen(process.stdout, {
|
|
576
586
|
mouse: mouseOn,
|
|
577
|
-
motion:
|
|
587
|
+
motion: mouseOn,
|
|
578
588
|
bracketedPaste: pasteOn && mode === 'fullscreen',
|
|
579
589
|
});
|
|
580
590
|
const restore = () => screen?.restore();
|
|
@@ -637,7 +647,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
637
647
|
// attempted. Naming the mechanism honestly is the entire reason that return
|
|
638
648
|
// value exists (R-7), and a door that lies is worse than no door.
|
|
639
649
|
const writeForeign = frameWriter
|
|
640
|
-
? (text) => frameWriter.writeForeign(text)
|
|
650
|
+
? (text) => { frameObserver?.invalidate(); frameWriter.writeForeign(text); }
|
|
641
651
|
: process.stdout.isTTY
|
|
642
652
|
? (text) => {
|
|
643
653
|
try {
|
|
@@ -659,10 +669,9 @@ function runInteractive(flags, extras = {}) {
|
|
|
659
669
|
// than inside `screen.ts`'s closure because `App` needs to READ it and the
|
|
660
670
|
// handle's own flag is private (it has to be — `restore()` reads it, and
|
|
661
671
|
// exposing a setter would be a second way to get the two out of step).
|
|
662
|
-
let mouseCaptured = mouseOn;
|
|
663
672
|
if (wantSelect && filter) {
|
|
664
673
|
selectionBridge.controller = createSelectionController({
|
|
665
|
-
source: filter.source,
|
|
674
|
+
source: pointerRouter?.selectionSource ?? filter.source,
|
|
666
675
|
// The frame writer is absent under `--no-diff-render`, where there is no
|
|
667
676
|
// cache to repaint from at all; the controller then falls back to asking
|
|
668
677
|
// `App` for a React redraw on every drag flush, which is slower and
|
|
@@ -702,11 +711,14 @@ function runInteractive(flags, extras = {}) {
|
|
|
702
711
|
const terminalBridge = mode === 'fullscreen' || writeForeign
|
|
703
712
|
? {
|
|
704
713
|
mouseSelect: wantSelect,
|
|
714
|
+
...(mode === 'fullscreen' ? { scrollbar } : {}),
|
|
705
715
|
...(mouseOn
|
|
706
716
|
? {
|
|
707
717
|
setMouseCapture: (on) => {
|
|
708
718
|
screen?.setMouseCapture(on);
|
|
709
719
|
mouseCaptured = on;
|
|
720
|
+
if (!on)
|
|
721
|
+
scrollbar.controller?.cancel();
|
|
710
722
|
// A released mouse cannot be dragging, and a selection left
|
|
711
723
|
// painted over a screen the terminal is now selecting on its
|
|
712
724
|
// own would be two highlights claiming the same rows.
|
|
@@ -724,41 +736,67 @@ function runInteractive(flags, extras = {}) {
|
|
|
724
736
|
: undefined;
|
|
725
737
|
const updateBridge = { service: null, onAttach: null };
|
|
726
738
|
let updateService = null;
|
|
739
|
+
let interactiveClosed = false;
|
|
740
|
+
const failInteractive = (error) => {
|
|
741
|
+
// Ink consumes render exceptions; retain failure even if a disposer throws.
|
|
742
|
+
process.exitCode = 1;
|
|
743
|
+
interactiveClosed = true;
|
|
744
|
+
logger.error('cli', 'render_failed', { error: String(error) });
|
|
745
|
+
for (const cleanup of [
|
|
746
|
+
() => screen?.restore(), disposeSelection, disposeStdinFilter, disposeFrameWriter,
|
|
747
|
+
() => controller.abort(), () => controller.dispose(), () => updateService?.dispose(),
|
|
748
|
+
() => { process.stdin.pause(); process.stdin.unref?.(); },
|
|
749
|
+
]) {
|
|
750
|
+
try {
|
|
751
|
+
cleanup();
|
|
752
|
+
}
|
|
753
|
+
catch (cleanupError) {
|
|
754
|
+
logger.error('cli', 'render_cleanup_failed', { error: String(cleanupError) });
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
};
|
|
727
758
|
const updateEligible = !!process.stdout.isTTY &&
|
|
728
759
|
!!process.stdin.isTTY &&
|
|
729
760
|
!process.env.CI &&
|
|
730
761
|
config.update.mode !== 'off';
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
+
let instance;
|
|
763
|
+
try {
|
|
764
|
+
instance = render(_jsx(App, { controller: controller, version: VERSION, mode: mode, confirmBridge: confirmBridge, humanInputBridge: humanInputBridge, initialPrompt: extras.initialPrompt, initialOverlay: extras.initialOverlay,
|
|
765
|
+
// `mouseOn`, NOT the handle (I-11). `App.tsx:186-188` documents this
|
|
766
|
+
// prop's contract as "absent under `--no-mouse`, inline, or non-TTY", and
|
|
767
|
+
// it feeds `enabled: !!mouseSource` on a wheel subscription that would
|
|
768
|
+
// otherwise be a channel no event can ever arrive on.
|
|
769
|
+
mouseSource: mouseOn && filter ? filter.source : undefined,
|
|
770
|
+
// Present ONLY when this console swallows `CSI Z` and mouse reports.
|
|
771
|
+
// Passed in rather than probed inside `App` so that every test renders
|
|
772
|
+
// the same tree on every machine — reading `process.versions.node` from
|
|
773
|
+
// a component would make the notice appear or vanish according to which
|
|
774
|
+
// Node the developer happened to have installed.
|
|
775
|
+
vtInputWarning: vtInputSupported ? undefined : { nodeVersion: process.versions.node }, updateBridge: updateEligible ? updateBridge : undefined,
|
|
776
|
+
// Absent when nothing can refuse a paste, so a `--no-paste` tree is
|
|
777
|
+
// byte-identical to a build without this feature.
|
|
778
|
+
pasteBridge: pasteOn ? pasteBridge : undefined, terminal: terminalBridge }),
|
|
779
|
+
// Full-screen owns console.* itself (I-4): Ink's patchConsole writes
|
|
780
|
+
// straight to stdout and permanently shifts the fixed frame's accounting.
|
|
781
|
+
{
|
|
782
|
+
exitOnCtrlC: false,
|
|
783
|
+
patchConsole: mode === 'inline',
|
|
784
|
+
// I-5: with mouse support off the REAL stdin is handed over unwrapped, so
|
|
785
|
+
// every existing path is provably unchanged.
|
|
786
|
+
stdin: filter?.stdin ?? process.stdin,
|
|
787
|
+
// SPREAD, not `frameWriter?.stdout ?? process.stdout`. This call passed no
|
|
788
|
+
// `stdout` at all before, and Ink defaults it to `process.stdout`; omitting
|
|
789
|
+
// the key entirely when the writer is off keeps the options object the same
|
|
790
|
+
// SHAPE it has always had, which is what makes AC-5 / AC-6 ("byte-identical
|
|
791
|
+
// with --no-diff-render, inline mode and `aragon -p`") a statement about
|
|
792
|
+
// the code rather than about Ink's defaulting.
|
|
793
|
+
...(frameObserver ? { stdout: frameObserver.stdout } : {}),
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
failInteractive(error);
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
762
800
|
// H1's primary disarm, and this is the earliest moment at which "this build
|
|
763
801
|
// STARTS" is proven: the module graph loaded, config resolved, Ink mounted
|
|
764
802
|
// (cli-auto-update-hardening section 5.1.4). It is what makes a long session
|
|
@@ -780,6 +818,8 @@ function runInteractive(flags, extras = {}) {
|
|
|
780
818
|
// must degrade to "no updater", never to a crashed TUI.
|
|
781
819
|
void import('./update/service.js')
|
|
782
820
|
.then(({ UpdateService }) => {
|
|
821
|
+
if (interactiveClosed)
|
|
822
|
+
return;
|
|
783
823
|
const service = new UpdateService({ config: config.update, currentVersion: VERSION });
|
|
784
824
|
updateService = service;
|
|
785
825
|
updateBridge.service = service;
|
|
@@ -810,6 +850,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
810
850
|
void instance
|
|
811
851
|
.waitUntilExit()
|
|
812
852
|
.then(() => {
|
|
853
|
+
interactiveClosed = true;
|
|
813
854
|
screen?.restore();
|
|
814
855
|
// BEFORE `disposeStdinFilter()`, which clears the listener set it subscribed to,
|
|
815
856
|
// and after `restore()` for the reason that call records: the terminal must
|
|
@@ -838,18 +879,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
838
879
|
updateService?.dispose();
|
|
839
880
|
replayTranscript();
|
|
840
881
|
})
|
|
841
|
-
.catch(
|
|
842
|
-
screen?.restore();
|
|
843
|
-
// BEFORE `disposeStdinFilter()`, which clears the listener set it subscribed to,
|
|
844
|
-
// and after `restore()` for the reason that call records: the terminal must
|
|
845
|
-
// stop reporting first, and anything already in flight is still consumed by
|
|
846
|
-
// a live filter.
|
|
847
|
-
disposeSelection();
|
|
848
|
-
disposeStdinFilter();
|
|
849
|
-
disposeFrameWriter();
|
|
850
|
-
controller.dispose();
|
|
851
|
-
updateService?.dispose();
|
|
852
|
-
});
|
|
882
|
+
.catch(failInteractive);
|
|
853
883
|
}
|
|
854
884
|
async function runOneShot(flags, prompt, quiet) {
|
|
855
885
|
// Headless: no App is ever rendered, so there is no human to approve a skill
|
|
@@ -724,7 +724,9 @@ const COMMANDS = [
|
|
|
724
724
|
// slash history already has. Putting this canned paragraph under the
|
|
725
725
|
// composer's up-arrow as well would be the same event recorded twice, in
|
|
726
726
|
// the more verbose of the two forms (AC-17).
|
|
727
|
-
ctx.submit(buildContinuationMessage(snapshot), {
|
|
727
|
+
ctx.submit(buildContinuationMessage(snapshot), {
|
|
728
|
+
userInitiated: false, todoPolicy: 'continue',
|
|
729
|
+
});
|
|
728
730
|
return;
|
|
729
731
|
}
|
|
730
732
|
ctx.notify('warn', `Unknown argument "${verb}" - use ` +
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Slash-command registry + dispatcher (spec §5.2). A leading `/` in the prompt
|
|
3
3
|
* input is routed here instead of to the agent; `//` is the literal-`/` escape.
|
|
4
4
|
*/
|
|
5
|
+
import type { SubmitMessageOptions } from '../agent/prompt-options.js';
|
|
5
6
|
import type { AgentController } from '../agent/controller.js';
|
|
6
7
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
7
8
|
import type { NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
|
|
@@ -38,9 +39,7 @@ export interface CommandContext {
|
|
|
38
39
|
* (P1-5): `builtins.ts` reaches `submitMessage` only through here, so
|
|
39
40
|
* `/todo continue` cannot opt out of prompt history without it.
|
|
40
41
|
*/
|
|
41
|
-
submit: (text: string, opts?:
|
|
42
|
-
userInitiated?: boolean;
|
|
43
|
-
}) => void;
|
|
42
|
+
submit: (text: string, opts?: SubmitMessageOptions) => void;
|
|
44
43
|
/**
|
|
45
44
|
* The live auto-continue budget, for `/todo status`'s `(2/25 used)` clause
|
|
46
45
|
* (todo-plan-followthrough §4.3).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Confirmation window for two independent decoded Escape events. */
|
|
2
|
+
export declare const INTERRUPT_CONFIRM_MS = 1500;
|
|
3
|
+
export type InterruptGestureState = {
|
|
4
|
+
phase: 'ready';
|
|
5
|
+
} | {
|
|
6
|
+
phase: 'armed';
|
|
7
|
+
armedAt: number;
|
|
8
|
+
} | {
|
|
9
|
+
phase: 'stopping';
|
|
10
|
+
};
|
|
11
|
+
export interface InterruptGestureResult {
|
|
12
|
+
state: InterruptGestureState;
|
|
13
|
+
action: 'hint' | 'abort' | 'force-stop';
|
|
14
|
+
}
|
|
15
|
+
/** Advance using a monotonic clock; the caller owns the current run's lifetime. */
|
|
16
|
+
export declare function advanceInterruptGesture(state: InterruptGestureState, now: number): InterruptGestureResult;
|
|
17
|
+
/** Other input cancels confirmation, but cannot undo an already requested stop. */
|
|
18
|
+
export declare function cancelInterruptConfirmation(state: InterruptGestureState): InterruptGestureState;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Confirmation window for two independent decoded Escape events. */
|
|
2
|
+
export const INTERRUPT_CONFIRM_MS = 1500;
|
|
3
|
+
/** Advance using a monotonic clock; the caller owns the current run's lifetime. */
|
|
4
|
+
export function advanceInterruptGesture(state, now) {
|
|
5
|
+
if (state.phase === 'stopping') {
|
|
6
|
+
return { state: { phase: 'ready' }, action: 'force-stop' };
|
|
7
|
+
}
|
|
8
|
+
if (state.phase === 'armed') {
|
|
9
|
+
const elapsed = now - state.armedAt;
|
|
10
|
+
if (elapsed >= 0 && elapsed <= INTERRUPT_CONFIRM_MS) {
|
|
11
|
+
return { state: { phase: 'stopping' }, action: 'abort' };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return { state: { phase: 'armed', armedAt: now }, action: 'hint' };
|
|
15
|
+
}
|
|
16
|
+
/** Other input cancels confirmation, but cannot undo an already requested stop. */
|
|
17
|
+
export function cancelInterruptConfirmation(state) {
|
|
18
|
+
return state.phase === 'armed' ? { phase: 'ready' } : state;
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MouseEvent } from './mouse-events.js';
|
|
2
|
+
import type { MouseSource } from './stdin-filter.js';
|
|
3
|
+
export interface PointerRouter {
|
|
4
|
+
selectionSource: MouseSource;
|
|
5
|
+
dispose(): void;
|
|
6
|
+
}
|
|
7
|
+
/** Deliver each button gesture to the scrollbar before the selection source. */
|
|
8
|
+
export declare function createPointerRouter(options: {
|
|
9
|
+
source: MouseSource;
|
|
10
|
+
handle(event: MouseEvent): boolean;
|
|
11
|
+
}): PointerRouter;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Deliver each button gesture to the scrollbar before the selection source. */
|
|
2
|
+
export function createPointerRouter(options) {
|
|
3
|
+
const listeners = new Set();
|
|
4
|
+
const unsubscribe = options.source.subscribe((event) => {
|
|
5
|
+
if (event.kind !== 'wheel' && options.handle(event))
|
|
6
|
+
return;
|
|
7
|
+
for (const listener of listeners)
|
|
8
|
+
listener(event);
|
|
9
|
+
});
|
|
10
|
+
return { selectionSource: { subscribe: (listener) => {
|
|
11
|
+
listeners.add(listener);
|
|
12
|
+
return () => { listeners.delete(listener); };
|
|
13
|
+
} }, dispose: () => { unsubscribe(); listeners.clear(); } };
|
|
14
|
+
}
|
package/dist/todo/store.d.ts
CHANGED
|
@@ -9,15 +9,9 @@
|
|
|
9
9
|
* is why `AgentController` can own this object as unconditionally as it owns
|
|
10
10
|
* `TeamRuntime`.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `TodoClearReason = 'user'`. Two commands come through that door: `/todo clear`
|
|
16
|
-
* and `/clear`. Neither tells the model anything, and that is fine, because what
|
|
17
|
-
* they leave behind is "panel absent, model still remembers" - the direction I-9
|
|
18
|
-
* below calls safe, and one the model's next full-replacement write heals on its
|
|
19
|
-
* own. `/reset` (reason `'reset'`) remains the only clear caused by the belief
|
|
20
|
-
* itself being destroyed, because it clears `messages`.
|
|
12
|
+
* The list projects the current plan. Explicit clear/reset and a new TUI task
|
|
13
|
+
* may remove that projection while the model still remembers the old plan.
|
|
14
|
+
* Continuations preserve it; legacy callers retain completion/staleness rules.
|
|
21
15
|
*
|
|
22
16
|
* Every write is a FULL REPLACEMENT and every clear is a FULL REMOVAL - there is
|
|
23
17
|
* no partial update anywhere, which is what makes I-9 ("the panel is never a
|
|
@@ -73,11 +67,12 @@ export declare class TodoStore {
|
|
|
73
67
|
* asymmetry for free, which is the behaviour you want: a steer must not age
|
|
74
68
|
* out the plan it is steering.
|
|
75
69
|
*
|
|
76
|
-
* A
|
|
77
|
-
*
|
|
78
|
-
*
|
|
70
|
+
* A new TUI task drops the old plan here, while a continuation preserves it.
|
|
71
|
+
* With no policy, legacy callers retain completed/stale-plan expiration.
|
|
72
|
+
* Clearing at `agent_end` would erase the final progress before the user can
|
|
73
|
+
* inspect it and remove the plan that automatic continuation still needs.
|
|
79
74
|
*/
|
|
80
|
-
beginUserTurn(): void;
|
|
75
|
+
beginUserTurn(policy?: 'new-task' | 'continue'): void;
|
|
81
76
|
/**
|
|
82
77
|
* Report a refusal that never reached `write()` (§3.3 steps 1 and 2).
|
|
83
78
|
*
|
package/dist/todo/store.js
CHANGED
|
@@ -9,15 +9,9 @@
|
|
|
9
9
|
* is why `AgentController` can own this object as unconditionally as it owns
|
|
10
10
|
* `TeamRuntime`.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `TodoClearReason = 'user'`. Two commands come through that door: `/todo clear`
|
|
16
|
-
* and `/clear`. Neither tells the model anything, and that is fine, because what
|
|
17
|
-
* they leave behind is "panel absent, model still remembers" - the direction I-9
|
|
18
|
-
* below calls safe, and one the model's next full-replacement write heals on its
|
|
19
|
-
* own. `/reset` (reason `'reset'`) remains the only clear caused by the belief
|
|
20
|
-
* itself being destroyed, because it clears `messages`.
|
|
12
|
+
* The list projects the current plan. Explicit clear/reset and a new TUI task
|
|
13
|
+
* may remove that projection while the model still remembers the old plan.
|
|
14
|
+
* Continuations preserve it; legacy callers retain completion/staleness rules.
|
|
21
15
|
*
|
|
22
16
|
* Every write is a FULL REPLACEMENT and every clear is a FULL REMOVAL - there is
|
|
23
17
|
* no partial update anywhere, which is what makes I-9 ("the panel is never a
|
|
@@ -137,13 +131,18 @@ export class TodoStore {
|
|
|
137
131
|
* asymmetry for free, which is the behaviour you want: a steer must not age
|
|
138
132
|
* out the plan it is steering.
|
|
139
133
|
*
|
|
140
|
-
* A
|
|
141
|
-
*
|
|
142
|
-
*
|
|
134
|
+
* A new TUI task drops the old plan here, while a continuation preserves it.
|
|
135
|
+
* With no policy, legacy callers retain completed/stale-plan expiration.
|
|
136
|
+
* Clearing at `agent_end` would erase the final progress before the user can
|
|
137
|
+
* inspect it and remove the plan that automatic continuation still needs.
|
|
143
138
|
*/
|
|
144
|
-
beginUserTurn() {
|
|
145
|
-
if (this.items.length === 0)
|
|
139
|
+
beginUserTurn(policy) {
|
|
140
|
+
if (this.items.length === 0 || policy === 'continue')
|
|
141
|
+
return;
|
|
142
|
+
if (policy === 'new-task') {
|
|
143
|
+
this.clear('turn');
|
|
146
144
|
return;
|
|
145
|
+
}
|
|
147
146
|
if (this.items.every((item) => item.status === 'completed')) {
|
|
148
147
|
this.clear('turn');
|
|
149
148
|
return;
|
package/dist/todo/types.d.ts
CHANGED
|
@@ -45,7 +45,8 @@ export interface TodoSnapshot {
|
|
|
45
45
|
* `user` - `/todo clear`, or `/clear`. Both are an explicit user instruction,
|
|
46
46
|
* which is I-2's standing exception; neither touches `messages`.
|
|
47
47
|
* `reset` - `/reset`, or a `/resume` whose file carried no list (§3.13).
|
|
48
|
-
* `turn` -
|
|
48
|
+
* `turn` - a new TUI task started, or a legacy caller began a turn after
|
|
49
|
+
* every item was completed.
|
|
49
50
|
* `stale` - unfinished, and unwritten for more than `TODO_LIMITS.staleTurns`
|
|
50
51
|
* consecutive turns (§3.2 / P1-7).
|
|
51
52
|
*/
|
package/dist/ui/App.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* input / toast stack / status bar / overlays.
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react';
|
|
8
|
+
import type { ScrollbarBridge } from './scrollbar-controller.js';
|
|
8
9
|
import type { AgentController } from '../agent/controller.js';
|
|
9
10
|
import type { ConfirmRequest } from '../tools/index.js';
|
|
10
11
|
import { type Overlay, type ToastLevel } from '../agent/reducer.js';
|
|
@@ -109,6 +110,7 @@ export interface AppProps {
|
|
|
109
110
|
* mounted. The effect below assigns both and clears them on unmount.
|
|
110
111
|
*/
|
|
111
112
|
export interface TerminalBridge {
|
|
113
|
+
scrollbar?: ScrollbarBridge;
|
|
112
114
|
/** Whether drag-select is on for this session (`mouseSelect` + a filter). */
|
|
113
115
|
mouseSelect: boolean;
|
|
114
116
|
/** Release the mouse to the terminal, or take it back (`/mouse`, G3). */
|