@aragon-agent/cli 0.6.29 → 0.6.32
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 +2223 -2103
- package/README.md +2957 -2759
- package/dist/agent/controller.d.ts +46 -0
- package/dist/agent/controller.js +175 -7
- package/dist/agent/job-notices.d.ts +143 -0
- package/dist/agent/job-notices.js +182 -0
- package/dist/agent/project-guidance-prompt.js +20 -20
- package/dist/agent/reducer.d.ts +21 -1
- package/dist/agent/reducer.js +7 -0
- package/dist/agent/system-prompt.d.ts +12 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +399 -62
- package/dist/commands/builtins.js +27 -8
- package/dist/commands/perf.d.ts +79 -2
- package/dist/commands/perf.js +51 -3
- package/dist/commands/registry.d.ts +6 -0
- package/dist/config/cli-commands.d.ts +27 -0
- package/dist/config/cli-commands.js +54 -1
- package/dist/config/env.js +42 -0
- package/dist/config/load.d.ts +3 -1
- package/dist/config/load.js +61 -1
- package/dist/config/schema.d.ts +100 -0
- package/dist/config/schema.js +43 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +9 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +29 -3
- package/dist/exec/events.js +3 -12
- package/dist/exec/runner.d.ts +24 -0
- package/dist/exec/runner.js +79 -0
- package/dist/input/cursor-report.d.ts +61 -0
- package/dist/input/cursor-report.js +97 -0
- package/dist/input/stdin-filter.d.ts +22 -0
- package/dist/input/stdin-filter.js +48 -3
- package/dist/proc/limits.d.ts +76 -2
- package/dist/proc/limits.js +83 -2
- package/dist/proc/log-mirror.d.ts +59 -0
- package/dist/proc/log-mirror.js +203 -0
- package/dist/proc/log-ring.d.ts +22 -0
- package/dist/proc/log-ring.js +20 -1
- package/dist/proc/notices.d.ts +57 -0
- package/dist/proc/notices.js +101 -0
- package/dist/proc/progress.d.ts +40 -0
- package/dist/proc/progress.js +135 -0
- package/dist/proc/prompt.d.ts +24 -2
- package/dist/proc/prompt.js +51 -2
- package/dist/proc/supervisor.d.ts +35 -2
- package/dist/proc/supervisor.js +142 -43
- package/dist/proc/types.d.ts +89 -1
- package/dist/proc/wait.d.ts +57 -0
- package/dist/proc/wait.js +133 -0
- package/dist/session/persist.js +3 -1
- package/dist/session/validate-session.js +17 -0
- package/dist/tools/bash-tool.d.ts +12 -1
- package/dist/tools/bash-tool.js +100 -13
- package/dist/tools/index.d.ts +26 -2
- package/dist/tools/index.js +43 -2
- package/dist/tools/job-tools.d.ts +54 -0
- package/dist/tools/job-tools.js +329 -0
- package/dist/tools/proc-tools.d.ts +1 -1
- package/dist/tools/proc-tools.js +49 -17
- package/dist/ui/ActivityLine.js +7 -0
- package/dist/ui/App.d.ts +11 -1
- package/dist/ui/App.js +181 -63
- package/dist/ui/ScrollbackShell.d.ts +54 -0
- package/dist/ui/ScrollbackShell.js +15 -0
- package/dist/ui/StatusBar.d.ts +5 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/Transcript.d.ts +14 -0
- package/dist/ui/Transcript.js +14 -2
- package/dist/ui/entries/ServiceCard.d.ts +17 -1
- package/dist/ui/entries/ServiceCard.js +39 -4
- package/dist/ui/frame-differ.d.ts +12 -0
- package/dist/ui/layout/status-layout.d.ts +13 -1
- package/dist/ui/layout/status-layout.js +10 -0
- package/dist/ui/layout/virtual-window.js +14 -1
- package/dist/ui/overlays/HelpOverlay.d.ts +1 -1
- package/dist/ui/overlays/HelpOverlay.js +21 -3
- package/dist/ui/run-status-row.d.ts +12 -0
- package/dist/ui/screen.d.ts +29 -0
- package/dist/ui/screen.js +40 -0
- package/dist/ui/scrollback/LiveTail.d.ts +64 -0
- package/dist/ui/scrollback/LiveTail.js +27 -0
- package/dist/ui/scrollback/band.d.ts +105 -0
- package/dist/ui/scrollback/band.js +109 -0
- package/dist/ui/scrollback/budget.d.ts +72 -0
- package/dist/ui/scrollback/budget.js +81 -0
- package/dist/ui/scrollback/canvas.d.ts +99 -0
- package/dist/ui/scrollback/canvas.js +113 -0
- package/dist/ui/scrollback/cursor-probe.d.ts +71 -0
- package/dist/ui/scrollback/cursor-probe.js +133 -0
- package/dist/ui/scrollback/drain.d.ts +32 -0
- package/dist/ui/scrollback/drain.js +64 -0
- package/dist/ui/scrollback/exit-summary.d.ts +23 -0
- package/dist/ui/scrollback/exit-summary.js +25 -0
- package/dist/ui/scrollback/limits.d.ts +74 -0
- package/dist/ui/scrollback/limits.js +74 -0
- package/dist/ui/scrollback/live-preview.d.ts +26 -0
- package/dist/ui/scrollback/live-preview.js +106 -0
- package/dist/ui/scrollback/renderer.d.ts +38 -0
- package/dist/ui/scrollback/renderer.js +158 -0
- package/dist/ui/scrollback/scrollback-writer.d.ts +162 -0
- package/dist/ui/scrollback/scrollback-writer.js +549 -0
- package/dist/ui/scrollback/settled-boundary.d.ts +42 -0
- package/dist/ui/scrollback/settled-boundary.js +106 -0
- package/dist/ui/scrollback/use-scrollback-view.d.ts +103 -0
- package/dist/ui/scrollback/use-scrollback-view.js +294 -0
- package/dist/ui/scrollback/writer-shapes.d.ts +127 -0
- package/dist/ui/scrollback/writer-shapes.js +159 -0
- package/dist/ui/stdout-frame-writer.js +12 -2
- package/dist/ui/terminal-plan.d.ts +52 -0
- package/dist/ui/terminal-plan.js +49 -0
- package/dist/ui/transcript-text.d.ts +11 -0
- package/dist/ui/transcript-text.js +21 -7
- package/dist/ui/use-startup-notices.d.ts +12 -1
- package/dist/ui/use-startup-notices.js +28 -2
- package/package.json +3 -3
- package/skills/project-indexer/SKILL.md +163 -163
- package/skills/project-indexer/templates/cleancode-overrides.md +46 -46
- package/skills/project-indexer/templates/cleancode-template.md +94 -94
- package/skills/project-indexer/templates/config-template.md +69 -69
- package/skills/project-indexer/templates/index-template.md +129 -129
- package/skills/skill-creator/SKILL.md +96 -96
package/dist/config/schema.d.ts
CHANGED
|
@@ -39,6 +39,17 @@ export declare function isThemeName(v: unknown): v is ThemeName;
|
|
|
39
39
|
* and would not match `clampThinkingLevel` / `clampTranscriptWindow`.
|
|
40
40
|
*/
|
|
41
41
|
export declare function clampTheme(v: unknown, fallback: ThemeName): ThemeName;
|
|
42
|
+
export declare const SCROLL_MODES: readonly ["native", "app"];
|
|
43
|
+
/**
|
|
44
|
+
* How the interactive TUI scrolls (tui-native-terminal-scroll D1).
|
|
45
|
+
*
|
|
46
|
+
* `native` prints settled conversation into the host terminal's own scrollback and
|
|
47
|
+
* keeps only a bounded live region on screen; `app` is the original full-screen
|
|
48
|
+
* alternate-screen layout with application-owned scrolling.
|
|
49
|
+
*/
|
|
50
|
+
export type ScrollMode = (typeof SCROLL_MODES)[number];
|
|
51
|
+
export declare const DEFAULT_SCROLL_MODE: ScrollMode;
|
|
52
|
+
export declare function clampScrollMode(v: unknown, fallback: ScrollMode): ScrollMode;
|
|
42
53
|
export declare const DENSITY_MODES: readonly ["comfortable", "compact"];
|
|
43
54
|
export type DensityMode = (typeof DENSITY_MODES)[number];
|
|
44
55
|
export declare function clampDensity(v: unknown, fallback: DensityMode): DensityMode;
|
|
@@ -784,6 +795,62 @@ export declare const DEFAULT_BASH_CONFIG: BashConfig;
|
|
|
784
795
|
* A garbage `startupSettleMs` resolves to 4000 and throws nothing.
|
|
785
796
|
*/
|
|
786
797
|
export declare function clampBashConfig(raw: unknown): BashConfig;
|
|
798
|
+
export interface JobsConfig {
|
|
799
|
+
/**
|
|
800
|
+
* Register `job_wait` / `job_list`, splice the `<background_jobs>` block,
|
|
801
|
+
* mirror job logs. Default TRUE.
|
|
802
|
+
*
|
|
803
|
+
* READ ONCE AT CONSTRUCTION, the `bash.background` / `team.enabled` rule. Its
|
|
804
|
+
* job is to be the single switch under which the tool array, the tool
|
|
805
|
+
* descriptions and the system prompt are BYTE-IDENTICAL to the pre-feature
|
|
806
|
+
* build (I-3) - which is why it is a registration switch and not a behaviour
|
|
807
|
+
* flag.
|
|
808
|
+
*/
|
|
809
|
+
enabled: boolean;
|
|
810
|
+
/**
|
|
811
|
+
* Seconds one `job_wait` blocks by default. Default 30, range 5..60.
|
|
812
|
+
*
|
|
813
|
+
* ALSO CONVERGED AGAINST THE TOOL CEILING at load time and again at call time
|
|
814
|
+
* (I-4): a wait that outlives the tool call hosting it would turn every long
|
|
815
|
+
* job into `Command timed out`, which is the defect this feature removes.
|
|
816
|
+
*/
|
|
817
|
+
waitSeconds: number;
|
|
818
|
+
/**
|
|
819
|
+
* Seconds without output before a job is reported `[stalled]`. Default 300,
|
|
820
|
+
* range 30..3600.
|
|
821
|
+
*
|
|
822
|
+
* A REPORT, NEVER AN ACTION: nothing kills or retries on this (R-13). The
|
|
823
|
+
* default is deliberately generous, because a training epoch that prints every
|
|
824
|
+
* five minutes is healthy.
|
|
825
|
+
*/
|
|
826
|
+
stallSeconds: number;
|
|
827
|
+
/**
|
|
828
|
+
* Mirror each job's full output to `<home>/jobs/<session>/<id>.log`. Default
|
|
829
|
+
* TRUE; `false` creates no directory at all, for privacy- or IO-sensitive
|
|
830
|
+
* environments (D-6).
|
|
831
|
+
*/
|
|
832
|
+
persistLog: boolean;
|
|
833
|
+
/**
|
|
834
|
+
* Whether a completion notice may STEER the running turn. Default TRUE.
|
|
835
|
+
*
|
|
836
|
+
* NOT A NOTICE SWITCH (§6.5). With this off, notices still arrive at the end of
|
|
837
|
+
* the next tool result and before the next user turn; only the active
|
|
838
|
+
* interruption stops. Turning notifications off entirely would require running
|
|
839
|
+
* no jobs.
|
|
840
|
+
*/
|
|
841
|
+
noticeSteer: boolean;
|
|
842
|
+
}
|
|
843
|
+
export declare const DEFAULT_JOBS_CONFIG: JobsConfig;
|
|
844
|
+
/**
|
|
845
|
+
* THE single gate for every `jobs` read AND write, mirroring `clampBashConfig`
|
|
846
|
+
* and the four sections before it.
|
|
847
|
+
*
|
|
848
|
+
* THE ELEVENTH SECTION, AND THE FIRST ONE THAT `config set` CAN REACH BY
|
|
849
|
+
* NESTED KEY (§6.5). Every key is a SCALAR and the section is ONE level deep,
|
|
850
|
+
* because `runConfigGet` splits on a single dot - a `jobs.mirror.enabled` would
|
|
851
|
+
* resolve to `undefined` and the setting would silently never apply.
|
|
852
|
+
*/
|
|
853
|
+
export declare function clampJobsConfig(raw: unknown): JobsConfig;
|
|
787
854
|
/**
|
|
788
855
|
* Parse `0.9`, `90%` or `90` into a ratio, or `null` when it is neither.
|
|
789
856
|
*
|
|
@@ -864,6 +931,8 @@ export interface PersistedConfig {
|
|
|
864
931
|
maxRenderIntervalMs: number;
|
|
865
932
|
/** Repaint only the frame rows that changed (tui-input-flicker-fix §5.1). */
|
|
866
933
|
diffRender: boolean;
|
|
934
|
+
/** Native terminal scrolling vs the full-screen app layout (tui-native-terminal-scroll D1). */
|
|
935
|
+
scrollMode: ScrollMode;
|
|
867
936
|
/** Wrap each repaint batch in DEC 2026 synchronized update (§5.1). */
|
|
868
937
|
syncOutput: boolean;
|
|
869
938
|
confirmTools: boolean;
|
|
@@ -942,6 +1011,8 @@ export interface PersistedConfig {
|
|
|
942
1011
|
compaction: CompactionConfig;
|
|
943
1012
|
/** Background services. The TENTH nested object; scalars only (see BashConfig). */
|
|
944
1013
|
bash: BashConfig;
|
|
1014
|
+
/** Background jobs and bounded waiting. The ELEVENTH; scalars only (see JobsConfig). */
|
|
1015
|
+
jobs: JobsConfig;
|
|
945
1016
|
}
|
|
946
1017
|
export declare const DEFAULT_CONFIG: PersistedConfig;
|
|
947
1018
|
/**
|
|
@@ -1068,6 +1139,20 @@ export interface CliConfig {
|
|
|
1068
1139
|
* with no compile error and no runtime error.
|
|
1069
1140
|
*/
|
|
1070
1141
|
diffRender: boolean;
|
|
1142
|
+
/**
|
|
1143
|
+
* Native terminal scrolling vs the full-screen app layout
|
|
1144
|
+
* (tui-native-terminal-scroll D1).
|
|
1145
|
+
*
|
|
1146
|
+
* IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason
|
|
1147
|
+
* `diffRender` above records: `runInteractive` reads `config.scrollMode`, where
|
|
1148
|
+
* `config` is a `CliConfig`, so a key added to the persisted shape alone would
|
|
1149
|
+
* round-trip through `config set` and never reach the screen plan.
|
|
1150
|
+
*
|
|
1151
|
+
* OPTIONAL ON THIS SHAPE ONLY so the many hand-built `CliConfig` fixtures in the
|
|
1152
|
+
* test suite stay valid without edits (I-S9); `loadConfig` always sets it and
|
|
1153
|
+
* the one reader (`runInteractive`) falls back to `DEFAULT_SCROLL_MODE`.
|
|
1154
|
+
*/
|
|
1155
|
+
scrollMode?: ScrollMode;
|
|
1071
1156
|
/** Wrap each repaint batch in DEC 2026 synchronized update (§5.1). */
|
|
1072
1157
|
syncOutput: boolean;
|
|
1073
1158
|
confirmTools: boolean;
|
|
@@ -1235,6 +1320,21 @@ export interface CliConfig {
|
|
|
1235
1320
|
* either of them.
|
|
1236
1321
|
*/
|
|
1237
1322
|
bash: BashConfig;
|
|
1323
|
+
/**
|
|
1324
|
+
* Background jobs and bounded waiting (long-task-jobs §6.5). The ELEVENTH
|
|
1325
|
+
* nested object; five scalar keys, one level deep.
|
|
1326
|
+
*
|
|
1327
|
+
* ON `CliConfig` AS WELL AS `PersistedConfig`, for the reason every section
|
|
1328
|
+
* above records: `AgentController` reads `config.jobs.waitSeconds` through a
|
|
1329
|
+
* closure and `job_wait` reads it again at call time, so a key persisted and
|
|
1330
|
+
* never projected would round-trip through `config set`, show up in
|
|
1331
|
+
* `config list`, and change nothing.
|
|
1332
|
+
*
|
|
1333
|
+
* ONE LEVEL DEEP IS A HARD REQUIREMENT, NOT A PREFERENCE: `runConfigGet` splits
|
|
1334
|
+
* `jobs.waitSeconds` on a single dot, so a nested sub-object would resolve to
|
|
1335
|
+
* `undefined` and report an empty value for a setting that IS on disk.
|
|
1336
|
+
*/
|
|
1337
|
+
jobs: JobsConfig;
|
|
1238
1338
|
/**
|
|
1239
1339
|
* Lifetime submit count driving the composer hint fade (`HINT_FADE_AFTER`).
|
|
1240
1340
|
*
|
package/dist/config/schema.js
CHANGED
|
@@ -69,6 +69,14 @@ export function clampTheme(v, fallback) {
|
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
70
70
|
// Density / hints (v0.4.0)
|
|
71
71
|
// ---------------------------------------------------------------------------
|
|
72
|
+
export const SCROLL_MODES = ['native', 'app'];
|
|
73
|
+
export const DEFAULT_SCROLL_MODE = 'native';
|
|
74
|
+
export function clampScrollMode(v, fallback) {
|
|
75
|
+
if (typeof v !== 'string')
|
|
76
|
+
return fallback;
|
|
77
|
+
const normalized = v.trim().toLowerCase();
|
|
78
|
+
return normalized === 'native' || normalized === 'app' ? normalized : fallback;
|
|
79
|
+
}
|
|
72
80
|
export const DENSITY_MODES = ['comfortable', 'compact'];
|
|
73
81
|
export function clampDensity(v, fallback) {
|
|
74
82
|
return v === 'comfortable' || v === 'compact' ? v : fallback;
|
|
@@ -922,6 +930,34 @@ export function clampBashConfig(raw) {
|
|
|
922
930
|
readyTimeoutMs: clampInt(src.readyTimeoutMs, DEFAULT_BASH_CONFIG.readyTimeoutMs, READY_TIMEOUT_RANGE),
|
|
923
931
|
};
|
|
924
932
|
}
|
|
933
|
+
export const DEFAULT_JOBS_CONFIG = {
|
|
934
|
+
enabled: true,
|
|
935
|
+
waitSeconds: 30,
|
|
936
|
+
stallSeconds: 300,
|
|
937
|
+
persistLog: true,
|
|
938
|
+
noticeSteer: true,
|
|
939
|
+
};
|
|
940
|
+
const WAIT_SECONDS_RANGE = { min: 5, max: 60 };
|
|
941
|
+
const STALL_SECONDS_RANGE = { min: 30, max: 3600 };
|
|
942
|
+
/**
|
|
943
|
+
* THE single gate for every `jobs` read AND write, mirroring `clampBashConfig`
|
|
944
|
+
* and the four sections before it.
|
|
945
|
+
*
|
|
946
|
+
* THE ELEVENTH SECTION, AND THE FIRST ONE THAT `config set` CAN REACH BY
|
|
947
|
+
* NESTED KEY (§6.5). Every key is a SCALAR and the section is ONE level deep,
|
|
948
|
+
* because `runConfigGet` splits on a single dot - a `jobs.mirror.enabled` would
|
|
949
|
+
* resolve to `undefined` and the setting would silently never apply.
|
|
950
|
+
*/
|
|
951
|
+
export function clampJobsConfig(raw) {
|
|
952
|
+
const src = (raw && typeof raw === 'object' ? raw : {});
|
|
953
|
+
return {
|
|
954
|
+
enabled: bool(src.enabled, DEFAULT_JOBS_CONFIG.enabled),
|
|
955
|
+
waitSeconds: clampInt(src.waitSeconds, DEFAULT_JOBS_CONFIG.waitSeconds, WAIT_SECONDS_RANGE),
|
|
956
|
+
stallSeconds: clampInt(src.stallSeconds, DEFAULT_JOBS_CONFIG.stallSeconds, STALL_SECONDS_RANGE),
|
|
957
|
+
persistLog: bool(src.persistLog, DEFAULT_JOBS_CONFIG.persistLog),
|
|
958
|
+
noticeSteer: bool(src.noticeSteer, DEFAULT_JOBS_CONFIG.noticeSteer),
|
|
959
|
+
};
|
|
960
|
+
}
|
|
925
961
|
/**
|
|
926
962
|
* Parse `0.9`, `90%` or `90` into a ratio, or `null` when it is neither.
|
|
927
963
|
*
|
|
@@ -970,6 +1006,7 @@ export const DEFAULT_CONFIG = {
|
|
|
970
1006
|
renderGovernor: DEFAULT_RENDER_GOVERNOR,
|
|
971
1007
|
maxRenderIntervalMs: DEFAULT_MAX_RENDER_INTERVAL_MS,
|
|
972
1008
|
diffRender: DEFAULT_DIFF_RENDER,
|
|
1009
|
+
scrollMode: DEFAULT_SCROLL_MODE,
|
|
973
1010
|
syncOutput: DEFAULT_SYNC_OUTPUT,
|
|
974
1011
|
confirmTools: false,
|
|
975
1012
|
toolTimeoutMs: DEFAULT_TOOL_TIMEOUT_MS,
|
|
@@ -1019,6 +1056,12 @@ export const DEFAULT_CONFIG = {
|
|
|
1019
1056
|
// `undefined` would arrive with background services OFF for every existing
|
|
1020
1057
|
// user — the same direction the compaction line above must never fail in.
|
|
1021
1058
|
bash: DEFAULT_BASH_CONFIG,
|
|
1059
|
+
// And one section later again, for the sixth time, and this one would fail
|
|
1060
|
+
// in the WORSE direction: every key in `jobs` is `true` or positive by
|
|
1061
|
+
// default, so a `config.json` predating this feature resolving `config.jobs`
|
|
1062
|
+
// to `undefined` would throw on `config.jobs.waitSeconds` at construction
|
|
1063
|
+
// rather than quietly disabling something.
|
|
1064
|
+
jobs: DEFAULT_JOBS_CONFIG,
|
|
1022
1065
|
};
|
|
1023
1066
|
/**
|
|
1024
1067
|
* Runtime state that used to be stored in `config.json` by mistake
|
package/dist/config/store.js
CHANGED
|
@@ -17,7 +17,7 @@ import { getConfigPath, getSessionsDir } from './app-paths.js';
|
|
|
17
17
|
import { registerProfileSecrets, registerSecretsFrom } from '../logging/secret-registry.js';
|
|
18
18
|
import { adaptLegacyConnectionPatch, ModelProfileConfigError, readModelSettingsDisk } from './model-profile-store.js';
|
|
19
19
|
import { normalizeModelProfiles } from './model-profiles.js';
|
|
20
|
-
import { clampBashConfig, CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampContextWindow, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, } from './schema.js';
|
|
20
|
+
import { clampBashConfig, clampJobsConfig, CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampContextWindow, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, } from './schema.js';
|
|
21
21
|
/**
|
|
22
22
|
* Normalize `maxTokens` for both directions of the config file.
|
|
23
23
|
*
|
|
@@ -189,6 +189,12 @@ export function normalizePersistedConfig(input) {
|
|
|
189
189
|
// and NOT merged over the defaults, which is verbatim the failure the
|
|
190
190
|
// paragraph above says this line prevents.
|
|
191
191
|
bash: clampBashConfig({ ...DEFAULT_CONFIG.bash, ...(partial.bash ?? {}) }),
|
|
192
|
+
// And the eleventh, on the same line and for the same reason. `jobs.enabled`
|
|
193
|
+
// is `true` by default, so a user whose `config.json` predates this feature
|
|
194
|
+
// must not resolve it to `undefined` — and `waitSeconds` would be `undefined`
|
|
195
|
+
// outright, which is a crash on the first `job_wait` rather than a silently
|
|
196
|
+
// wrong setting.
|
|
197
|
+
jobs: clampJobsConfig({ ...DEFAULT_CONFIG.jobs, ...(partial.jobs ?? {}) }),
|
|
192
198
|
};
|
|
193
199
|
}
|
|
194
200
|
/**
|
|
@@ -289,6 +295,13 @@ export function mergePersistedConfig(current, patch) {
|
|
|
289
295
|
// top level and write `background` absent, silently unregistering
|
|
290
296
|
// `bash_output` / `bash_kill` from the next launch onward.
|
|
291
297
|
bash: clampBashConfig({ ...current.bash, ...(patch.bash ?? {}) }),
|
|
298
|
+
// The eleventh section, and the sixth time this file records the same trap.
|
|
299
|
+
// `aragon config set jobs.waitSeconds 45` sends `{ jobs: { waitSeconds: 45 } }`;
|
|
300
|
+
// without this line the shallow top-level spread REPLACES the section and
|
|
301
|
+
// `persistLog` / `noticeSteer` / `enabled` / `stallSeconds` are written
|
|
302
|
+
// absent — with `noticeSteer`'s default masking it on the next load, so the
|
|
303
|
+
// user would never learn that their privacy switch had been turned back on.
|
|
304
|
+
jobs: clampJobsConfig({ ...current.jobs, ...(patch.jobs ?? {}) }),
|
|
292
305
|
};
|
|
293
306
|
return merged;
|
|
294
307
|
}
|
|
@@ -84,6 +84,12 @@ export interface UiState {
|
|
|
84
84
|
* older file, which is the behaviour wanted here anyway.
|
|
85
85
|
*/
|
|
86
86
|
vtInputNoticeVersion: number;
|
|
87
|
+
/**
|
|
88
|
+
* Which revision of the "scrolling now uses your terminal" notice this user has
|
|
89
|
+
* been shown (`0` = none; tui-native-terminal-scroll D12). A scalar beside the
|
|
90
|
+
* others for the same reasons the two above record, and NOT a schema bump.
|
|
91
|
+
*/
|
|
92
|
+
scrollModeNoticeVersion: number;
|
|
87
93
|
}
|
|
88
94
|
/** Read the state, caching it for the life of the process. Never throws. */
|
|
89
95
|
export declare function loadUiState(): UiState;
|
|
@@ -98,6 +104,9 @@ export declare function setMouseNoticeVersion(version: number): void;
|
|
|
98
104
|
/** `0` on a file written before the field existed — see the field's comment. */
|
|
99
105
|
export declare function getVtInputNoticeVersion(): number;
|
|
100
106
|
export declare function setVtInputNoticeVersion(version: number): void;
|
|
107
|
+
/** `0` on a file written before the field existed. */
|
|
108
|
+
export declare function getScrollModeNoticeVersion(): number;
|
|
109
|
+
export declare function setScrollModeNoticeVersion(version: number): void;
|
|
101
110
|
/**
|
|
102
111
|
* Migration-only: fill in fields that are still at their default, and never
|
|
103
112
|
* overwrite a value already here. A second import — after a half-finished
|
package/dist/config/ui-state.js
CHANGED
|
@@ -37,6 +37,7 @@ const DEFAULT_UI_STATE = {
|
|
|
37
37
|
mouseNoticeSeen: false,
|
|
38
38
|
mouseNoticeVersion: 0,
|
|
39
39
|
vtInputNoticeVersion: 0,
|
|
40
|
+
scrollModeNoticeVersion: 0,
|
|
40
41
|
};
|
|
41
42
|
let memory = null;
|
|
42
43
|
/** Non-negative integer or `fallback`; shared by both numeric fields. */
|
|
@@ -69,6 +70,7 @@ function readFromDisk() {
|
|
|
69
70
|
: DEFAULT_UI_STATE.mouseNoticeSeen,
|
|
70
71
|
mouseNoticeVersion: coerceCount(parsed.mouseNoticeVersion, DEFAULT_UI_STATE.mouseNoticeVersion),
|
|
71
72
|
vtInputNoticeVersion: coerceCount(parsed.vtInputNoticeVersion, DEFAULT_UI_STATE.vtInputNoticeVersion),
|
|
73
|
+
scrollModeNoticeVersion: coerceCount(parsed.scrollModeNoticeVersion, DEFAULT_UI_STATE.scrollModeNoticeVersion),
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
catch {
|
|
@@ -142,6 +144,13 @@ export function getVtInputNoticeVersion() {
|
|
|
142
144
|
export function setVtInputNoticeVersion(version) {
|
|
143
145
|
update({ vtInputNoticeVersion: version });
|
|
144
146
|
}
|
|
147
|
+
/** `0` on a file written before the field existed. */
|
|
148
|
+
export function getScrollModeNoticeVersion() {
|
|
149
|
+
return loadUiState().scrollModeNoticeVersion;
|
|
150
|
+
}
|
|
151
|
+
export function setScrollModeNoticeVersion(version) {
|
|
152
|
+
update({ scrollModeNoticeVersion: version });
|
|
153
|
+
}
|
|
145
154
|
/**
|
|
146
155
|
* Migration-only: fill in fields that are still at their default, and never
|
|
147
156
|
* overwrite a value already here. A second import — after a half-finished
|
package/dist/exec/events.d.ts
CHANGED
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* PURE TYPES AND BUILDERS. No I/O, no process access, no imports outside
|
|
11
11
|
* `node:`-free type space, so a consumer of this module cannot accidentally
|
|
12
|
-
* evaluate the runner.
|
|
13
|
-
*
|
|
12
|
+
* evaluate the runner. The one import below is a TYPE from `proc/types.ts` for
|
|
13
|
+
* the same reason: a shape, erased at compile time.
|
|
14
|
+
*/
|
|
15
|
+
import type { ServiceStatus } from '../proc/types.js';
|
|
16
|
+
/**
|
|
14
17
|
* `EXEC_SCHEMA_VERSION` appears on `system` and on `result` and NOWHERE ELSE.
|
|
15
18
|
* ADDING A FIELD OR AN EVENT TYPE NEVER BUMPS IT; renaming or removing one does.
|
|
16
19
|
* That rule only holds if consumers keep their side of it, so the README states
|
|
@@ -370,7 +373,30 @@ export interface ExecResultEvent {
|
|
|
370
373
|
todos: ExecTodoCounts | null;
|
|
371
374
|
error: ExecErrorInfo | null;
|
|
372
375
|
}
|
|
373
|
-
|
|
376
|
+
/**
|
|
377
|
+
* A background job's lifecycle (long-task-jobs §4.10).
|
|
378
|
+
*
|
|
379
|
+
* A PURE ADDITION to schema v1: a consumer that does not know this `type`
|
|
380
|
+
* ignores it, which is the forward-compatibility contract this file's header
|
|
381
|
+
* states. `progress` is emitted only when the extracted text CHANGES and at most
|
|
382
|
+
* once a second - a 40-minute training run would otherwise write thousands of
|
|
383
|
+
* lines to a machine consumer's stdout.
|
|
384
|
+
*/
|
|
385
|
+
export interface ExecJobEvent {
|
|
386
|
+
type: 'job';
|
|
387
|
+
schemaVersion: number;
|
|
388
|
+
sessionId: string;
|
|
389
|
+
subtype: 'started' | 'progress' | 'exited' | 'stalled';
|
|
390
|
+
/** `t3` / `s1`. */
|
|
391
|
+
jobId: string;
|
|
392
|
+
kind: 'service' | 'task';
|
|
393
|
+
status: ServiceStatus;
|
|
394
|
+
elapsedMs: number;
|
|
395
|
+
/** Only on `subtype === 'progress'`, and only when the text changed. */
|
|
396
|
+
progress?: string;
|
|
397
|
+
exitCode?: number | null;
|
|
398
|
+
}
|
|
399
|
+
export type ExecEvent = ExecSystemInitEvent | ExecUserEvent | ExecTurnStateEvent | ExecProgressEvent | ExecTextDeltaEvent | ExecThinkingEvent | ExecAssistantEvent | ExecToolCallEvent | ExecToolResultEvent | ExecTodoEvent | ExecTeamEvent | ExecFastReviewEvent | ExecFastTierEvent | ExecCompactionEvent | ExecRetryEvent | ExecJobEvent | ExecErrorEvent | ExecResultEvent;
|
|
374
400
|
/** Everything `system/init` needs that the runner cannot derive for itself. */
|
|
375
401
|
export interface ExecInitParams {
|
|
376
402
|
sessionId: string;
|
package/dist/exec/events.js
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* THE CONTRACT (cli-integration-surface section 5.1).
|
|
3
|
-
*
|
|
4
|
-
* ASCII ONLY - `src/exec/**` is inside the glyph scanner's scope
|
|
5
|
-
* (`glyphs.test.ts::inScope`, R-3 / AC-25). Every string here can reach a
|
|
6
|
-
* consumer's terminal through `--output-format text` fallbacks or a stderr
|
|
7
|
-
* warning, and the whole point of the scanner is that a legacy `cmd.exe` shows
|
|
8
|
-
* mojibake for anything else.
|
|
9
|
-
*
|
|
10
|
-
* PURE TYPES AND BUILDERS. No I/O, no process access, no imports outside
|
|
11
|
-
* `node:`-free type space, so a consumer of this module cannot accidentally
|
|
12
|
-
* evaluate the runner.
|
|
13
|
-
*
|
|
14
2
|
* `EXEC_SCHEMA_VERSION` appears on `system` and on `result` and NOWHERE ELSE.
|
|
15
3
|
* ADDING A FIELD OR AN EVENT TYPE NEVER BUMPS IT; renaming or removing one does.
|
|
16
4
|
* That rule only holds if consumers keep their side of it, so the README states
|
|
@@ -53,6 +41,9 @@ export const EXEC_CAPABILITIES = [
|
|
|
53
41
|
'session-file',
|
|
54
42
|
'turn-lifecycle',
|
|
55
43
|
'execution-progress',
|
|
44
|
+
// Background job lifecycle events (long-task-jobs §4.10). A consumer that does
|
|
45
|
+
// not know the `job` type ignores it; this string is how it asks first.
|
|
46
|
+
'background-jobs',
|
|
56
47
|
];
|
|
57
48
|
export function buildInitEvent(params) {
|
|
58
49
|
return {
|
package/dist/exec/runner.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { type FollowThroughMode } from '../todo/follow-through.js';
|
|
|
20
20
|
import type { ExecEmitter } from './emitter.js';
|
|
21
21
|
import type { FastSnapshot } from '../fast/types.js';
|
|
22
22
|
import type { ExecStopReason } from './events.js';
|
|
23
|
+
import type { ProcEvent } from '../proc/types.js';
|
|
23
24
|
/**
|
|
24
25
|
* The controller surface the runner needs.
|
|
25
26
|
*
|
|
@@ -45,6 +46,14 @@ export interface ExecRunnerController extends HeadlessController {
|
|
|
45
46
|
* it simply emits no `fast_tier` events.
|
|
46
47
|
*/
|
|
47
48
|
fastSnapshot?(): FastSnapshot | null;
|
|
49
|
+
/**
|
|
50
|
+
* The CLI-local process stream, when this session supervises processes.
|
|
51
|
+
*
|
|
52
|
+
* OPTIONAL AND CALLED WITH `?.()`, exactly like the three optional streams
|
|
53
|
+
* above: this interface is minimal so a test can satisfy it with an object
|
|
54
|
+
* literal, and a host with no supervisor has nothing to report.
|
|
55
|
+
*/
|
|
56
|
+
subscribeProc?(listener: (event: ProcEvent) => void): () => void;
|
|
48
57
|
}
|
|
49
58
|
/** Where the caller's messages come from. One turn, or an NDJSON stdin stream. */
|
|
50
59
|
export interface ExecPromptSource {
|
|
@@ -97,6 +106,10 @@ export declare class ExecRunner {
|
|
|
97
106
|
private readonly progress;
|
|
98
107
|
private controller;
|
|
99
108
|
private unsubscribers;
|
|
109
|
+
/** Per-job progress throttle state: the last text emitted, and when. */
|
|
110
|
+
private readonly jobProgressAt;
|
|
111
|
+
/** Per-job `lastOutputAt` at which `stalled` was last reported. */
|
|
112
|
+
private readonly jobStalledAt;
|
|
100
113
|
private turns;
|
|
101
114
|
private readonly usage;
|
|
102
115
|
private compactionBilling;
|
|
@@ -123,6 +136,17 @@ export declare class ExecRunner {
|
|
|
123
136
|
* multiple subscribers.
|
|
124
137
|
*/
|
|
125
138
|
attach(controller: ExecRunnerController): void;
|
|
139
|
+
/**
|
|
140
|
+
* Map a supervisor event onto the `job` channel.
|
|
141
|
+
*
|
|
142
|
+
* THROTTLING IS THE WHOLE DISCIPLINE HERE. `output` is already coalesced to
|
|
143
|
+
* <=10 Hz inside the supervisor, which is still far too fast for a machine
|
|
144
|
+
* consumer reading a 40-minute training run; `progress` is therefore emitted
|
|
145
|
+
* only when the extracted TEXT CHANGES and never more than once a second. The
|
|
146
|
+
* other three subtypes are one-shot per job, so they pass straight through.
|
|
147
|
+
*/
|
|
148
|
+
private onProcEvent;
|
|
149
|
+
private emitJob;
|
|
126
150
|
/**
|
|
127
151
|
* Drop the subscriptions and the timer.
|
|
128
152
|
*
|
package/dist/exec/runner.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { formatStreamError } from '../agent/reducer.js';
|
|
18
18
|
import { setSignalTerminator } from '../logging/install.js';
|
|
19
19
|
import { advanceBudget, decideFollowThrough, emptyBudget, } from '../todo/follow-through.js';
|
|
20
|
+
import { EXEC_SCHEMA_VERSION } from './events.js';
|
|
20
21
|
import { ExecProgressReporter } from './progress-reporter.js';
|
|
21
22
|
export function singlePrompt(text) {
|
|
22
23
|
let served = false;
|
|
@@ -44,6 +45,10 @@ export class ExecRunner {
|
|
|
44
45
|
progress;
|
|
45
46
|
controller = null;
|
|
46
47
|
unsubscribers = [];
|
|
48
|
+
/** Per-job progress throttle state: the last text emitted, and when. */
|
|
49
|
+
jobProgressAt = new Map();
|
|
50
|
+
/** Per-job `lastOutputAt` at which `stalled` was last reported. */
|
|
51
|
+
jobStalledAt = new Map();
|
|
47
52
|
turns = 0;
|
|
48
53
|
usage = { inputTokens: 0, outputTokens: 0 };
|
|
49
54
|
compactionBilling;
|
|
@@ -218,8 +223,82 @@ export class ExecRunner {
|
|
|
218
223
|
});
|
|
219
224
|
if (compaction)
|
|
220
225
|
this.unsubscribers.push(compaction);
|
|
226
|
+
// Background jobs (long-task-jobs §4.10). SUBSCRIBED HERE, IN `attach()`, NOT
|
|
227
|
+
// IN `start()`: the follow-through chain enters `run()` more than once, and a
|
|
228
|
+
// subscription made there would be re-made every time and never released —
|
|
229
|
+
// one leaked listener per turn for the life of the process.
|
|
230
|
+
//
|
|
231
|
+
// The four-stream shape is deliberate: `?:` optional call, `unsubscribers`
|
|
232
|
+
// push, `detach()` releases. Identical to `subscribeTeam` / `subscribeTodos` /
|
|
233
|
+
// `subscribeFast` / `subscribeCompaction`, so a host without a supervisor
|
|
234
|
+
// simply emits nothing.
|
|
235
|
+
const proc = controller.subscribeProc?.((event) => this.onProcEvent(event));
|
|
236
|
+
if (proc)
|
|
237
|
+
this.unsubscribers.push(proc);
|
|
221
238
|
this.installTerminator();
|
|
222
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Map a supervisor event onto the `job` channel.
|
|
242
|
+
*
|
|
243
|
+
* THROTTLING IS THE WHOLE DISCIPLINE HERE. `output` is already coalesced to
|
|
244
|
+
* <=10 Hz inside the supervisor, which is still far too fast for a machine
|
|
245
|
+
* consumer reading a 40-minute training run; `progress` is therefore emitted
|
|
246
|
+
* only when the extracted TEXT CHANGES and never more than once a second. The
|
|
247
|
+
* other three subtypes are one-shot per job, so they pass straight through.
|
|
248
|
+
*/
|
|
249
|
+
onProcEvent(event) {
|
|
250
|
+
const job = event.service;
|
|
251
|
+
if (event.type === 'started') {
|
|
252
|
+
this.emitJob(job, 'started');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (event.type === 'exited' || event.type === 'stopped') {
|
|
256
|
+
this.jobProgressAt.delete(job.id);
|
|
257
|
+
this.jobStalledAt.delete(job.id);
|
|
258
|
+
this.emitJob(job, 'exited');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// `ready` is deliberately NOT mapped onto a second `started`: a service emits
|
|
262
|
+
// both, and a consumer that counted `started` events would see two. The
|
|
263
|
+
// readiness fact is already carried by `status` on any later event, and the
|
|
264
|
+
// four subtypes this channel publishes are one-shot per job.
|
|
265
|
+
if (event.type !== 'output')
|
|
266
|
+
return;
|
|
267
|
+
if (job.stalled === true) {
|
|
268
|
+
if (this.jobStalledAt.get(job.id) === job.lastOutputAt)
|
|
269
|
+
return;
|
|
270
|
+
this.jobStalledAt.set(job.id, job.lastOutputAt);
|
|
271
|
+
this.emitJob(job, 'stalled');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const text = job.progress?.text;
|
|
275
|
+
if (text === undefined)
|
|
276
|
+
return;
|
|
277
|
+
if (this.jobProgressAt.get(job.id)?.text === text)
|
|
278
|
+
return;
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
const last = this.jobProgressAt.get(job.id)?.at ?? 0;
|
|
281
|
+
// ONE SECOND IS THE FLOOR, and the first change of a job is always emitted
|
|
282
|
+
// (no previous entry), so a consumer sees the initial figure immediately.
|
|
283
|
+
if (last !== 0 && now - last < 1000)
|
|
284
|
+
return;
|
|
285
|
+
this.jobProgressAt.set(job.id, { text, at: now });
|
|
286
|
+
this.emitJob(job, 'progress', text);
|
|
287
|
+
}
|
|
288
|
+
emitJob(job, subtype, progress) {
|
|
289
|
+
this.emit({
|
|
290
|
+
type: 'job',
|
|
291
|
+
schemaVersion: EXEC_SCHEMA_VERSION,
|
|
292
|
+
sessionId: this.sessionId,
|
|
293
|
+
subtype,
|
|
294
|
+
jobId: job.id,
|
|
295
|
+
kind: job.kind,
|
|
296
|
+
status: job.status,
|
|
297
|
+
elapsedMs: Math.max(0, (job.endedAt ?? Date.now()) - job.startedAt),
|
|
298
|
+
...(progress !== undefined ? { progress } : {}),
|
|
299
|
+
...(subtype === 'exited' ? { exitCode: job.exitCode } : {}),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
223
302
|
/**
|
|
224
303
|
* Drop the subscriptions and the timer.
|
|
225
304
|
*
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Position Report parsing (tui-bottom-anchored-live-region §4.5).
|
|
3
|
+
*
|
|
4
|
+
* The bottom anchor needs one number the writer cannot compute: how many rows are
|
|
5
|
+
* left below the cursor. `ESC[6n` is the only way to ask, and the terminal answers
|
|
6
|
+
* with `ESC[<row>;<col>R` (DECXCPR adds a `?`: `ESC[?<row>;<col>R`) on STDIN —
|
|
7
|
+
* the same stream the user types on. Two things follow, and both live here:
|
|
8
|
+
*
|
|
9
|
+
* 1. THE ANSWER MUST BE STRIPPED BEFORE INK SEES IT. A report that reaches Ink
|
|
10
|
+
* travels the keystroke path: `[` is 0x5B, so `PromptInput`'s control-sequence
|
|
11
|
+
* guard passes it through and the user's draft gets `[12;1R` typed into it (the
|
|
12
|
+
* same class of accident the mouse filter exists to prevent).
|
|
13
|
+
* 2. THE ANSWER CAN ARRIVE IN TWO CHUNKS. ConPTY, tmux and a loaded SSH session
|
|
14
|
+
* all split reads at arbitrary points, and a half report (`ESC[12`) is worse
|
|
15
|
+
* than a whole one: `parse-keypress` has no branch for it.
|
|
16
|
+
*
|
|
17
|
+
* Pure functions only, mirroring `mouse-events.ts`: the filter owns the buffering
|
|
18
|
+
* and the timers, this module owns the shapes.
|
|
19
|
+
*/
|
|
20
|
+
/** The query for a Cursor Position Report (DSR 6). ASCII by construction. */
|
|
21
|
+
export declare const CURSOR_REPORT_QUERY = "\u001B[6n";
|
|
22
|
+
/** One parsed report. `row`/`col` are 1-based, exactly as the terminal sent them. */
|
|
23
|
+
export interface CursorReport {
|
|
24
|
+
row: number;
|
|
25
|
+
col: number;
|
|
26
|
+
}
|
|
27
|
+
export interface CursorReportSplit {
|
|
28
|
+
/** Text that survives stripping — everything that is not a report. */
|
|
29
|
+
text: string;
|
|
30
|
+
/** Reports found in this chunk, in order. */
|
|
31
|
+
reports: CursorReport[];
|
|
32
|
+
/** Trailing bytes that could still become a report; hand them to the next chunk. */
|
|
33
|
+
pending: string;
|
|
34
|
+
}
|
|
35
|
+
/** True when `s` is a non-empty proper prefix of a possible report. */
|
|
36
|
+
export declare function isCursorReportPrefix(s: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Split the reports out of one chunk.
|
|
39
|
+
*
|
|
40
|
+
* `pending` carries the tail that might still complete into a report. It is only
|
|
41
|
+
* ever the LAST fragment: the scan is left to right, so anything before a complete
|
|
42
|
+
* report is settled text.
|
|
43
|
+
*/
|
|
44
|
+
export declare function splitCursorReports(text: string): CursorReportSplit;
|
|
45
|
+
/**
|
|
46
|
+
* Length of the trailing run of `text` that is a proper prefix of a report, or 0.
|
|
47
|
+
*
|
|
48
|
+
* LONGEST MATCH WINS, unlike the mouse family's shortest-wins rule. The families are
|
|
49
|
+
* combined with `max()`, so only the longest answer ever survives, and a longer hold
|
|
50
|
+
* is never wrong here: `ESC[1` can still become `ESC[12;1R`, while the shorter
|
|
51
|
+
* answer `ESC` could not describe it. The bound is the longest report the terminals
|
|
52
|
+
* we target emit (DECXCPR with a `?` and three-digit coordinates).
|
|
53
|
+
*/
|
|
54
|
+
export declare function trailingCprPrefixLength(text: string): number;
|
|
55
|
+
/** The query bytes, as a function so call sites read as an action rather than a literal. */
|
|
56
|
+
export declare function formatCursorReportQuery(): string;
|
|
57
|
+
/**
|
|
58
|
+
* Rows left below a reported cursor row on a terminal of `rows` rows.
|
|
59
|
+
* `0` means the cursor is already on the last row; `null` when the height is unknown.
|
|
60
|
+
*/
|
|
61
|
+
export declare function spaceBelowFromReport(report: CursorReport, rows: number | undefined): number | null;
|