@aragon-agent/cli 0.5.8 → 0.5.10
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 +1157 -747
- package/README.md +1552 -1163
- package/dist/agent/coalesce.d.ts +13 -0
- package/dist/agent/coalesce.js +50 -11
- package/dist/agent/controller.d.ts +141 -2
- package/dist/agent/controller.js +396 -4
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +40 -0
- package/dist/agent/reducer.d.ts +245 -3
- package/dist/agent/reducer.js +490 -17
- package/dist/agent/retry-view.d.ts +68 -0
- package/dist/agent/retry-view.js +95 -0
- package/dist/agent/system-prompt.d.ts +11 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/boot/guard.d.ts +135 -0
- package/dist/boot/guard.js +218 -0
- package/dist/boot/rollback.d.ts +86 -0
- package/dist/boot/rollback.js +185 -0
- package/dist/cli.js +368 -3
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +426 -2
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +31 -0
- package/dist/config/app-paths.js +37 -0
- package/dist/config/cli-commands.d.ts +79 -2
- package/dist/config/cli-commands.js +185 -3
- package/dist/config/env.d.ts +15 -3
- package/dist/config/env.js +143 -3
- package/dist/config/load.d.ts +66 -0
- package/dist/config/load.js +107 -1
- package/dist/config/schema.d.ts +268 -2
- package/dist/config/schema.js +288 -1
- package/dist/config/store.js +32 -1
- package/dist/fast/critique.d.ts +43 -0
- package/dist/fast/critique.js +62 -0
- package/dist/fast/digest.d.ts +42 -0
- package/dist/fast/digest.js +85 -0
- package/dist/fast/frames.d.ts +99 -0
- package/dist/fast/frames.js +180 -0
- package/dist/fast/limits.d.ts +98 -0
- package/dist/fast/limits.js +98 -0
- package/dist/fast/prompt.d.ts +72 -0
- package/dist/fast/prompt.js +94 -0
- package/dist/fast/resolve.d.ts +43 -0
- package/dist/fast/resolve.js +105 -0
- package/dist/fast/review-call.d.ts +99 -0
- package/dist/fast/review-call.js +130 -0
- package/dist/fast/reviewer.d.ts +248 -0
- package/dist/fast/reviewer.js +664 -0
- package/dist/fast/types.d.ts +128 -0
- package/dist/fast/types.js +14 -0
- package/dist/fast/wiring.d.ts +170 -0
- package/dist/fast/wiring.js +316 -0
- package/dist/launcher.d.ts +14 -0
- package/dist/launcher.js +43 -0
- package/dist/logging/install.d.ts +15 -1
- package/dist/logging/install.js +37 -4
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.js +57 -1
- package/dist/team/normalize.d.ts +26 -1
- package/dist/team/normalize.js +23 -3
- package/dist/team/prompt.d.ts +11 -0
- package/dist/team/prompt.js +10 -0
- package/dist/team/report.d.ts +18 -0
- package/dist/team/report.js +27 -1
- package/dist/team/runtime.d.ts +13 -2
- package/dist/team/runtime.js +33 -6
- package/dist/team/subagent.d.ts +19 -1
- package/dist/team/subagent.js +72 -10
- package/dist/team/task-tool.d.ts +26 -0
- package/dist/team/task-tool.js +48 -4
- package/dist/team/types.d.ts +80 -1
- package/dist/tools/bash-tool.js +19 -3
- package/dist/tools/diff.d.ts +25 -5
- package/dist/tools/diff.js +72 -41
- package/dist/tools/file-change-store.d.ts +34 -0
- package/dist/tools/file-change-store.js +57 -0
- package/dist/tools/fs-tools.d.ts +26 -0
- package/dist/tools/fs-tools.js +62 -5
- package/dist/tools/index.d.ts +31 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch.d.ts +115 -0
- package/dist/tools/patch.js +453 -0
- package/dist/tools/terminal-output.d.ts +107 -0
- package/dist/tools/terminal-output.js +198 -0
- package/dist/tools/tool-output-store.d.ts +68 -0
- package/dist/tools/tool-output-store.js +102 -0
- package/dist/ui/ActivityLine.d.ts +48 -0
- package/dist/ui/ActivityLine.js +22 -0
- package/dist/ui/App.d.ts +11 -4
- package/dist/ui/App.js +359 -37
- package/dist/ui/BottomStatusRow.d.ts +65 -0
- package/dist/ui/BottomStatusRow.js +17 -0
- package/dist/ui/Composer.d.ts +1 -6
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +1 -15
- package/dist/ui/PromptInput.js +1 -38
- package/dist/ui/StatusBar.d.ts +37 -0
- package/dist/ui/StatusBar.js +5 -3
- package/dist/ui/TeamPanel.d.ts +5 -4
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/TodoPanel.d.ts +5 -4
- package/dist/ui/Transcript.d.ts +26 -3
- package/dist/ui/Transcript.js +76 -10
- package/dist/ui/UpdateLine.d.ts +44 -0
- package/dist/ui/UpdateLine.js +129 -0
- package/dist/ui/activity-phrases.d.ts +33 -0
- package/dist/ui/activity-phrases.js +50 -0
- package/dist/ui/entries/AssistantEntry.d.ts +20 -0
- package/dist/ui/entries/AssistantEntry.js +3 -2
- package/dist/ui/entries/DiffView.d.ts +67 -0
- package/dist/ui/entries/DiffView.js +103 -0
- package/dist/ui/entries/FastCard.d.ts +53 -0
- package/dist/ui/entries/FastCard.js +87 -0
- package/dist/ui/entries/RetryCard.d.ts +56 -0
- package/dist/ui/entries/RetryCard.js +60 -0
- package/dist/ui/entries/TeamCard.js +6 -1
- package/dist/ui/entries/ToolCard.d.ts +38 -1
- package/dist/ui/entries/ToolCard.js +68 -5
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/AppShell.d.ts +13 -22
- package/dist/ui/layout/AppShell.js +3 -38
- package/dist/ui/layout/virtual-window.d.ts +1 -1
- package/dist/ui/layout/virtual-window.js +109 -5
- package/dist/ui/overlays/HelpOverlay.js +16 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
- package/dist/ui/overlays/SettingsScreen.js +147 -1
- package/dist/ui/transcript-text.js +27 -0
- package/dist/ui/use-wheel-routing.d.ts +16 -32
- package/dist/ui/use-wheel-routing.js +21 -38
- package/dist/update/classify-failure.d.ts +32 -0
- package/dist/update/classify-failure.js +60 -0
- package/dist/update/install-lock.d.ts +55 -0
- package/dist/update/install-lock.js +184 -0
- package/dist/update/install-source.d.ts +97 -0
- package/dist/update/install-source.js +218 -0
- package/dist/update/installer.d.ts +108 -0
- package/dist/update/installer.js +242 -0
- package/dist/update/limits.d.ts +111 -0
- package/dist/update/limits.js +111 -0
- package/dist/update/npm-view.d.ts +62 -0
- package/dist/update/npm-view.js +133 -0
- package/dist/update/registry.d.ts +52 -0
- package/dist/update/registry.js +161 -0
- package/dist/update/semver.d.ts +89 -0
- package/dist/update/semver.js +295 -0
- package/dist/update/service.d.ts +193 -0
- package/dist/update/service.js +636 -0
- package/dist/update/state.d.ts +103 -0
- package/dist/update/state.js +142 -0
- package/dist/update/types.d.ts +169 -0
- package/dist/update/types.js +68 -0
- package/package.json +3 -3
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime shapes for the fast tier (fast-model-tier §5.2 / §5.3).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: `src/fast/**` is inside the glyph scanner's scope.
|
|
5
|
+
*
|
|
6
|
+
* `FastEvent` is a CLI-LOCAL stream and must stay that way (D-10 / I-1), for
|
|
7
|
+
* the reason `TeamEvent` and `TodoEvent` each record: `packages/core` freezes
|
|
8
|
+
* its runtime export list (`public-api.test.ts`) and forbids host coupling
|
|
9
|
+
* (`no-host-coupling.test.ts`), so a new member of core's `AgentEvent` union
|
|
10
|
+
* would break the first and teach core about a host shape it has no business
|
|
11
|
+
* defining. This whole feature therefore changes ZERO files under
|
|
12
|
+
* `packages/core/` (AC-30).
|
|
13
|
+
*/
|
|
14
|
+
import type { ModelRef, ThinkingLevel, TokenUsage } from '@aragon-agent/core';
|
|
15
|
+
/** Which model runs a piece of work. Two tiers, and deliberately not three. */
|
|
16
|
+
export type FastTierName = 'main' | 'fast';
|
|
17
|
+
/**
|
|
18
|
+
* The resolved fast tier, or the reason there isn't one.
|
|
19
|
+
*
|
|
20
|
+
* A DISCRIMINATED UNION rather than a nullable `ModelRef`, because every UI
|
|
21
|
+
* surface has to be able to say WHY the tier is off: "enabled but no model" and
|
|
22
|
+
* "enabled but no key for openai" send the user to two different settings.
|
|
23
|
+
*/
|
|
24
|
+
export type FastTier = {
|
|
25
|
+
ok: true;
|
|
26
|
+
ref: ModelRef;
|
|
27
|
+
thinkingLevel: ThinkingLevel;
|
|
28
|
+
sameAsMain: boolean;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
reason: FastTierOffReason;
|
|
32
|
+
};
|
|
33
|
+
export type FastTierOffReason = 'disabled' | 'no_model' | 'no_adapter' | 'no_key';
|
|
34
|
+
/**
|
|
35
|
+
* One completed turn of the lead, as the reviewer records it.
|
|
36
|
+
*
|
|
37
|
+
* `sealed` is what §3.5.1 turns on: a frame is complete only once every
|
|
38
|
+
* `tool_execution_end` of its batch has landed, because `isError` and `ms` do
|
|
39
|
+
* not exist before then (`agent-loop.ts:249-256`). A digest built from an
|
|
40
|
+
* unsealed frame is structurally missing the single fact a reviewer most needs -
|
|
41
|
+
* whether the batch it is reviewing just failed (RV-2 / D-22).
|
|
42
|
+
*/
|
|
43
|
+
export interface TurnFrame {
|
|
44
|
+
/** 1-based within the run. */
|
|
45
|
+
index: number;
|
|
46
|
+
/** <= FAST_LIMITS.turnTailChars, appended and clipped per `text_delta`. */
|
|
47
|
+
textTail: string;
|
|
48
|
+
tools: Array<{
|
|
49
|
+
name: string;
|
|
50
|
+
arg?: string;
|
|
51
|
+
isError?: boolean;
|
|
52
|
+
ms?: number;
|
|
53
|
+
}>;
|
|
54
|
+
/** `formatStreamError` output, when the turn errored. */
|
|
55
|
+
error?: string;
|
|
56
|
+
/** Complete, and safe to digest (§3.5.1). */
|
|
57
|
+
sealed: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** One review, in whatever state it reached. Rendered by `FastCard`. */
|
|
60
|
+
export interface FastReview {
|
|
61
|
+
/** 1-based within the SESSION, so `/fast status` can count them. */
|
|
62
|
+
index: number;
|
|
63
|
+
runId: number;
|
|
64
|
+
/** The turn that triggered it. */
|
|
65
|
+
turn: number;
|
|
66
|
+
/** The resolved fast model id, for the card and the injected wrapper. */
|
|
67
|
+
model: string;
|
|
68
|
+
kind: 'ok' | 'advice' | 'empty' | 'failed' | 'dropped';
|
|
69
|
+
/** Present only for `advice`. */
|
|
70
|
+
text?: string;
|
|
71
|
+
/** Failure reason / drop reason. */
|
|
72
|
+
detail?: string;
|
|
73
|
+
durationMs: number;
|
|
74
|
+
usage?: TokenUsage;
|
|
75
|
+
injected: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** Everything `/fast status`, the chip and the settings line read. */
|
|
78
|
+
export interface FastSnapshot {
|
|
79
|
+
/** The tier resolved AND the live switch is on. */
|
|
80
|
+
live: boolean;
|
|
81
|
+
model: string;
|
|
82
|
+
sameAsMain: boolean;
|
|
83
|
+
reviews: number;
|
|
84
|
+
/** The live session budget, for the `n/N` render (fast-model-tier-hardening
|
|
85
|
+
* §5). Read in the SAME expression as `reviews`, so the numerator and the
|
|
86
|
+
* denominator cannot describe different moments. */
|
|
87
|
+
reviewBudget: number;
|
|
88
|
+
/**
|
|
89
|
+
* Whether the session budget is exhausted. CARRIED, NOT DERIVED: callers MUST
|
|
90
|
+
* NOT recompute `reviews >= reviewBudget`. In `offFastStatus()` both numbers
|
|
91
|
+
* are `0` placeholders for "there is no tier", and that comparison would
|
|
92
|
+
* report a budget reached for a session that never had one (RV-H10).
|
|
93
|
+
*/
|
|
94
|
+
budgetReached: boolean;
|
|
95
|
+
/** Fast-tier children dispatched this session. */
|
|
96
|
+
delegated: number;
|
|
97
|
+
usage: TokenUsage;
|
|
98
|
+
/**
|
|
99
|
+
* No price table for `model` (C-11 / RV-4).
|
|
100
|
+
*
|
|
101
|
+
* `ModelRegistry.buildRuntimeModel` returns `cost: { input: 0, output: 0 }`
|
|
102
|
+
* for a model the static table has never seen, and the fast tier is precisely
|
|
103
|
+
* where an unrecognised id is LIKELY. Rendering that as `$0.00` would make the
|
|
104
|
+
* feature look free while it is spending money.
|
|
105
|
+
*/
|
|
106
|
+
pricingUnknown: boolean;
|
|
107
|
+
/** A review call is open right now. */
|
|
108
|
+
inFlight: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The CLI-local event stream. `FastReviewer` emits; `FastWiring` forwards;
|
|
112
|
+
* `AgentController.subscribeFast` exposes; `App` and `runHeadless` consume.
|
|
113
|
+
*/
|
|
114
|
+
export type FastEvent = {
|
|
115
|
+
type: 'review_start';
|
|
116
|
+
index: number;
|
|
117
|
+
turn: number;
|
|
118
|
+
} | {
|
|
119
|
+
type: 'review_end';
|
|
120
|
+
review: FastReview;
|
|
121
|
+
} | {
|
|
122
|
+
type: 'usage';
|
|
123
|
+
usage: TokenUsage;
|
|
124
|
+
} | {
|
|
125
|
+
type: 'tier_changed';
|
|
126
|
+
snapshot: FastSnapshot;
|
|
127
|
+
};
|
|
128
|
+
export type FastEventListener = (event: FastEvent) => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime shapes for the fast tier (fast-model-tier §5.2 / §5.3).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: `src/fast/**` is inside the glyph scanner's scope.
|
|
5
|
+
*
|
|
6
|
+
* `FastEvent` is a CLI-LOCAL stream and must stay that way (D-10 / I-1), for
|
|
7
|
+
* the reason `TeamEvent` and `TodoEvent` each record: `packages/core` freezes
|
|
8
|
+
* its runtime export list (`public-api.test.ts`) and forbids host coupling
|
|
9
|
+
* (`no-host-coupling.test.ts`), so a new member of core's `AgentEvent` union
|
|
10
|
+
* would break the first and teach core about a host shape it has no business
|
|
11
|
+
* defining. This whole feature therefore changes ZERO files under
|
|
12
|
+
* `packages/core/` (AC-30).
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FastWiring — the controller-side glue for the fast tier (fast-model-tier §3.1
|
|
3
|
+
* / §3.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/fast/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* THIS CLASS EXISTS FOR A BUDGET REASON, NOT AN AESTHETIC ONE (C-12 / RV-5).
|
|
8
|
+
* `CLAUDE.md` caps a source file at 1000 lines and `controller.ts` was at 960
|
|
9
|
+
* before this feature, which handed it nine new responsibilities. `FastWiring`
|
|
10
|
+
* owns the flag pair, the tier cache, the reviewer's lifetime and the
|
|
11
|
+
* `resolveTier` closure; `AgentController` keeps one field and four thin
|
|
12
|
+
* forwarders. It is still CLI-local and still holds no `ui/` import.
|
|
13
|
+
*
|
|
14
|
+
* TWO FLAGS AND ONE LIVE PREDICATE (§3.3), which is the shape every optional
|
|
15
|
+
* subsystem in this package already uses:
|
|
16
|
+
*
|
|
17
|
+
* fastRegistered decided ONCE at construction = resolveFastTier(config).ok
|
|
18
|
+
* fastEnabled live, flipped by /fast on|off = starts equal to registered
|
|
19
|
+
* available() derived, read at EVERY use = registered && enabled
|
|
20
|
+
* && resolveFastTier(config).ok
|
|
21
|
+
*
|
|
22
|
+
* THE THIRD TERM IS NOT REDUNDANT, and leaving it out is RV-3. `fastRegistered`
|
|
23
|
+
* is frozen by C-2, but the tier it was derived from is not: `fast.provider: ''`
|
|
24
|
+
* and `fast.baseUrl: ''` INHERIT from `config.provider` / `config.baseUrl`, and
|
|
25
|
+
* the key check reads `apiKeys` - all of which `/model`, `/provider`, `/reload`,
|
|
26
|
+
* the settings screen's save and a one-shot key edit rewrite mid-session. So
|
|
27
|
+
* `registered && enabled` can be true at the exact moment `resolveFastTier` has
|
|
28
|
+
* started returning `no_key`, at which point `task` keeps advertising
|
|
29
|
+
* `model:"fast"`, the child is built against a provider with no key, and it dies
|
|
30
|
+
* inside its own loop with an error naming neither the tier nor the setting that
|
|
31
|
+
* caused it (`agent-loop.ts:140-145`).
|
|
32
|
+
*/
|
|
33
|
+
import { type AgentEvent, type AssistantMessage, type LLMRequest, type ModelRef, type ThinkingLevel } from '@aragon-agent/core';
|
|
34
|
+
import type { CliConfig } from '../config/schema.js';
|
|
35
|
+
import type { NoticeLevel } from '../agent/reducer.js';
|
|
36
|
+
import type { FastEventListener, FastSnapshot, FastTier, FastTierName } from './types.js';
|
|
37
|
+
export interface FastWiringDeps {
|
|
38
|
+
/** Read LIVE on every call - the controller replaces the object on mutation. */
|
|
39
|
+
getConfig: () => CliConfig;
|
|
40
|
+
/** `AgentController.hasApiKey`, so there is exactly one key resolver. */
|
|
41
|
+
hasKey: (providerId: string) => boolean;
|
|
42
|
+
getApiKey: (providerId: string) => string | undefined;
|
|
43
|
+
/** Whether the static price table knows this model (C-11 / RV-4). */
|
|
44
|
+
isPricedModel: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => boolean;
|
|
45
|
+
/** The lead agent's event stream and steering surface. */
|
|
46
|
+
subscribe: (listener: (event: AgentEvent) => void) => () => void;
|
|
47
|
+
/**
|
|
48
|
+
* NOT THE REVIEWER'S TRANSPORT SINCE ROUND 2 (W2 / §3.2): the reviewer calls
|
|
49
|
+
* through `getFastRegistry()`, which is fail-fast and does not observe the
|
|
50
|
+
* user's `/retry`. Retained because the only alternative is a `controller.ts`
|
|
51
|
+
* edit that the round-2 change plan forbids (D-H13), and because the next
|
|
52
|
+
* caller that needs the LEAD's registry from here already has it. Do not
|
|
53
|
+
* "clean up" without reading §3.2.
|
|
54
|
+
*/
|
|
55
|
+
complete: (providerId: string, request: LLMRequest) => Promise<AssistantMessage>;
|
|
56
|
+
steer: (text: string) => void;
|
|
57
|
+
isRunning: () => boolean;
|
|
58
|
+
isAbortRequested: () => boolean;
|
|
59
|
+
userSteerCount: () => number;
|
|
60
|
+
clearAllQueues: () => void;
|
|
61
|
+
notify: (level: NoticeLevel, text: string) => void;
|
|
62
|
+
/** Recompose the system prompt: `<fast_tier>` interpolates the model id, so a
|
|
63
|
+
* tier that stops resolving must not leave the prompt naming it (§3.9). */
|
|
64
|
+
onPromptChanged: () => void;
|
|
65
|
+
}
|
|
66
|
+
/** What `/fast status`, the settings line and the status chip all read. */
|
|
67
|
+
export interface FastStatus {
|
|
68
|
+
registered: boolean;
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
tier: FastTier;
|
|
71
|
+
snapshot: FastSnapshot;
|
|
72
|
+
}
|
|
73
|
+
/** The status of a session that never had the tier (wiring is `null`). */
|
|
74
|
+
export declare function offFastStatus(): FastStatus;
|
|
75
|
+
export declare class FastWiring {
|
|
76
|
+
private readonly deps;
|
|
77
|
+
/** Scope `fast` since round 2 (W4), for the reason `FastReviewer` records. */
|
|
78
|
+
private readonly log;
|
|
79
|
+
private readonly listeners;
|
|
80
|
+
private readonly reviewer;
|
|
81
|
+
/** The reviewer's OWN transport (W2 / §3.2). Lazy - see `getFastRegistry()`. */
|
|
82
|
+
private fastRegistry;
|
|
83
|
+
/** Frozen at construction (C-2): the `task` schema cannot grow a field later. */
|
|
84
|
+
private readonly registered;
|
|
85
|
+
private enabled;
|
|
86
|
+
/** The last resolution. Recomputed by `onConfigChanged()`, never trusted stale. */
|
|
87
|
+
private tier;
|
|
88
|
+
/** Warned once per TRANSITION, so a broken tier does not shout every turn. */
|
|
89
|
+
private lastReportedReason;
|
|
90
|
+
private delegated;
|
|
91
|
+
constructor(deps: FastWiringDeps);
|
|
92
|
+
/**
|
|
93
|
+
* The registry the REVIEW calls through — separate from the lead's, and
|
|
94
|
+
* fail-fast.
|
|
95
|
+
*
|
|
96
|
+
* THE PROBLEM IT SOLVES. `ProviderRegistry.complete()` routes through a
|
|
97
|
+
* retrying `stream()`, the policy is per-registry, and `LLMRequest` carries no
|
|
98
|
+
* override (IF-3) - so before this the background advisory inherited the
|
|
99
|
+
* user's `retry` config, up to ten attempts with backoff. During the one
|
|
100
|
+
* condition where the fast tier is most likely to be failing (a rate-limited
|
|
101
|
+
* or overloaded provider), an unrequested call competed with the user's actual
|
|
102
|
+
* work for the same quota. It needs NO core change: `initProviders()` is
|
|
103
|
+
* exported and returns a FRESH registry per call with all three adapters
|
|
104
|
+
* registered, which is exactly what `resolveFastTier` rule 4 gates on.
|
|
105
|
+
*
|
|
106
|
+
* IT DOES NOT OBSERVE THE USER'S `retry` CONFIG OR `/retry`, deliberately
|
|
107
|
+
* (D-H4): that policy states how hard to fight for the USER'S answer, and
|
|
108
|
+
* inheriting it for a call nobody asked for is the bug. Logged at construction
|
|
109
|
+
* so a support reader can see which policy a review ran under.
|
|
110
|
+
*
|
|
111
|
+
* LAZY, SO OFF STAYS BYTE-IDENTICAL (I-2 / AC-H13). Nothing is constructed
|
|
112
|
+
* until a review actually calls, so a session that never uses the tier
|
|
113
|
+
* allocates no registry and registers no adapters.
|
|
114
|
+
*/
|
|
115
|
+
private getFastRegistry;
|
|
116
|
+
isRegistered(): boolean;
|
|
117
|
+
isEnabled(): boolean;
|
|
118
|
+
/** §3.3's live predicate. THE THIRD TERM IS THE POINT - see the header. */
|
|
119
|
+
available(): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Whether `model: "fast"` on `task` is honoured right now.
|
|
122
|
+
*
|
|
123
|
+
* Evaluated AT DISPATCH TIME, never snapshot (§3.4 / RV-3). `fast.delegate:
|
|
124
|
+
* false` and "the tier stopped resolving twenty minutes ago" are the same
|
|
125
|
+
* observable to the model - a downgrade, counted and reported - which is the
|
|
126
|
+
* honest reading of both.
|
|
127
|
+
*/
|
|
128
|
+
delegationAvailable(): boolean;
|
|
129
|
+
setEnabled(enabled: boolean): void;
|
|
130
|
+
/**
|
|
131
|
+
* Re-resolve the tier after ANY config mutation (§3.2 rule 8 / §3.9 / RV-3).
|
|
132
|
+
*
|
|
133
|
+
* Called from `setModel`, `setApiKey`, `setFastConfig`, `/reload` and the
|
|
134
|
+
* settings screen's save - the five live inputs rules 2, 5 and 6 read. It also
|
|
135
|
+
* recomposes the system prompt, because `<fast_tier>` interpolates the
|
|
136
|
+
* resolved model id and a prompt naming a model the tier will not use is worse
|
|
137
|
+
* than no prompt at all.
|
|
138
|
+
*/
|
|
139
|
+
onConfigChanged(): void;
|
|
140
|
+
/**
|
|
141
|
+
* The startup notice for a configuration mistake that is otherwise invisible
|
|
142
|
+
* (§3.3). Raised once, at construction, through the controller's `notify`.
|
|
143
|
+
*/
|
|
144
|
+
startupNotice(): string | null;
|
|
145
|
+
/**
|
|
146
|
+
* The `ModelRef` and thinking level one subagent tier runs on.
|
|
147
|
+
*
|
|
148
|
+
* `'fast'` FALLS BACK TO MAIN when delegation is not available, so the two
|
|
149
|
+
* guards agree BY CONSTRUCTION: the normalizer can never produce a
|
|
150
|
+
* `tier: 'fast'` spec this factory would refuse, and this factory can never
|
|
151
|
+
* build a child against a `ModelRef` the tier no longer resolves (RV-3).
|
|
152
|
+
*/
|
|
153
|
+
resolveTier(tier: FastTierName): {
|
|
154
|
+
ref: ModelRef;
|
|
155
|
+
thinkingLevel: ThinkingLevel;
|
|
156
|
+
};
|
|
157
|
+
/** The resolved fast `ModelRef`, or `null` - for per-tier cost lookup (§3.6). */
|
|
158
|
+
fastRef(): ModelRef | null;
|
|
159
|
+
/** Count fast-tier children so `/fast status` can answer "is this saving me
|
|
160
|
+
* anything?" — the aggregate on the status bar cannot. */
|
|
161
|
+
noteDelegated(count: number): void;
|
|
162
|
+
setGoal(text: string): void;
|
|
163
|
+
abort(): void;
|
|
164
|
+
dispose(): void;
|
|
165
|
+
getTier(): FastTier;
|
|
166
|
+
snapshot(): FastSnapshot;
|
|
167
|
+
status(): FastStatus;
|
|
168
|
+
subscribe(listener: FastEventListener): () => void;
|
|
169
|
+
private emit;
|
|
170
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FastWiring — the controller-side glue for the fast tier (fast-model-tier §3.1
|
|
3
|
+
* / §3.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/fast/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* THIS CLASS EXISTS FOR A BUDGET REASON, NOT AN AESTHETIC ONE (C-12 / RV-5).
|
|
8
|
+
* `CLAUDE.md` caps a source file at 1000 lines and `controller.ts` was at 960
|
|
9
|
+
* before this feature, which handed it nine new responsibilities. `FastWiring`
|
|
10
|
+
* owns the flag pair, the tier cache, the reviewer's lifetime and the
|
|
11
|
+
* `resolveTier` closure; `AgentController` keeps one field and four thin
|
|
12
|
+
* forwarders. It is still CLI-local and still holds no `ui/` import.
|
|
13
|
+
*
|
|
14
|
+
* TWO FLAGS AND ONE LIVE PREDICATE (§3.3), which is the shape every optional
|
|
15
|
+
* subsystem in this package already uses:
|
|
16
|
+
*
|
|
17
|
+
* fastRegistered decided ONCE at construction = resolveFastTier(config).ok
|
|
18
|
+
* fastEnabled live, flipped by /fast on|off = starts equal to registered
|
|
19
|
+
* available() derived, read at EVERY use = registered && enabled
|
|
20
|
+
* && resolveFastTier(config).ok
|
|
21
|
+
*
|
|
22
|
+
* THE THIRD TERM IS NOT REDUNDANT, and leaving it out is RV-3. `fastRegistered`
|
|
23
|
+
* is frozen by C-2, but the tier it was derived from is not: `fast.provider: ''`
|
|
24
|
+
* and `fast.baseUrl: ''` INHERIT from `config.provider` / `config.baseUrl`, and
|
|
25
|
+
* the key check reads `apiKeys` - all of which `/model`, `/provider`, `/reload`,
|
|
26
|
+
* the settings screen's save and a one-shot key edit rewrite mid-session. So
|
|
27
|
+
* `registered && enabled` can be true at the exact moment `resolveFastTier` has
|
|
28
|
+
* started returning `no_key`, at which point `task` keeps advertising
|
|
29
|
+
* `model:"fast"`, the child is built against a provider with no key, and it dies
|
|
30
|
+
* inside its own loop with an error naming neither the tier nor the setting that
|
|
31
|
+
* caused it (`agent-loop.ts:140-145`).
|
|
32
|
+
*/
|
|
33
|
+
import { DEFAULT_RETRY_POLICY, initProviders, } from '@aragon-agent/core';
|
|
34
|
+
import { getLogger } from '../logging/logger.js';
|
|
35
|
+
import { describeFastTierProblem, fastProviderOf, resolveFastTier } from './resolve.js';
|
|
36
|
+
import { FastReviewer } from './reviewer.js';
|
|
37
|
+
/**
|
|
38
|
+
* One retry, not ten and not zero (D-H3).
|
|
39
|
+
*
|
|
40
|
+
* ZERO loses a review to a single connection reset; TEN is what makes a
|
|
41
|
+
* background advisory compete with the user's own work for a rate-limited
|
|
42
|
+
* provider's quota, which is the whole defect W2 exists to close. It spreads
|
|
43
|
+
* `DEFAULT_RETRY_POLICY` rather than replacing it, so `respectRetryAfter` and
|
|
44
|
+
* the transient classification stay exactly as the lead's - only the attempt
|
|
45
|
+
* count differs.
|
|
46
|
+
*/
|
|
47
|
+
const FAST_RETRY_POLICY = { ...DEFAULT_RETRY_POLICY, maxRetries: 1 };
|
|
48
|
+
/** The status of a session that never had the tier (wiring is `null`). */
|
|
49
|
+
export function offFastStatus() {
|
|
50
|
+
return {
|
|
51
|
+
registered: false,
|
|
52
|
+
enabled: false,
|
|
53
|
+
tier: { ok: false, reason: 'disabled' },
|
|
54
|
+
snapshot: {
|
|
55
|
+
live: false,
|
|
56
|
+
model: '',
|
|
57
|
+
sameAsMain: false,
|
|
58
|
+
reviews: 0,
|
|
59
|
+
// BOTH ARE PLACEHOLDERS FOR "THERE IS NO TIER", NOT A LIMIT (RV-H10).
|
|
60
|
+
// `reviews >= reviewBudget` here is `0 >= 0` -> `true`, which is why
|
|
61
|
+
// `budgetReached` is CARRIED and callers must never re-derive it.
|
|
62
|
+
reviewBudget: 0,
|
|
63
|
+
budgetReached: false,
|
|
64
|
+
delegated: 0,
|
|
65
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
66
|
+
pricingUnknown: false,
|
|
67
|
+
inFlight: false,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export class FastWiring {
|
|
72
|
+
deps;
|
|
73
|
+
/** Scope `fast` since round 2 (W4), for the reason `FastReviewer` records. */
|
|
74
|
+
log = getLogger().child('fast');
|
|
75
|
+
listeners = new Set();
|
|
76
|
+
reviewer;
|
|
77
|
+
/** The reviewer's OWN transport (W2 / §3.2). Lazy - see `getFastRegistry()`. */
|
|
78
|
+
fastRegistry = null;
|
|
79
|
+
/** Frozen at construction (C-2): the `task` schema cannot grow a field later. */
|
|
80
|
+
registered;
|
|
81
|
+
enabled;
|
|
82
|
+
/** The last resolution. Recomputed by `onConfigChanged()`, never trusted stale. */
|
|
83
|
+
tier;
|
|
84
|
+
/** Warned once per TRANSITION, so a broken tier does not shout every turn. */
|
|
85
|
+
lastReportedReason = null;
|
|
86
|
+
delegated = 0;
|
|
87
|
+
constructor(deps) {
|
|
88
|
+
this.deps = deps;
|
|
89
|
+
this.tier = resolveFastTier(deps.getConfig(), deps.hasKey);
|
|
90
|
+
this.registered = this.tier.ok;
|
|
91
|
+
this.enabled = this.registered;
|
|
92
|
+
this.lastReportedReason = this.tier.ok ? null : this.tier.reason;
|
|
93
|
+
this.log.info('fast_tier_resolved', {
|
|
94
|
+
enabled: deps.getConfig().fast.enabled,
|
|
95
|
+
provider: this.tier.ok ? this.tier.ref.providerId : fastProviderOf(deps.getConfig()),
|
|
96
|
+
model: this.tier.ok ? this.tier.ref.modelId : deps.getConfig().fast.model,
|
|
97
|
+
sameAsMain: this.tier.ok ? this.tier.sameAsMain : false,
|
|
98
|
+
...(this.tier.ok ? {} : { reason: this.tier.reason }),
|
|
99
|
+
});
|
|
100
|
+
this.reviewer = new FastReviewer({
|
|
101
|
+
subscribe: deps.subscribe,
|
|
102
|
+
// W2: the reviewer's own fail-fast registry, NOT `deps.complete`. Bound
|
|
103
|
+
// through a closure so the registry stays lazy - the reviewer holds no
|
|
104
|
+
// reference to it and never learns a second one exists.
|
|
105
|
+
complete: (providerId, request) => this.getFastRegistry().complete(providerId, request),
|
|
106
|
+
steer: deps.steer,
|
|
107
|
+
isRunning: deps.isRunning,
|
|
108
|
+
isAbortRequested: deps.isAbortRequested,
|
|
109
|
+
userSteerCount: deps.userSteerCount,
|
|
110
|
+
clearAllQueues: deps.clearAllQueues,
|
|
111
|
+
getTier: () => this.tier,
|
|
112
|
+
getConfig: () => this.deps.getConfig().fast,
|
|
113
|
+
available: () => this.available(),
|
|
114
|
+
getApiKey: deps.getApiKey,
|
|
115
|
+
emit: (event) => this.emit(event),
|
|
116
|
+
notify: deps.notify,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// -----------------------------------------------------------------------
|
|
120
|
+
// The reviewer's transport (W2 / §3.2)
|
|
121
|
+
// -----------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* The registry the REVIEW calls through — separate from the lead's, and
|
|
124
|
+
* fail-fast.
|
|
125
|
+
*
|
|
126
|
+
* THE PROBLEM IT SOLVES. `ProviderRegistry.complete()` routes through a
|
|
127
|
+
* retrying `stream()`, the policy is per-registry, and `LLMRequest` carries no
|
|
128
|
+
* override (IF-3) - so before this the background advisory inherited the
|
|
129
|
+
* user's `retry` config, up to ten attempts with backoff. During the one
|
|
130
|
+
* condition where the fast tier is most likely to be failing (a rate-limited
|
|
131
|
+
* or overloaded provider), an unrequested call competed with the user's actual
|
|
132
|
+
* work for the same quota. It needs NO core change: `initProviders()` is
|
|
133
|
+
* exported and returns a FRESH registry per call with all three adapters
|
|
134
|
+
* registered, which is exactly what `resolveFastTier` rule 4 gates on.
|
|
135
|
+
*
|
|
136
|
+
* IT DOES NOT OBSERVE THE USER'S `retry` CONFIG OR `/retry`, deliberately
|
|
137
|
+
* (D-H4): that policy states how hard to fight for the USER'S answer, and
|
|
138
|
+
* inheriting it for a call nobody asked for is the bug. Logged at construction
|
|
139
|
+
* so a support reader can see which policy a review ran under.
|
|
140
|
+
*
|
|
141
|
+
* LAZY, SO OFF STAYS BYTE-IDENTICAL (I-2 / AC-H13). Nothing is constructed
|
|
142
|
+
* until a review actually calls, so a session that never uses the tier
|
|
143
|
+
* allocates no registry and registers no adapters.
|
|
144
|
+
*/
|
|
145
|
+
getFastRegistry() {
|
|
146
|
+
if (this.fastRegistry === null) {
|
|
147
|
+
this.fastRegistry = initProviders({ retryPolicy: FAST_RETRY_POLICY });
|
|
148
|
+
this.log.info('fast_registry_created', { maxRetries: FAST_RETRY_POLICY.maxRetries });
|
|
149
|
+
}
|
|
150
|
+
return this.fastRegistry;
|
|
151
|
+
}
|
|
152
|
+
// -----------------------------------------------------------------------
|
|
153
|
+
// Flags and the live predicate
|
|
154
|
+
// -----------------------------------------------------------------------
|
|
155
|
+
isRegistered() {
|
|
156
|
+
return this.registered;
|
|
157
|
+
}
|
|
158
|
+
isEnabled() {
|
|
159
|
+
return this.enabled;
|
|
160
|
+
}
|
|
161
|
+
/** §3.3's live predicate. THE THIRD TERM IS THE POINT - see the header. */
|
|
162
|
+
available() {
|
|
163
|
+
return this.registered && this.enabled && this.tier.ok;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Whether `model: "fast"` on `task` is honoured right now.
|
|
167
|
+
*
|
|
168
|
+
* Evaluated AT DISPATCH TIME, never snapshot (§3.4 / RV-3). `fast.delegate:
|
|
169
|
+
* false` and "the tier stopped resolving twenty minutes ago" are the same
|
|
170
|
+
* observable to the model - a downgrade, counted and reported - which is the
|
|
171
|
+
* honest reading of both.
|
|
172
|
+
*/
|
|
173
|
+
delegationAvailable() {
|
|
174
|
+
return this.available() && this.deps.getConfig().fast.delegate === true;
|
|
175
|
+
}
|
|
176
|
+
setEnabled(enabled) {
|
|
177
|
+
this.enabled = enabled;
|
|
178
|
+
this.deps.onPromptChanged();
|
|
179
|
+
this.emit({ type: 'tier_changed', snapshot: this.snapshot() });
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Re-resolve the tier after ANY config mutation (§3.2 rule 8 / §3.9 / RV-3).
|
|
183
|
+
*
|
|
184
|
+
* Called from `setModel`, `setApiKey`, `setFastConfig`, `/reload` and the
|
|
185
|
+
* settings screen's save - the five live inputs rules 2, 5 and 6 read. It also
|
|
186
|
+
* recomposes the system prompt, because `<fast_tier>` interpolates the
|
|
187
|
+
* resolved model id and a prompt naming a model the tier will not use is worse
|
|
188
|
+
* than no prompt at all.
|
|
189
|
+
*/
|
|
190
|
+
onConfigChanged() {
|
|
191
|
+
const next = resolveFastTier(this.deps.getConfig(), this.deps.hasKey);
|
|
192
|
+
const before = this.tier;
|
|
193
|
+
this.tier = next;
|
|
194
|
+
const reason = next.ok ? null : next.reason;
|
|
195
|
+
if (reason !== this.lastReportedReason) {
|
|
196
|
+
this.lastReportedReason = reason;
|
|
197
|
+
if (reason && before.ok) {
|
|
198
|
+
// A tier that stopped resolving MID-SESSION is the case R-14 is about:
|
|
199
|
+
// without a word here the only symptom is a child dying with
|
|
200
|
+
// `No API key available for provider "openai"`.
|
|
201
|
+
this.log.warn('fast_tier_unavailable', { reason });
|
|
202
|
+
const text = describeFastTierProblem(next, fastProviderOf(this.deps.getConfig()));
|
|
203
|
+
if (text)
|
|
204
|
+
this.deps.notify('warn', text);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
this.deps.onPromptChanged();
|
|
208
|
+
this.emit({ type: 'tier_changed', snapshot: this.snapshot() });
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The startup notice for a configuration mistake that is otherwise invisible
|
|
212
|
+
* (§3.3). Raised once, at construction, through the controller's `notify`.
|
|
213
|
+
*/
|
|
214
|
+
startupNotice() {
|
|
215
|
+
return describeFastTierProblem(this.tier, fastProviderOf(this.deps.getConfig()));
|
|
216
|
+
}
|
|
217
|
+
// -----------------------------------------------------------------------
|
|
218
|
+
// Tier resolution for subagents (§3.4)
|
|
219
|
+
// -----------------------------------------------------------------------
|
|
220
|
+
/**
|
|
221
|
+
* The `ModelRef` and thinking level one subagent tier runs on.
|
|
222
|
+
*
|
|
223
|
+
* `'fast'` FALLS BACK TO MAIN when delegation is not available, so the two
|
|
224
|
+
* guards agree BY CONSTRUCTION: the normalizer can never produce a
|
|
225
|
+
* `tier: 'fast'` spec this factory would refuse, and this factory can never
|
|
226
|
+
* build a child against a `ModelRef` the tier no longer resolves (RV-3).
|
|
227
|
+
*/
|
|
228
|
+
resolveTier(tier) {
|
|
229
|
+
const config = this.deps.getConfig();
|
|
230
|
+
if (tier === 'fast' && this.delegationAvailable() && this.tier.ok) {
|
|
231
|
+
return { ref: this.tier.ref, thinkingLevel: this.tier.thinkingLevel };
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
ref: {
|
|
235
|
+
providerId: config.provider,
|
|
236
|
+
modelId: config.model,
|
|
237
|
+
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
238
|
+
},
|
|
239
|
+
thinkingLevel: config.thinkingLevel,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/** The resolved fast `ModelRef`, or `null` - for per-tier cost lookup (§3.6). */
|
|
243
|
+
fastRef() {
|
|
244
|
+
return this.tier.ok ? this.tier.ref : null;
|
|
245
|
+
}
|
|
246
|
+
/** Count fast-tier children so `/fast status` can answer "is this saving me
|
|
247
|
+
* anything?" — the aggregate on the status bar cannot. */
|
|
248
|
+
noteDelegated(count) {
|
|
249
|
+
if (count > 0)
|
|
250
|
+
this.delegated += count;
|
|
251
|
+
}
|
|
252
|
+
// -----------------------------------------------------------------------
|
|
253
|
+
// Reviewer lifetime
|
|
254
|
+
// -----------------------------------------------------------------------
|
|
255
|
+
setGoal(text) {
|
|
256
|
+
this.reviewer.setGoal(text);
|
|
257
|
+
}
|
|
258
|
+
abort() {
|
|
259
|
+
this.reviewer.abort();
|
|
260
|
+
}
|
|
261
|
+
dispose() {
|
|
262
|
+
this.reviewer.dispose();
|
|
263
|
+
this.listeners.clear();
|
|
264
|
+
}
|
|
265
|
+
// -----------------------------------------------------------------------
|
|
266
|
+
// Status and events
|
|
267
|
+
// -----------------------------------------------------------------------
|
|
268
|
+
getTier() {
|
|
269
|
+
return this.tier;
|
|
270
|
+
}
|
|
271
|
+
snapshot() {
|
|
272
|
+
const ref = this.tier.ok ? this.tier.ref : null;
|
|
273
|
+
return {
|
|
274
|
+
live: this.available(),
|
|
275
|
+
model: ref ? ref.modelId : '',
|
|
276
|
+
sameAsMain: this.tier.ok ? this.tier.sameAsMain : false,
|
|
277
|
+
// THE NUMERATOR, THE DENOMINATOR AND THE FLAG ARE READ IN ONE EXPRESSION,
|
|
278
|
+
// so they cannot describe different moments (§5 / R-H8). `reviewBudget`
|
|
279
|
+
// comes from live config, which is what lets `/fast budget <n>` change the
|
|
280
|
+
// denominator on the status chip with no restart.
|
|
281
|
+
reviews: this.reviewer.reviewCount(),
|
|
282
|
+
reviewBudget: this.reviewer.budgetLimit(),
|
|
283
|
+
budgetReached: this.reviewer.isBudgetReached(),
|
|
284
|
+
delegated: this.delegated,
|
|
285
|
+
usage: this.reviewer.sessionUsage(),
|
|
286
|
+
// UNKNOWN PRICING IS NOT ZERO PRICING (C-11 / RV-4). The fast tier is
|
|
287
|
+
// precisely where an unrecognised model id is likely, and rendering it as
|
|
288
|
+
// `$0.00` would make the feature look free while it is spending money.
|
|
289
|
+
pricingUnknown: ref ? !this.deps.isPricedModel(ref) : false,
|
|
290
|
+
inFlight: this.reviewer.isReviewInFlight(),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
status() {
|
|
294
|
+
return {
|
|
295
|
+
registered: this.registered,
|
|
296
|
+
enabled: this.enabled,
|
|
297
|
+
tier: this.tier,
|
|
298
|
+
snapshot: this.snapshot(),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
subscribe(listener) {
|
|
302
|
+
this.listeners.add(listener);
|
|
303
|
+
return () => this.listeners.delete(listener);
|
|
304
|
+
}
|
|
305
|
+
emit(event) {
|
|
306
|
+
for (const listener of [...this.listeners]) {
|
|
307
|
+
try {
|
|
308
|
+
listener(event);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// One bad subscriber must not take the run down with it — the rule
|
|
312
|
+
// `TeamRuntime.emit` already states.
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bin entry (cli-auto-update-hardening section 5.1.1).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - this file is inside the glyph scanner's scope (C-19).
|
|
5
|
+
*
|
|
6
|
+
* KEEP IT THIS SMALL. Every import here is a module that can crash before the
|
|
7
|
+
* guard that exists to survive crashes. `node:process` is the only static one,
|
|
8
|
+
* and `__tests__/update-wiring.test.ts` asserts that on the source text.
|
|
9
|
+
*
|
|
10
|
+
* `dist/cli.js` IS UNCHANGED AND KEEPS ITS SHEBANG (R-17 / AC-50). Only the
|
|
11
|
+
* `bin` mapping moves; `npm start` (`node dist/cli.js`) and any user script that
|
|
12
|
+
* invokes `dist/cli.js` directly keep working byte for byte.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|