@aragon-agent/cli 0.6.3 → 0.6.4
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 +105 -0
- package/README.md +95 -2
- package/dist/agent/controller.d.ts +119 -0
- package/dist/agent/controller.js +275 -1
- package/dist/agent/reducer.d.ts +69 -0
- package/dist/agent/reducer.js +100 -0
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +84 -22
- package/dist/commands/builtins.js +92 -1
- package/dist/compaction/child.js +5 -0
- package/dist/compaction/command.js +14 -1
- package/dist/compaction/compactor.d.ts +34 -0
- package/dist/compaction/compactor.js +158 -37
- package/dist/compaction/types.d.ts +36 -0
- package/dist/compaction/wiring.d.ts +9 -0
- package/dist/compaction/wiring.js +22 -1
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +48 -1
- package/dist/config/load.d.ts +11 -0
- package/dist/config/load.js +39 -1
- package/dist/config/schema.d.ts +85 -0
- package/dist/config/schema.js +33 -0
- package/dist/config/store.js +21 -1
- package/dist/input/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +2 -0
- package/dist/logging/install.js +53 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +36 -1
- package/dist/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/App.d.ts +15 -2
- package/dist/ui/App.js +256 -7
- package/dist/ui/Composer.d.ts +54 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +49 -3
- package/dist/ui/PromptInput.js +184 -52
- package/dist/ui/StatusBar.d.ts +19 -0
- package/dist/ui/StatusBar.js +5 -2
- package/dist/ui/Transcript.js +29 -1
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- package/dist/ui/layout/budget.d.ts +34 -6
- package/dist/ui/layout/budget.js +32 -7
- package/dist/ui/layout/virtual-window.js +51 -1
- package/dist/ui/overlays/HelpOverlay.js +3 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.js +14 -1
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +16 -0
- package/dist/ui/screen.js +45 -2
- package/dist/ui/selection/selection-controller.d.ts +1 -1
- package/dist/ui/transcript-text.js +49 -0
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -47
- package/dist/input/stdin-mouse-filter.js +0 -153
package/dist/config/schema.d.ts
CHANGED
|
@@ -599,6 +599,48 @@ export declare const DEFAULT_COMPACTION_CONFIG: CompactionConfig;
|
|
|
599
599
|
* gives `warnThreshold` a range of `[0.4, 0.45]`.
|
|
600
600
|
*/
|
|
601
601
|
export declare function clampCompactionConfig(raw: unknown): CompactionConfig;
|
|
602
|
+
export interface BashConfig {
|
|
603
|
+
/**
|
|
604
|
+
* Register `bash_output` / `bash_kill`, honour `background`, splice the prompt
|
|
605
|
+
* block, render the chip. Default TRUE.
|
|
606
|
+
*
|
|
607
|
+
* READ ONCE AT CONSTRUCTION, the `team.enabled` / `todo.enabled` /
|
|
608
|
+
* `fast.enabled` rule.
|
|
609
|
+
*
|
|
610
|
+
* IT GOVERNS *SERVICES*, NEVER *INTERRUPTIBILITY* (I-2 / D-9). `Esc`/`Esc`,
|
|
611
|
+
* the foreground child registry and `forceStop()` are unconditional, because
|
|
612
|
+
* "bash always settles" and "Esc twice always works" are promised
|
|
613
|
+
* unconditionally — a user who turned background services off has not asked
|
|
614
|
+
* for a less interruptible agent.
|
|
615
|
+
*/
|
|
616
|
+
background: boolean;
|
|
617
|
+
/**
|
|
618
|
+
* Let the classifier decide when the model did not say. Default TRUE.
|
|
619
|
+
*
|
|
620
|
+
* RESOLVES TO `false` WITHOUT AN INTERACTIVE VIEW (D-15 / P1-8), and that is
|
|
621
|
+
* decided at the tool layer rather than here: the value on disk is the user's
|
|
622
|
+
* preference, and headless has no card, no `/bg` and no user to keep a service
|
|
623
|
+
* alive, so the rescue would be a surprise instead. An explicit
|
|
624
|
+
* `background: true` is still honoured there — that one the model asked for.
|
|
625
|
+
*/
|
|
626
|
+
autoBackground: boolean;
|
|
627
|
+
/** How long a background launch may block the turn before returning. */
|
|
628
|
+
startupSettleMs: number;
|
|
629
|
+
/** How long a service may stay `starting` before it is called `running`. */
|
|
630
|
+
readyTimeoutMs: number;
|
|
631
|
+
}
|
|
632
|
+
export declare const DEFAULT_BASH_CONFIG: BashConfig;
|
|
633
|
+
/**
|
|
634
|
+
* THE single gate for every bash-config read AND write, mirroring
|
|
635
|
+
* `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` / `clampTodoConfig`
|
|
636
|
+
* for the reason those record: hardening only the read path leaves a bad value
|
|
637
|
+
* on disk that reverts to the default on every launch, which presents to the
|
|
638
|
+
* user as "my setting won't stick".
|
|
639
|
+
*
|
|
640
|
+
* CLAMPED, NEVER REJECTED — the discipline every other key in this file follows.
|
|
641
|
+
* A garbage `startupSettleMs` resolves to 4000 and throws nothing.
|
|
642
|
+
*/
|
|
643
|
+
export declare function clampBashConfig(raw: unknown): BashConfig;
|
|
602
644
|
/**
|
|
603
645
|
* Parse `0.9`, `90%` or `90` into a ratio, or `null` when it is neither.
|
|
604
646
|
*
|
|
@@ -702,6 +744,16 @@ export interface PersistedConfig {
|
|
|
702
744
|
* written at all, so `false` is byte-identical to a pre-feature session.
|
|
703
745
|
*/
|
|
704
746
|
mouseSelect: boolean;
|
|
747
|
+
/**
|
|
748
|
+
* Recognise pasted text and collapse a large paste into a placeholder
|
|
749
|
+
* (tui-paste-handling D-10 / section 7.3).
|
|
750
|
+
*
|
|
751
|
+
* A PLAIN BOOLEAN and the ONLY user-facing key this feature adds: the collapse
|
|
752
|
+
* threshold and every size bound are structural and live in `input/limits.ts`
|
|
753
|
+
* / `ui/composer-limits.ts`. A tunable threshold is a preference nobody has
|
|
754
|
+
* asked for; a kill switch is what a bug report needs.
|
|
755
|
+
*/
|
|
756
|
+
paste: boolean;
|
|
705
757
|
/** Idle ms before a paused viewport returns to the newest line; `0` disables. */
|
|
706
758
|
scrollResumeMs: number;
|
|
707
759
|
/** Start every session in PLAN mode (plan-mode §4.4). */
|
|
@@ -726,6 +778,8 @@ export interface PersistedConfig {
|
|
|
726
778
|
update: UpdateConfig;
|
|
727
779
|
/** Context compaction. The NINTH nested object; scalars only (see CompactionConfig). */
|
|
728
780
|
compaction: CompactionConfig;
|
|
781
|
+
/** Background services. The TENTH nested object; scalars only (see BashConfig). */
|
|
782
|
+
bash: BashConfig;
|
|
729
783
|
}
|
|
730
784
|
export declare const DEFAULT_CONFIG: PersistedConfig;
|
|
731
785
|
/**
|
|
@@ -866,6 +920,17 @@ export interface CliConfig {
|
|
|
866
920
|
* `config list`, and never reach a single one of them.
|
|
867
921
|
*/
|
|
868
922
|
mouseSelect: boolean;
|
|
923
|
+
/**
|
|
924
|
+
* Whether this session recognises pastes at all.
|
|
925
|
+
*
|
|
926
|
+
* IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason
|
|
927
|
+
* `mouseSelect` above records: `runInteractive` reads `config.paste` -- where
|
|
928
|
+
* `config` is a `CliConfig` -- to decide the filter's `paste` feature, the
|
|
929
|
+
* `?2004h` write and the `PasteBridge`. A key added to the persisted shape
|
|
930
|
+
* alone would round-trip through `config set`, appear in `config list`, and
|
|
931
|
+
* reach none of them.
|
|
932
|
+
*/
|
|
933
|
+
paste: boolean;
|
|
869
934
|
/** Idle ms before a paused viewport returns to the newest line; `0` disables. */
|
|
870
935
|
scrollResumeMs: number;
|
|
871
936
|
/**
|
|
@@ -959,6 +1024,26 @@ export interface CliConfig {
|
|
|
959
1024
|
* reach either of them.
|
|
960
1025
|
*/
|
|
961
1026
|
compaction: CompactionConfig;
|
|
1027
|
+
/**
|
|
1028
|
+
* Resolved background-service settings (background-service-supervision §5.4).
|
|
1029
|
+
*
|
|
1030
|
+
* `background` here decides whether `bash`'s schema carries a `background`
|
|
1031
|
+
* property, whether `bash_output` / `bash_kill` are REGISTERED and whether
|
|
1032
|
+
* `<background_services>` is spliced — all three at construction, and none of
|
|
1033
|
+
* them undoable, the `team.enabled` / `todo.enabled` / `fast.enabled` rule.
|
|
1034
|
+
* The other three keys are read LIVE.
|
|
1035
|
+
*
|
|
1036
|
+
* IT DOES NOT GATE THE INTERRUPT LADDER (I-2 / D-9). `ProcSupervisor` is
|
|
1037
|
+
* constructed unconditionally, because `Esc`/`Esc` and the foreground registry
|
|
1038
|
+
* are what make G2 and G3 true and those are promised whatever this says.
|
|
1039
|
+
*
|
|
1040
|
+
* IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason every
|
|
1041
|
+
* section above records: the controller and `bash` read `config.bash`, where
|
|
1042
|
+
* `config` is a `CliConfig`, so a key added to the persisted shape alone would
|
|
1043
|
+
* round-trip through `config set`, appear in `config list` — and never reach
|
|
1044
|
+
* either of them.
|
|
1045
|
+
*/
|
|
1046
|
+
bash: BashConfig;
|
|
962
1047
|
/**
|
|
963
1048
|
* Lifetime submit count driving the composer hint fade (`HINT_FADE_AFTER`).
|
|
964
1049
|
*
|
package/dist/config/schema.js
CHANGED
|
@@ -777,6 +777,33 @@ export function clampCompactionConfig(raw) {
|
|
|
777
777
|
archive: bool(src.archive, DEFAULT_COMPACTION_CONFIG.archive),
|
|
778
778
|
};
|
|
779
779
|
}
|
|
780
|
+
export const DEFAULT_BASH_CONFIG = {
|
|
781
|
+
background: true,
|
|
782
|
+
autoBackground: true,
|
|
783
|
+
startupSettleMs: 4000,
|
|
784
|
+
readyTimeoutMs: 60_000,
|
|
785
|
+
};
|
|
786
|
+
const STARTUP_SETTLE_RANGE = { min: 500, max: 30_000 };
|
|
787
|
+
const READY_TIMEOUT_RANGE = { min: 5_000, max: 600_000 };
|
|
788
|
+
/**
|
|
789
|
+
* THE single gate for every bash-config read AND write, mirroring
|
|
790
|
+
* `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` / `clampTodoConfig`
|
|
791
|
+
* for the reason those record: hardening only the read path leaves a bad value
|
|
792
|
+
* on disk that reverts to the default on every launch, which presents to the
|
|
793
|
+
* user as "my setting won't stick".
|
|
794
|
+
*
|
|
795
|
+
* CLAMPED, NEVER REJECTED — the discipline every other key in this file follows.
|
|
796
|
+
* A garbage `startupSettleMs` resolves to 4000 and throws nothing.
|
|
797
|
+
*/
|
|
798
|
+
export function clampBashConfig(raw) {
|
|
799
|
+
const src = (raw && typeof raw === 'object' ? raw : {});
|
|
800
|
+
return {
|
|
801
|
+
background: bool(src.background, DEFAULT_BASH_CONFIG.background),
|
|
802
|
+
autoBackground: bool(src.autoBackground, DEFAULT_BASH_CONFIG.autoBackground),
|
|
803
|
+
startupSettleMs: clampInt(src.startupSettleMs, DEFAULT_BASH_CONFIG.startupSettleMs, STARTUP_SETTLE_RANGE),
|
|
804
|
+
readyTimeoutMs: clampInt(src.readyTimeoutMs, DEFAULT_BASH_CONFIG.readyTimeoutMs, READY_TIMEOUT_RANGE),
|
|
805
|
+
};
|
|
806
|
+
}
|
|
780
807
|
/**
|
|
781
808
|
* Parse `0.9`, `90%` or `90` into a ratio, or `null` when it is neither.
|
|
782
809
|
*
|
|
@@ -833,6 +860,7 @@ export const DEFAULT_CONFIG = {
|
|
|
833
860
|
hints: true,
|
|
834
861
|
mouse: true,
|
|
835
862
|
mouseSelect: DEFAULT_MOUSE_SELECT,
|
|
863
|
+
paste: true,
|
|
836
864
|
scrollResumeMs: DEFAULT_SCROLL_RESUME_MS,
|
|
837
865
|
planModeDefault: false,
|
|
838
866
|
planModeMaxAskRounds: DEFAULT_PLAN_MAX_ASK_ROUNDS,
|
|
@@ -865,6 +893,11 @@ export const DEFAULT_CONFIG = {
|
|
|
865
893
|
// arrive OFF for every existing user, which is the one direction this
|
|
866
894
|
// particular default must never fail in.
|
|
867
895
|
compaction: DEFAULT_COMPACTION_CONFIG,
|
|
896
|
+
// And one section later again, for the fifth time. `bash.background` is `true`
|
|
897
|
+
// by default, so a `config.json` predating this feature resolving it to
|
|
898
|
+
// `undefined` would arrive with background services OFF for every existing
|
|
899
|
+
// user — the same direction the compaction line above must never fail in.
|
|
900
|
+
bash: DEFAULT_BASH_CONFIG,
|
|
868
901
|
};
|
|
869
902
|
/**
|
|
870
903
|
* Runtime state that used to be stored in `config.json` by mistake
|
package/dist/config/store.js
CHANGED
|
@@ -14,7 +14,7 @@ import { dirname } from 'node:path';
|
|
|
14
14
|
import process from 'node:process';
|
|
15
15
|
import { getConfigPath, getSessionsDir } from './app-paths.js';
|
|
16
16
|
import { registerSecretsFrom } from '../logging/secret-registry.js';
|
|
17
|
-
import { CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, } from './schema.js';
|
|
17
|
+
import { clampBashConfig, CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, } from './schema.js';
|
|
18
18
|
/**
|
|
19
19
|
* Normalize `maxTokens` for both directions of the config file.
|
|
20
20
|
*
|
|
@@ -148,6 +148,20 @@ export function loadPersistedConfig() {
|
|
|
148
148
|
...DEFAULT_CONFIG.compaction,
|
|
149
149
|
...(partial.compaction ?? {}),
|
|
150
150
|
}),
|
|
151
|
+
// `bash` is the tenth. Same one-level rule, same merge — and the READ half
|
|
152
|
+
// supplies a whole section to every user whose `config.json` predates
|
|
153
|
+
// background services, which is all of them. Without it
|
|
154
|
+
// `config.bash.background` reads `undefined`, which is FALSY, so a feature
|
|
155
|
+
// that is `true` by default would arrive off for every existing user.
|
|
156
|
+
//
|
|
157
|
+
// A SIBLING OF `compaction`, NOT A KEY INSIDE ITS ARGUMENT. Nesting it one
|
|
158
|
+
// level too deep type-checks — `...DEFAULT_CONFIG` above already supplies a
|
|
159
|
+
// `bash`, so nothing is missing from the return type — and hands
|
|
160
|
+
// `clampCompactionConfig` a key it drops on the floor. The section then
|
|
161
|
+
// reaches callers straight off the shallow `...partial` spread: unclamped,
|
|
162
|
+
// and NOT merged over the defaults, which is verbatim the failure the
|
|
163
|
+
// paragraph above says this line prevents.
|
|
164
|
+
bash: clampBashConfig({ ...DEFAULT_CONFIG.bash, ...(partial.bash ?? {}) }),
|
|
151
165
|
};
|
|
152
166
|
}
|
|
153
167
|
/**
|
|
@@ -224,6 +238,12 @@ export function updatePersistedConfig(patch) {
|
|
|
224
238
|
// OFF finds it back on tomorrow — a feature that spends money on their behalf
|
|
225
239
|
// re-enabling itself because they nudged a threshold.
|
|
226
240
|
compaction: clampCompactionConfig({ ...current.compaction, ...(patch.compaction ?? {}) }),
|
|
241
|
+
// The tenth section, and the fifth time this file records the same trap.
|
|
242
|
+
// A patch carrying only `{ bash: { autoBackground: false } }` — turning the
|
|
243
|
+
// CLASSIFIER off — would, without this line, be spread shallowly over the
|
|
244
|
+
// top level and write `background` absent, silently unregistering
|
|
245
|
+
// `bash_output` / `bash_kill` from the next launch onward.
|
|
246
|
+
bash: clampBashConfig({ ...current.bash, ...(patch.bash ?? {}) }),
|
|
227
247
|
};
|
|
228
248
|
writeConfigFile(merged);
|
|
229
249
|
return merged;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural bounds for paste handling (tui-paste-handling D-10 / section 8).
|
|
3
|
+
*
|
|
4
|
+
* THESE ARE STRUCTURAL, NOT POLICY. The package convention recorded in
|
|
5
|
+
* `.claude-index/index.md` is that `limits.ts` carries the numbers an
|
|
6
|
+
* implementation needs to stay bounded, while `config/schema.ts` carries the
|
|
7
|
+
* preferences a user is allowed to have. The only user-facing key this feature
|
|
8
|
+
* adds is the boolean `paste`; a tunable collapse threshold is a preference
|
|
9
|
+
* nobody has asked for, and a kill switch is what a bug report actually needs.
|
|
10
|
+
*
|
|
11
|
+
* The two framing constants below are the only bytes that cross from the stdin
|
|
12
|
+
* filter into the composer, so they are load-bearing in a way a threshold is
|
|
13
|
+
* not: see `PASTE_OPEN`.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling on ONE paste. Above it the body is dropped and the user is told,
|
|
17
|
+
* never truncated (D-11): a truncated paste loses data and looks like success.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PASTE_MAX_BYTES: number;
|
|
20
|
+
/** Ceiling on every live payload in one draft, summed. Above it a paste is refused. */
|
|
21
|
+
export declare const PASTE_DRAFT_MAX_BYTES: number;
|
|
22
|
+
/** Ceiling on collapsed blocks in one draft. Above it a paste is refused. */
|
|
23
|
+
export declare const PASTE_MAX_BLOCKS = 32;
|
|
24
|
+
/**
|
|
25
|
+
* Tier 2's single-line rule: a chunk of at least this many printable characters
|
|
26
|
+
* with no line break is a paste.
|
|
27
|
+
*
|
|
28
|
+
* Safe because coalescing a keyboard burst into 25 characters inside one
|
|
29
|
+
* event-loop turn needs roughly 1 500 WPM. It is a statement about what a
|
|
30
|
+
* KEYBOARD can emit, not about what text looks like.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PASTE_MIN_BURST_CHARS = 25;
|
|
33
|
+
/** Tier 2 coalescing window. Ordinary typing never opens it, so never pays it. */
|
|
34
|
+
export declare const PASTE_BURST_MS = 15;
|
|
35
|
+
/**
|
|
36
|
+
* How long an unterminated Tier 1 body is held before it is force-flushed.
|
|
37
|
+
*
|
|
38
|
+
* I-4: an unterminated `\x1b[200~` must never be able to wedge keyboard input,
|
|
39
|
+
* which is the rule `MAX_PENDING_MOUSE_CHARS` already encodes for mouse reports.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PASTE_ASSEMBLY_MAX_MS = 2000;
|
|
42
|
+
/**
|
|
43
|
+
* The inline frame the filter writes into the stream Ink reads (D-3 / I-5).
|
|
44
|
+
*
|
|
45
|
+
* ON THE SAME STREAM, NOT A SIDE CHANNEL. `wrapper.write(text)` is delivered
|
|
46
|
+
* asynchronously; a synchronous side-channel emit would apply the paste BEFORE
|
|
47
|
+
* text that preceded it in the same chunk. Framing preserves order by
|
|
48
|
+
* construction.
|
|
49
|
+
*
|
|
50
|
+
* NUL is the delimiter because no terminal delivers it for any key, and because
|
|
51
|
+
* `sanitisePaste` strips every NUL out of every payload -- so a payload can
|
|
52
|
+
* never forge a frame boundary.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PASTE_OPEN = "\0[";
|
|
55
|
+
export declare const PASTE_CLOSE = "\0]";
|
|
56
|
+
/**
|
|
57
|
+
* The one channel from the stdin filter to `App`'s notice dispatch (P1-3 / I-15).
|
|
58
|
+
*
|
|
59
|
+
* The filter is constructed in `cli.tsx` BEFORE `render()`, so it has no
|
|
60
|
+
* `dispatch`. Its only other outlet is the logger, and a limit that writes only
|
|
61
|
+
* to a log file is indistinguishable -- from the user's chair -- from a paste
|
|
62
|
+
* that silently did nothing, which is exactly what D-11 exists to rule out.
|
|
63
|
+
*
|
|
64
|
+
* Same late-bound shape as `selectionBridge` / `updateBridge` / `terminalBridge`:
|
|
65
|
+
* `cli.tsx` creates the object, `App` assigns `notify` on mount and clears it on
|
|
66
|
+
* unmount, and the filter calls `bridge.notify?.(...)`.
|
|
67
|
+
*/
|
|
68
|
+
export interface PasteBridge {
|
|
69
|
+
notify: ((level: 'warn' | 'error', text: string) => void) | null;
|
|
70
|
+
}
|
|
71
|
+
/** Human-readable byte size for the refusal toast (`4.1 MB`, `812.0 KB`, `7 B`). */
|
|
72
|
+
export declare function formatPasteSize(bytes: number): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural bounds for paste handling (tui-paste-handling D-10 / section 8).
|
|
3
|
+
*
|
|
4
|
+
* THESE ARE STRUCTURAL, NOT POLICY. The package convention recorded in
|
|
5
|
+
* `.claude-index/index.md` is that `limits.ts` carries the numbers an
|
|
6
|
+
* implementation needs to stay bounded, while `config/schema.ts` carries the
|
|
7
|
+
* preferences a user is allowed to have. The only user-facing key this feature
|
|
8
|
+
* adds is the boolean `paste`; a tunable collapse threshold is a preference
|
|
9
|
+
* nobody has asked for, and a kill switch is what a bug report actually needs.
|
|
10
|
+
*
|
|
11
|
+
* The two framing constants below are the only bytes that cross from the stdin
|
|
12
|
+
* filter into the composer, so they are load-bearing in a way a threshold is
|
|
13
|
+
* not: see `PASTE_OPEN`.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling on ONE paste. Above it the body is dropped and the user is told,
|
|
17
|
+
* never truncated (D-11): a truncated paste loses data and looks like success.
|
|
18
|
+
*/
|
|
19
|
+
export const PASTE_MAX_BYTES = 2 * 1024 * 1024;
|
|
20
|
+
/** Ceiling on every live payload in one draft, summed. Above it a paste is refused. */
|
|
21
|
+
export const PASTE_DRAFT_MAX_BYTES = 8 * 1024 * 1024;
|
|
22
|
+
/** Ceiling on collapsed blocks in one draft. Above it a paste is refused. */
|
|
23
|
+
export const PASTE_MAX_BLOCKS = 32;
|
|
24
|
+
/**
|
|
25
|
+
* Tier 2's single-line rule: a chunk of at least this many printable characters
|
|
26
|
+
* with no line break is a paste.
|
|
27
|
+
*
|
|
28
|
+
* Safe because coalescing a keyboard burst into 25 characters inside one
|
|
29
|
+
* event-loop turn needs roughly 1 500 WPM. It is a statement about what a
|
|
30
|
+
* KEYBOARD can emit, not about what text looks like.
|
|
31
|
+
*/
|
|
32
|
+
export const PASTE_MIN_BURST_CHARS = 25;
|
|
33
|
+
/** Tier 2 coalescing window. Ordinary typing never opens it, so never pays it. */
|
|
34
|
+
export const PASTE_BURST_MS = 15;
|
|
35
|
+
/**
|
|
36
|
+
* How long an unterminated Tier 1 body is held before it is force-flushed.
|
|
37
|
+
*
|
|
38
|
+
* I-4: an unterminated `\x1b[200~` must never be able to wedge keyboard input,
|
|
39
|
+
* which is the rule `MAX_PENDING_MOUSE_CHARS` already encodes for mouse reports.
|
|
40
|
+
*/
|
|
41
|
+
export const PASTE_ASSEMBLY_MAX_MS = 2_000;
|
|
42
|
+
/**
|
|
43
|
+
* The inline frame the filter writes into the stream Ink reads (D-3 / I-5).
|
|
44
|
+
*
|
|
45
|
+
* ON THE SAME STREAM, NOT A SIDE CHANNEL. `wrapper.write(text)` is delivered
|
|
46
|
+
* asynchronously; a synchronous side-channel emit would apply the paste BEFORE
|
|
47
|
+
* text that preceded it in the same chunk. Framing preserves order by
|
|
48
|
+
* construction.
|
|
49
|
+
*
|
|
50
|
+
* NUL is the delimiter because no terminal delivers it for any key, and because
|
|
51
|
+
* `sanitisePaste` strips every NUL out of every payload -- so a payload can
|
|
52
|
+
* never forge a frame boundary.
|
|
53
|
+
*/
|
|
54
|
+
export const PASTE_OPEN = '\u0000[';
|
|
55
|
+
export const PASTE_CLOSE = '\u0000]';
|
|
56
|
+
/** Human-readable byte size for the refusal toast (`4.1 MB`, `812.0 KB`, `7 B`). */
|
|
57
|
+
export function formatPasteSize(bytes) {
|
|
58
|
+
if (bytes >= 1024 * 1024)
|
|
59
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
60
|
+
if (bytes >= 1024)
|
|
61
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
62
|
+
return `${bytes} B`;
|
|
63
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paste recognition primitives (tui-paste-handling section 5.1) -- pure,
|
|
3
|
+
* stream-free, no timers.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS LIVES BELOW INK. `ink/build/components/App.js:131-138` reads stdin on
|
|
6
|
+
* `'readable'` and emits each chunk as one `'input'` event;
|
|
7
|
+
* `parse-keypress.js:145-148` maps the exact string `'\r'` to `name: 'return'`,
|
|
8
|
+
* which `PromptInput` reads as "send the message". A 15-line paste on a Windows
|
|
9
|
+
* console routinely arrives as a dozen chunks that are exactly `"\r"`, and each
|
|
10
|
+
* one SUBMITS THE DRAFT AS IT STOOD. No component-level heuristic can undo a
|
|
11
|
+
* `key.return` that has already fired, so recognition has to happen on the byte
|
|
12
|
+
* stream, in front of Ink -- the same lesson `mouse-events.ts` records.
|
|
13
|
+
*
|
|
14
|
+
* Two tiers feed one framing (D-4). Tier 1 is bracketed paste and is exact;
|
|
15
|
+
* Tier 2 is a heuristic on chunk SHAPE for the terminals that ignore
|
|
16
|
+
* `DECSET 2004`. Both are about what a keyboard can physically emit.
|
|
17
|
+
*/
|
|
18
|
+
/** `DECSET 2004` wraps a paste in these two markers. */
|
|
19
|
+
export declare const PASTE_BEGIN_MARK = "\u001B[200~";
|
|
20
|
+
export declare const PASTE_END_MARK = "\u001B[201~";
|
|
21
|
+
/** The longest tail worth holding back while a marker might still complete. */
|
|
22
|
+
export declare const PASTE_MARK_LENGTH: number;
|
|
23
|
+
/**
|
|
24
|
+
* True when `s` is a PROPER prefix of either paste marker (never a whole one).
|
|
25
|
+
*
|
|
26
|
+
* Both markers are held, in every mode, whether or not THIS process enabled DEC
|
|
27
|
+
* 2004 (I-16): the user's shell, `tmux` or a wrapper script may have left it on,
|
|
28
|
+
* and an unconsumed marker is a literal `[200~` typed into the draft -- the
|
|
29
|
+
* exact failure the feature exists to prevent.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isPastePrefix(s: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Length of the trailing run of `text` that is a proper prefix of a paste
|
|
34
|
+
* marker, or 0. Shortest match wins, so only the bytes that could still become a
|
|
35
|
+
* marker are held back.
|
|
36
|
+
*/
|
|
37
|
+
export declare function trailingPastePrefixLength(text: string): number;
|
|
38
|
+
/**
|
|
39
|
+
* Tier 2: is this chunk a paste, or keystrokes?
|
|
40
|
+
*
|
|
41
|
+
* The PRIMARY rule -- "longer than one character AND contains a line break" --
|
|
42
|
+
* is exactly the shape defect A needs, so the two coincide: any chunk that could
|
|
43
|
+
* submit the draft by accident is classified as a paste. A lone `\r` is the
|
|
44
|
+
* Enter key and stays `'keys'`.
|
|
45
|
+
*
|
|
46
|
+
* The SECONDARY rule needs `PASTE_MIN_BURST_CHARS` printable characters in one
|
|
47
|
+
* chunk. It excludes chunks carrying any C0 byte but TAB, which is what keeps an
|
|
48
|
+
* arrow-key burst (`\x1b[B\x1b[B...`, and every wheel notch translated by
|
|
49
|
+
* alternate scroll) out of the paste path.
|
|
50
|
+
*/
|
|
51
|
+
export declare function classifyChunk(text: string, minBurstChars?: number): 'paste' | 'keys';
|
|
52
|
+
/**
|
|
53
|
+
* Make a payload safe to put in a buffer and draw through `<Text>` (G2).
|
|
54
|
+
*
|
|
55
|
+
* 1. CRLF and lone CR both become exactly ONE `\n` -- terminals send CR for a
|
|
56
|
+
* newline in pasted text, and a lone LF is dropped by Ink's key parser
|
|
57
|
+
* entirely, so both have to be normalised here or a pasted line break either
|
|
58
|
+
* submits the message or silently vanishes.
|
|
59
|
+
* 2. Every C0 byte except `\n` and `\t` is removed. Ink writes `<Text>` children
|
|
60
|
+
* to the terminal without escaping anything, so a surviving `\r` overwrites
|
|
61
|
+
* the row it is drawn on and a surviving `\x1b` repaints the frame's colours.
|
|
62
|
+
* 3. DEL and every C1 byte (U+0080..U+009F) go too, and that is NOT padding:
|
|
63
|
+
* U+009B is the single-byte CSI and U+0085 is NEL, and a terminal that decodes
|
|
64
|
+
* them acts on them exactly as it acts on `\x1b[`. A file pasted out of a
|
|
65
|
+
* non-UTF-8 editor walks straight through a "strip C0" rule.
|
|
66
|
+
* 4. TAB is KEPT. Indentation is content, and it is what the model receives.
|
|
67
|
+
*
|
|
68
|
+
* Applied to PAYLOAD ONLY, never to keystrokes, so `Ctrl+A` and Escape keep
|
|
69
|
+
* working. Non-ASCII text (CJK, emoji) is untouched.
|
|
70
|
+
*/
|
|
71
|
+
export declare function sanitisePaste(raw: string): string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paste recognition primitives (tui-paste-handling section 5.1) -- pure,
|
|
3
|
+
* stream-free, no timers.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS LIVES BELOW INK. `ink/build/components/App.js:131-138` reads stdin on
|
|
6
|
+
* `'readable'` and emits each chunk as one `'input'` event;
|
|
7
|
+
* `parse-keypress.js:145-148` maps the exact string `'\r'` to `name: 'return'`,
|
|
8
|
+
* which `PromptInput` reads as "send the message". A 15-line paste on a Windows
|
|
9
|
+
* console routinely arrives as a dozen chunks that are exactly `"\r"`, and each
|
|
10
|
+
* one SUBMITS THE DRAFT AS IT STOOD. No component-level heuristic can undo a
|
|
11
|
+
* `key.return` that has already fired, so recognition has to happen on the byte
|
|
12
|
+
* stream, in front of Ink -- the same lesson `mouse-events.ts` records.
|
|
13
|
+
*
|
|
14
|
+
* Two tiers feed one framing (D-4). Tier 1 is bracketed paste and is exact;
|
|
15
|
+
* Tier 2 is a heuristic on chunk SHAPE for the terminals that ignore
|
|
16
|
+
* `DECSET 2004`. Both are about what a keyboard can physically emit.
|
|
17
|
+
*/
|
|
18
|
+
import { PASTE_MIN_BURST_CHARS } from './limits.js';
|
|
19
|
+
/** `DECSET 2004` wraps a paste in these two markers. */
|
|
20
|
+
export const PASTE_BEGIN_MARK = '\x1b[200~';
|
|
21
|
+
export const PASTE_END_MARK = '\x1b[201~';
|
|
22
|
+
/** The longest tail worth holding back while a marker might still complete. */
|
|
23
|
+
export const PASTE_MARK_LENGTH = PASTE_BEGIN_MARK.length;
|
|
24
|
+
/**
|
|
25
|
+
* True when `s` is a PROPER prefix of either paste marker (never a whole one).
|
|
26
|
+
*
|
|
27
|
+
* Both markers are held, in every mode, whether or not THIS process enabled DEC
|
|
28
|
+
* 2004 (I-16): the user's shell, `tmux` or a wrapper script may have left it on,
|
|
29
|
+
* and an unconsumed marker is a literal `[200~` typed into the draft -- the
|
|
30
|
+
* exact failure the feature exists to prevent.
|
|
31
|
+
*/
|
|
32
|
+
export function isPastePrefix(s) {
|
|
33
|
+
if (s.length === 0 || s.length >= PASTE_MARK_LENGTH)
|
|
34
|
+
return false;
|
|
35
|
+
return PASTE_BEGIN_MARK.startsWith(s) || PASTE_END_MARK.startsWith(s);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Length of the trailing run of `text` that is a proper prefix of a paste
|
|
39
|
+
* marker, or 0. Shortest match wins, so only the bytes that could still become a
|
|
40
|
+
* marker are held back.
|
|
41
|
+
*/
|
|
42
|
+
export function trailingPastePrefixLength(text) {
|
|
43
|
+
const max = Math.min(text.length, PASTE_MARK_LENGTH - 1);
|
|
44
|
+
for (let k = 1; k <= max; k += 1) {
|
|
45
|
+
if (isPastePrefix(text.slice(text.length - k)))
|
|
46
|
+
return k;
|
|
47
|
+
}
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
/** Any C0 control byte other than TAB -- ESC, BEL, NUL, CR, LF included. */
|
|
51
|
+
const HAS_CONTROL_EXCEPT_TAB = /[\u0000-\u0008\u000A-\u001F]/;
|
|
52
|
+
/**
|
|
53
|
+
* Tier 2: is this chunk a paste, or keystrokes?
|
|
54
|
+
*
|
|
55
|
+
* The PRIMARY rule -- "longer than one character AND contains a line break" --
|
|
56
|
+
* is exactly the shape defect A needs, so the two coincide: any chunk that could
|
|
57
|
+
* submit the draft by accident is classified as a paste. A lone `\r` is the
|
|
58
|
+
* Enter key and stays `'keys'`.
|
|
59
|
+
*
|
|
60
|
+
* The SECONDARY rule needs `PASTE_MIN_BURST_CHARS` printable characters in one
|
|
61
|
+
* chunk. It excludes chunks carrying any C0 byte but TAB, which is what keeps an
|
|
62
|
+
* arrow-key burst (`\x1b[B\x1b[B...`, and every wheel notch translated by
|
|
63
|
+
* alternate scroll) out of the paste path.
|
|
64
|
+
*/
|
|
65
|
+
export function classifyChunk(text, minBurstChars = PASTE_MIN_BURST_CHARS) {
|
|
66
|
+
if (text.length > 1 && /[\r\n]/.test(text))
|
|
67
|
+
return 'paste';
|
|
68
|
+
if (text.length >= minBurstChars && !HAS_CONTROL_EXCEPT_TAB.test(text))
|
|
69
|
+
return 'paste';
|
|
70
|
+
return 'keys';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Make a payload safe to put in a buffer and draw through `<Text>` (G2).
|
|
74
|
+
*
|
|
75
|
+
* 1. CRLF and lone CR both become exactly ONE `\n` -- terminals send CR for a
|
|
76
|
+
* newline in pasted text, and a lone LF is dropped by Ink's key parser
|
|
77
|
+
* entirely, so both have to be normalised here or a pasted line break either
|
|
78
|
+
* submits the message or silently vanishes.
|
|
79
|
+
* 2. Every C0 byte except `\n` and `\t` is removed. Ink writes `<Text>` children
|
|
80
|
+
* to the terminal without escaping anything, so a surviving `\r` overwrites
|
|
81
|
+
* the row it is drawn on and a surviving `\x1b` repaints the frame's colours.
|
|
82
|
+
* 3. DEL and every C1 byte (U+0080..U+009F) go too, and that is NOT padding:
|
|
83
|
+
* U+009B is the single-byte CSI and U+0085 is NEL, and a terminal that decodes
|
|
84
|
+
* them acts on them exactly as it acts on `\x1b[`. A file pasted out of a
|
|
85
|
+
* non-UTF-8 editor walks straight through a "strip C0" rule.
|
|
86
|
+
* 4. TAB is KEPT. Indentation is content, and it is what the model receives.
|
|
87
|
+
*
|
|
88
|
+
* Applied to PAYLOAD ONLY, never to keystrokes, so `Ctrl+A` and Escape keep
|
|
89
|
+
* working. Non-ASCII text (CJK, emoji) is untouched.
|
|
90
|
+
*/
|
|
91
|
+
export function sanitisePaste(raw) {
|
|
92
|
+
return raw
|
|
93
|
+
.replace(/\r\n?/g, '\n')
|
|
94
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
95
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdin filter (mouse-wheel-region-routing section 4.2 / 5.2, extended by
|
|
3
|
+
* tui-paste-handling section 5.1).
|
|
4
|
+
*
|
|
5
|
+
* WHY A WRAPPER AND NOT A SECOND LISTENER. Ink 5.2.1 owns stdin in a way that
|
|
6
|
+
* forbids a second reader: `components/App.js` calls `setEncoding('utf8')`,
|
|
7
|
+
* `ref()`, `setRawMode(true)`, subscribes to `'readable'` and drains with
|
|
8
|
+
* `read()`. Attaching our own `'data'` listener to the same `process.stdin`
|
|
9
|
+
* would switch the stream into flowing mode and RACE Ink's `read()` for chunks
|
|
10
|
+
* -- some keystrokes would reach Ink, some would not, non-deterministically
|
|
11
|
+
* (D-3). So the filter REPLACES the stream Ink is given: it reads the real
|
|
12
|
+
* stdin, strips every mouse report, frames every paste, and writes the
|
|
13
|
+
* remainder into a `PassThrough` that carries the surface Ink requires.
|
|
14
|
+
*
|
|
15
|
+
* ONE CHANNEL, NOT TWO, AND THAT NOW COVERS PASTES AS WELL (D-2 / D-3). A second
|
|
16
|
+
* chained filter would need a second `PassThrough` and would reopen the ordering
|
|
17
|
+
* hazard this note closes; a side-channel paste emit would apply the payload
|
|
18
|
+
* BEFORE text that preceded it in the same chunk, because `wrapper.write` is
|
|
19
|
+
* asynchronous and a synchronous emit is not. The paste therefore travels
|
|
20
|
+
* INLINE, wrapped in `PASTE_OPEN` / `PASTE_CLOSE` (I-5).
|
|
21
|
+
*
|
|
22
|
+
* ORDERING IS AN INVARIANT, NOT A CONVENTION (I-8). The caller must build this
|
|
23
|
+
* filter BEFORE anything writes `\x1b[?1000h` or `\x1b[?2004h`. Reporting or
|
|
24
|
+
* bracketed paste enabled while Ink still holds the raw stream does not degrade
|
|
25
|
+
* the feature -- it types `[<0;12;5M` or `[200~` into the user's message, which
|
|
26
|
+
* is strictly worse than the bug being fixed.
|
|
27
|
+
*
|
|
28
|
+
* THE HANDLE ANSWERS EXACTLY ONE QUESTION: "is a stream wrapped?" (I-11 / D-17).
|
|
29
|
+
* It stopped being the answer to "is the mouse on?" the moment paste could build
|
|
30
|
+
* a filter on its own -- `paste` defaults to `true`, so `aragon --no-mouse`
|
|
31
|
+
* builds one. `cli.tsx` reads its own `mouseOn` / `pasteOn` booleans; this module
|
|
32
|
+
* is told which features it is running and never derives them.
|
|
33
|
+
*/
|
|
34
|
+
import { type MouseEvent } from './mouse-events.js';
|
|
35
|
+
import { type PasteBridge } from './limits.js';
|
|
36
|
+
export interface MouseSource {
|
|
37
|
+
/**
|
|
38
|
+
* Returns an unsubscribe function. Multiple subscribers are allowed -- the
|
|
39
|
+
* wheel router and (since tui-selection-and-scroll-follow) the selection
|
|
40
|
+
* controller both take this one channel, and each ignores the kinds it does
|
|
41
|
+
* not own.
|
|
42
|
+
*
|
|
43
|
+
* With `features.mouse` false the channel exists and stays SILENT forever, so
|
|
44
|
+
* a subscriber written against it does not have to know which features are on.
|
|
45
|
+
*/
|
|
46
|
+
subscribe(listener: (event: MouseEvent) => void): () => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Which stream-level features this filter is running.
|
|
50
|
+
*
|
|
51
|
+
* TOLD, NEVER DERIVED (D-17 / P0-1). With `{ mouse: false, paste: true }` no
|
|
52
|
+
* `MouseEvent` is ever emitted and `splitMouseEvents` is not called at all: a
|
|
53
|
+
* `--no-mouse` session never wrote `?1000h`, so no real report can arrive, and
|
|
54
|
+
* running the parser anyway could only ever EAT pasted bytes that happen to look
|
|
55
|
+
* like one. With `{ mouse: true, paste: false }` this is byte-for-byte the
|
|
56
|
+
* v0.6.3 mouse filter.
|
|
57
|
+
*/
|
|
58
|
+
export interface StdinFilterFeatures {
|
|
59
|
+
/** Parse and strip SGR / X10 mouse reports, and emit `MouseEvent`s. */
|
|
60
|
+
readonly mouse: boolean;
|
|
61
|
+
/** Recognise pastes and deliver them framed. */
|
|
62
|
+
readonly paste: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface StdinFilter {
|
|
65
|
+
/** Hand this to Ink: `render(<App/>, { stdin: filter.stdin })`. */
|
|
66
|
+
readonly stdin: NodeJS.ReadStream;
|
|
67
|
+
readonly source: MouseSource;
|
|
68
|
+
/** Idempotent. Detaches from the real stdin and clears every timer (I-7). */
|
|
69
|
+
dispose(): void;
|
|
70
|
+
}
|
|
71
|
+
export declare function createStdinFilter(real: NodeJS.ReadStream, features: StdinFilterFeatures, bridge?: PasteBridge, onWarn?: (reason: string) => void): StdinFilter;
|
|
72
|
+
/**
|
|
73
|
+
* Build a filter, or `null` if construction throws.
|
|
74
|
+
*
|
|
75
|
+
* The `null` return is the whole point (P0-1): a construction failure must
|
|
76
|
+
* degrade to "the wheel is inert and pastes are keystrokes" -- a supported,
|
|
77
|
+
* tested rung of the fail-safe ladder -- rather than leaving `?1000h` or `?2004h`
|
|
78
|
+
* on with no filter in front of Ink. `cli.tsx` derives BOTH `mouseOn` and
|
|
79
|
+
* `pasteOn` from the returned handle for exactly that reason.
|
|
80
|
+
*/
|
|
81
|
+
export declare function tryCreateStdinFilter(real: NodeJS.ReadStream, features: StdinFilterFeatures, onError?: (reason: string) => void, bridge?: PasteBridge): StdinFilter | null;
|