@namzu/cli 2.6.0 → 3.0.0
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/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +15 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/run-flags.d.ts +23 -0
- package/dist/commands/run-flags.d.ts.map +1 -1
- package/dist/commands/run-flags.js +39 -0
- package/dist/commands/run-flags.js.map +1 -1
- package/dist/commands/run-stream.d.ts.map +1 -1
- package/dist/commands/run-stream.js +12 -6
- package/dist/commands/run-stream.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +12 -6
- package/dist/commands/run.js.map +1 -1
- package/dist/doctor/checks/chain.d.ts +32 -0
- package/dist/doctor/checks/chain.d.ts.map +1 -0
- package/dist/doctor/checks/chain.js +118 -0
- package/dist/doctor/checks/chain.js.map +1 -0
- package/dist/doctor/checks/index.d.ts +2 -1
- package/dist/doctor/checks/index.d.ts.map +1 -1
- package/dist/doctor/checks/index.js +6 -1
- package/dist/doctor/checks/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/providers/chain-capabilities.d.ts +80 -0
- package/dist/integrations/providers/chain-capabilities.d.ts.map +1 -0
- package/dist/integrations/providers/chain-capabilities.js +207 -0
- package/dist/integrations/providers/chain-capabilities.js.map +1 -0
- package/dist/integrations/providers/index.d.ts +2 -1
- package/dist/integrations/providers/index.d.ts.map +1 -1
- package/dist/integrations/providers/index.js +2 -1
- package/dist/integrations/providers/index.js.map +1 -1
- package/dist/integrations/providers/preferences.d.ts +84 -15
- package/dist/integrations/providers/preferences.d.ts.map +1 -1
- package/dist/integrations/providers/preferences.js +180 -25
- package/dist/integrations/providers/preferences.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +69 -13
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +1 -1
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/agent.d.ts +12 -0
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +70 -10
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/bottom-spacer.d.ts +72 -0
- package/dist/tui/bottom-spacer.d.ts.map +1 -0
- package/dist/tui/bottom-spacer.js +83 -0
- package/dist/tui/bottom-spacer.js.map +1 -0
- package/dist/tui/permission-timing.d.ts +35 -0
- package/dist/tui/permission-timing.d.ts.map +1 -0
- package/dist/tui/permission-timing.js +39 -0
- package/dist/tui/permission-timing.js.map +1 -0
- package/package.json +4 -4
|
@@ -1,33 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `~/.namzu/preferences.json` — the
|
|
2
|
+
* `~/.namzu/preferences.json` — the operator's provider chain.
|
|
3
3
|
*
|
|
4
|
-
* Schema (current =
|
|
4
|
+
* Schema (current = v3):
|
|
5
5
|
* {
|
|
6
|
-
* "version":
|
|
7
|
-
* "
|
|
8
|
-
*
|
|
6
|
+
* "version": 3,
|
|
7
|
+
* "providers": [ // ordered; index 0 is the primary
|
|
8
|
+
* { "id": "anthropic", "model": "claude-opus-4-7" },
|
|
9
|
+
* { "id": "openai" } // model omitted → the registry default
|
|
10
|
+
* ],
|
|
9
11
|
* "subagents": { "active": [...] } // instances reserved for subagent dispatch
|
|
10
12
|
* }
|
|
11
13
|
*
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* The chain is an ORDER the operator declares, replacing the single provider v2
|
|
15
|
+
* held. It is deliberately a plain readable array in a file the operator owns:
|
|
16
|
+
* the order has to be legible and editable without launching the TUI, which a
|
|
17
|
+
* value only reachable through an interactive picker is not.
|
|
18
|
+
*
|
|
19
|
+
* **Only `providers[0]` runs today.** The rest are validated and reported (see
|
|
20
|
+
* `readPreferences` and the `providers.chain` doctor check) but nothing falls
|
|
21
|
+
* over to them yet. That is the point of shipping this first: a chain you can
|
|
22
|
+
* declare, read back, and have checked is useful before failover exists, and it
|
|
23
|
+
* is what failover will consume.
|
|
24
|
+
*
|
|
25
|
+
* ## Versions
|
|
26
|
+
*
|
|
27
|
+
* v1 stored a peer instance as the default — a different primitive (subagent
|
|
28
|
+
* dispatch, not primary chat). It is still REFUSED rather than migrated, because
|
|
29
|
+
* mapping between the two semantics would surprise more than help.
|
|
30
|
+
*
|
|
31
|
+
* v2 stored a single `provider` + optional `model`. It IS migrated, on read, in
|
|
32
|
+
* memory: one provider is a one-element chain, which is unambiguous, and the
|
|
33
|
+
* distinction from v1 is exactly that. Migration does not rewrite the file — a
|
|
34
|
+
* reader with a write side effect would rewrite `$HOME` on a `--help`. The next
|
|
35
|
+
* `writePreferences` lands v3.
|
|
36
|
+
*
|
|
37
|
+
* Note the consequence: a v3 file read by an older namzu reports `needs-repick`
|
|
38
|
+
* rather than silently losing the tail, which is the safe direction.
|
|
17
39
|
*/
|
|
18
40
|
import { randomBytes } from 'node:crypto';
|
|
19
41
|
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
20
42
|
import { homedir } from 'node:os';
|
|
21
43
|
import { dirname, join } from 'node:path';
|
|
44
|
+
import { PROVIDER_REGISTRY } from './registry.js';
|
|
22
45
|
const FILE_MODE = 0o600;
|
|
23
46
|
const DIR_MODE = 0o700;
|
|
24
|
-
export const PREFERENCES_FILE_VERSION =
|
|
47
|
+
export const PREFERENCES_FILE_VERSION = 3;
|
|
48
|
+
/**
|
|
49
|
+
* How a member is named to the operator, by position.
|
|
50
|
+
*
|
|
51
|
+
* One function because three surfaces say it — validation, the doctor listing
|
|
52
|
+
* and the capability refusal — and a chain member called `fallback #1` in one
|
|
53
|
+
* message and `fallback 1` in the next reads as two different things.
|
|
54
|
+
*/
|
|
55
|
+
export function chainPositionName(index) {
|
|
56
|
+
return index === 0 ? 'primary provider' : `fallback #${index}`;
|
|
57
|
+
}
|
|
25
58
|
export class PreferencesError extends Error {
|
|
26
59
|
constructor(message) {
|
|
27
60
|
super(message);
|
|
28
61
|
this.name = 'PreferencesError';
|
|
29
62
|
}
|
|
30
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* The member that actually runs.
|
|
66
|
+
*
|
|
67
|
+
* A helper rather than `prefs.providers[0]` at each call site: the chain is
|
|
68
|
+
* guaranteed non-empty by validation, and every reader repeating an index
|
|
69
|
+
* lookup plus a non-null assertion would be four places to get that wrong.
|
|
70
|
+
*/
|
|
71
|
+
export function primaryProvider(prefs) {
|
|
72
|
+
// Optional-chained rather than indexed: a caller handing over an object of
|
|
73
|
+
// the old shape is a real case (a stale test fixture did exactly that), and
|
|
74
|
+
// a `TypeError: cannot read '0' of undefined` names neither the field nor
|
|
75
|
+
// the fix, while the error below names both.
|
|
76
|
+
const first = prefs.providers?.[0];
|
|
77
|
+
if (!first) {
|
|
78
|
+
// Unreachable through readPreferences/writePreferences, both of which
|
|
79
|
+
// refuse an empty chain. Reachable only from a hand-built object, and
|
|
80
|
+
// silently returning a placeholder there would put the caller on a
|
|
81
|
+
// provider nobody chose.
|
|
82
|
+
throw new PreferencesError('preferences.providers is empty — no provider to run');
|
|
83
|
+
}
|
|
84
|
+
return first;
|
|
85
|
+
}
|
|
31
86
|
export function preferencesPath(home = homedir()) {
|
|
32
87
|
return join(home, '.namzu', 'preferences.json');
|
|
33
88
|
}
|
|
@@ -59,6 +114,16 @@ export function readPreferences(home = homedir()) {
|
|
|
59
114
|
reason: 'preferences file uses an older schema (v1) — namzu now picks an LLM provider client directly. Please re-pick.',
|
|
60
115
|
};
|
|
61
116
|
}
|
|
117
|
+
if (v.version === 2) {
|
|
118
|
+
const migrated = migrateV2(parsed);
|
|
119
|
+
if (!migrated) {
|
|
120
|
+
throw new PreferencesError(`${path} has an unexpected shape for a v2 file`);
|
|
121
|
+
}
|
|
122
|
+
const invalid = describeInvalidChain(migrated.providers);
|
|
123
|
+
if (invalid)
|
|
124
|
+
return { status: 'needs-repick', reason: `${invalid} — please re-pick.` };
|
|
125
|
+
return { status: 'ok', prefs: migrated };
|
|
126
|
+
}
|
|
62
127
|
if (v.version !== PREFERENCES_FILE_VERSION) {
|
|
63
128
|
return {
|
|
64
129
|
status: 'needs-repick',
|
|
@@ -68,15 +133,27 @@ export function readPreferences(home = homedir()) {
|
|
|
68
133
|
if (!isPreferences(parsed)) {
|
|
69
134
|
throw new PreferencesError(`${path} has an unexpected shape`);
|
|
70
135
|
}
|
|
136
|
+
// Shape is right; the CONTENT may still name a provider that does not exist
|
|
137
|
+
// or repeat a member. Reported as `needs-repick` with the offending member
|
|
138
|
+
// named, not thrown: a bad chain is something the operator can fix by
|
|
139
|
+
// picking again, and it is the tail this is really for — an unusable
|
|
140
|
+
// fallback that only surfaces the day the primary goes down is the failure
|
|
141
|
+
// this check exists to prevent.
|
|
142
|
+
const invalid = describeInvalidChain(parsed.providers);
|
|
143
|
+
if (invalid)
|
|
144
|
+
return { status: 'needs-repick', reason: `${invalid} — please re-pick.` };
|
|
71
145
|
return { status: 'ok', prefs: parsed };
|
|
72
146
|
}
|
|
73
147
|
export function writePreferences(prefs, home = homedir()) {
|
|
74
148
|
if (prefs.version !== PREFERENCES_FILE_VERSION) {
|
|
75
149
|
throw new PreferencesError(`unsupported preferences version: ${String(prefs.version)} (expected ${PREFERENCES_FILE_VERSION})`);
|
|
76
150
|
}
|
|
77
|
-
if (
|
|
78
|
-
throw new PreferencesError('preferences.
|
|
151
|
+
if (!Array.isArray(prefs.providers) || prefs.providers.length === 0) {
|
|
152
|
+
throw new PreferencesError('preferences.providers must list at least one provider');
|
|
79
153
|
}
|
|
154
|
+
const invalid = describeInvalidChain(prefs.providers);
|
|
155
|
+
if (invalid)
|
|
156
|
+
throw new PreferencesError(invalid);
|
|
80
157
|
const path = preferencesPath(home);
|
|
81
158
|
mkdirSync(dirname(path), { recursive: true, mode: DIR_MODE });
|
|
82
159
|
const body = `${JSON.stringify(prefs, null, 2)}\n`;
|
|
@@ -85,27 +162,105 @@ export function writePreferences(prefs, home = homedir()) {
|
|
|
85
162
|
chmodSync(tmp, FILE_MODE);
|
|
86
163
|
renameSync(tmp, path);
|
|
87
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* A v2 file as a one-element chain, or null if it was not a well-formed v2.
|
|
167
|
+
*
|
|
168
|
+
* `model` is carried only when present, so a v2 file that never pinned a model
|
|
169
|
+
* does not gain one.
|
|
170
|
+
*/
|
|
171
|
+
function migrateV2(value) {
|
|
172
|
+
const v = value;
|
|
173
|
+
if (typeof v.provider !== 'string' || v.provider.length === 0)
|
|
174
|
+
return null;
|
|
175
|
+
if (v.model !== undefined && typeof v.model !== 'string')
|
|
176
|
+
return null;
|
|
177
|
+
if (!isSubagents(v.subagents))
|
|
178
|
+
return null;
|
|
179
|
+
const member = {
|
|
180
|
+
id: v.provider,
|
|
181
|
+
...(typeof v.model === 'string' ? { model: v.model } : {}),
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
version: PREFERENCES_FILE_VERSION,
|
|
185
|
+
providers: [member],
|
|
186
|
+
...(v.subagents !== undefined
|
|
187
|
+
? { subagents: v.subagents }
|
|
188
|
+
: {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Why this chain is unusable, or null if it is fine.
|
|
193
|
+
*
|
|
194
|
+
* Every member is checked, not only the head. A chain exists to be fallen back
|
|
195
|
+
* to, so a tail member naming a provider that does not exist is precisely as
|
|
196
|
+
* broken as a bad head — it is just broken later, on the worst day.
|
|
197
|
+
*/
|
|
198
|
+
function describeInvalidChain(members) {
|
|
199
|
+
if (members.length === 0) {
|
|
200
|
+
return 'preferences lists no providers';
|
|
201
|
+
}
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
for (const [index, member] of members.entries()) {
|
|
204
|
+
const position = chainPositionName(index);
|
|
205
|
+
if (!(member.id in PROVIDER_REGISTRY)) {
|
|
206
|
+
return `${position} "${member.id}" is not a provider namzu knows (known: ${Object.keys(PROVIDER_REGISTRY).join(', ')})`;
|
|
207
|
+
}
|
|
208
|
+
// Compared as the pair the operator WROTE, not against the resolved
|
|
209
|
+
// default model. Resolving would make a file that was valid yesterday
|
|
210
|
+
// invalid the day a registry default changes, which is a config that
|
|
211
|
+
// breaks on upgrade. Two entries for one provider with different models
|
|
212
|
+
// stay legitimate: falling back from a large model to a small one on the
|
|
213
|
+
// same provider is a real chain.
|
|
214
|
+
const key = JSON.stringify([member.id, member.model ?? null]);
|
|
215
|
+
if (seen.has(key)) {
|
|
216
|
+
const shown = member.model ? `${member.id} (${member.model})` : member.id;
|
|
217
|
+
return `${position} repeats "${shown}", which is already earlier in the chain — a provider cannot be its own fallback`;
|
|
218
|
+
}
|
|
219
|
+
seen.add(key);
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
function isSubagents(value) {
|
|
224
|
+
if (value === undefined)
|
|
225
|
+
return true;
|
|
226
|
+
if (typeof value !== 'object' || value === null)
|
|
227
|
+
return false;
|
|
228
|
+
const sa = value;
|
|
229
|
+
if (!Array.isArray(sa.active))
|
|
230
|
+
return false;
|
|
231
|
+
for (const item of sa.active) {
|
|
232
|
+
if (typeof item !== 'string')
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
function isProviderChoice(value) {
|
|
238
|
+
if (typeof value !== 'object' || value === null)
|
|
239
|
+
return false;
|
|
240
|
+
const v = value;
|
|
241
|
+
if (typeof v.id !== 'string' || v.id.length === 0)
|
|
242
|
+
return false;
|
|
243
|
+
if (v.model !== undefined && typeof v.model !== 'string')
|
|
244
|
+
return false;
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
88
247
|
function isPreferences(value) {
|
|
89
248
|
if (typeof value !== 'object' || value === null)
|
|
90
249
|
return false;
|
|
91
250
|
const v = value;
|
|
92
251
|
if (v.version !== PREFERENCES_FILE_VERSION)
|
|
93
252
|
return false;
|
|
94
|
-
if (
|
|
253
|
+
if (!Array.isArray(v.providers))
|
|
95
254
|
return false;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (v.subagents !== undefined) {
|
|
99
|
-
if (typeof v.subagents !== 'object' || v.subagents === null)
|
|
100
|
-
return false;
|
|
101
|
-
const sa = v.subagents;
|
|
102
|
-
if (!Array.isArray(sa.active))
|
|
255
|
+
for (const member of v.providers) {
|
|
256
|
+
if (!isProviderChoice(member))
|
|
103
257
|
return false;
|
|
104
|
-
for (const item of sa.active) {
|
|
105
|
-
if (typeof item !== 'string')
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
258
|
}
|
|
259
|
+
if (v.allowCapabilityMismatch !== undefined && typeof v.allowCapabilityMismatch !== 'boolean') {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (!isSubagents(v.subagents))
|
|
263
|
+
return false;
|
|
109
264
|
return true;
|
|
110
265
|
}
|
|
111
266
|
//# sourceMappingURL=preferences.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preferences.js","sourceRoot":"","sources":["../../../src/integrations/providers/preferences.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"preferences.js","sourceRoot":"","sources":["../../../src/integrations/providers/preferences.ts"],"names":[],"mappings":"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"}
|
package/dist/tui/App.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/tui/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/tui/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0DH,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,YAAY,CAAA;AAE/D,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;CACxB;AAWD,wBAAgB,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,QAAQ,2CA+4BpC"}
|
package/dist/tui/App.js
CHANGED
|
@@ -2,17 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
3
|
import { relative } from 'node:path';
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
|
-
import { writePreferences, } from '../integrations/providers/index.js';
|
|
5
|
+
import { primaryProvider, writePreferences, } from '../integrations/providers/index.js';
|
|
6
6
|
import { isTrusted, trustDir } from '../integrations/trust/store.js';
|
|
7
7
|
import { appendMemory, composeMemoryPrompt, readMemory } from '../memory/store.js';
|
|
8
8
|
import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
|
|
9
9
|
import { checkUpdates } from '../integrations/updates.js';
|
|
10
10
|
import { LiveActivity, formatElapsed } from './LiveActivity.js';
|
|
11
|
+
import { bottomSpacerRows } from './bottom-spacer.js';
|
|
11
12
|
import { expandFileMentions } from './mentions.js';
|
|
12
13
|
import { Composer } from './Composer.js';
|
|
13
14
|
import { TrustPrompt } from './TrustPrompt.js';
|
|
14
15
|
import { NAMZU_MARK, NAMZU_MARK_COLOR, NAMZU_WORDMARK, NAMZU_WORDMARK_GRADIENT, NAMZU_WORDMARK_MIN_WIDTH, } from './logo.js';
|
|
15
16
|
import { PermissionOverlay } from './PermissionOverlay.js';
|
|
17
|
+
import { approvalIsDeliberate } from './permission-timing.js';
|
|
16
18
|
import { Picker } from './Picker.js';
|
|
17
19
|
import { StatusBar } from './StatusBar.js';
|
|
18
20
|
import { Transcript } from './Transcript.js';
|
|
@@ -70,6 +72,14 @@ export function App({ ctx }) {
|
|
|
70
72
|
setActiveTools([]);
|
|
71
73
|
}, []);
|
|
72
74
|
const permissionResolveRef = useRef(null);
|
|
75
|
+
/**
|
|
76
|
+
* When the pending prompt took the screen, or `null` with none open.
|
|
77
|
+
*
|
|
78
|
+
* A ref rather than state because the keypress handler reads it in the same
|
|
79
|
+
* tick the prompt opens, before any re-render could carry a new value — see
|
|
80
|
+
* `permission-timing.ts` for why an approving key has to wait on this.
|
|
81
|
+
*/
|
|
82
|
+
const permissionOpenedAtRef = useRef(null);
|
|
73
83
|
// SDK-backed conversation persistence (DiskSessionStore). `scopeRef` carries
|
|
74
84
|
// the active session id used by query() — mutated in place on /resume so new
|
|
75
85
|
// turns attribute to the resumed conversation.
|
|
@@ -143,10 +153,16 @@ export function App({ ctx }) {
|
|
|
143
153
|
// the file is shadowed instead of leaving it silently unused.
|
|
144
154
|
reserved: SLASH_COMMANDS.map((c) => c.name),
|
|
145
155
|
}));
|
|
146
|
-
setCurrentProvider(prefs.
|
|
156
|
+
setCurrentProvider(primaryProvider(prefs).id);
|
|
147
157
|
if (s.hasProvider) {
|
|
148
158
|
setPhase('ready');
|
|
149
159
|
pushMessage('system', `Connected to ${s.providerSummary}${s.modelSummary ? ` · ${s.modelSummary}` : ''} · ${s.toolNames.length} tools`);
|
|
160
|
+
// Before the rest: a limitation the operator accepted once and has
|
|
161
|
+
// been living with since is the thing they are least likely to
|
|
162
|
+
// remember and most likely to be surprised by.
|
|
163
|
+
for (const notice of s.configNotices) {
|
|
164
|
+
pushMessage('system', notice);
|
|
165
|
+
}
|
|
150
166
|
// Named, not counted. "2 files" tells a user their conventions were
|
|
151
167
|
// found but not WHICH ones, and the interesting case is the one
|
|
152
168
|
// they did not expect — an instructions file in a parent directory
|
|
@@ -213,6 +229,19 @@ export function App({ ctx }) {
|
|
|
213
229
|
setPhase('probing');
|
|
214
230
|
void runProbe();
|
|
215
231
|
}, [ctx.cwd, runProbe]);
|
|
232
|
+
// Blank rows above the composer, while the transcript is short enough that
|
|
233
|
+
// the answer is knowable. `liveRows` is the fixed furniture beneath the
|
|
234
|
+
// transcript — activity line, composer frame, status bar and their padding —
|
|
235
|
+
// counted generously, because over-counting costs a gap and under-counting
|
|
236
|
+
// costs the composer.
|
|
237
|
+
const spacerRows = phase === 'ready'
|
|
238
|
+
? bottomSpacerRows({
|
|
239
|
+
rows: process.stdout.rows,
|
|
240
|
+
columns: process.stdout.columns,
|
|
241
|
+
transcript: messages.filter((m) => !m.pending).map((m) => m.content),
|
|
242
|
+
liveRows: 10,
|
|
243
|
+
})
|
|
244
|
+
: 0;
|
|
216
245
|
const slashCtx = {
|
|
217
246
|
availableTools: session?.toolNames ?? [],
|
|
218
247
|
providerSummary: session?.providerSummary ?? null,
|
|
@@ -281,6 +310,7 @@ export function App({ ctx }) {
|
|
|
281
310
|
const resolvePermission = useCallback((decision) => {
|
|
282
311
|
const resolve = permissionResolveRef.current;
|
|
283
312
|
permissionResolveRef.current = null;
|
|
313
|
+
permissionOpenedAtRef.current = null;
|
|
284
314
|
setPermission(null);
|
|
285
315
|
if (resolve)
|
|
286
316
|
resolve(decision);
|
|
@@ -289,6 +319,7 @@ export function App({ ctx }) {
|
|
|
289
319
|
// non-read-only tool batch; it parks until the user presses y/n/a.
|
|
290
320
|
const onPermission = useCallback((req) => new Promise((resolve) => {
|
|
291
321
|
permissionResolveRef.current = resolve;
|
|
322
|
+
permissionOpenedAtRef.current = Date.now();
|
|
292
323
|
setPermission(req);
|
|
293
324
|
setState('awaiting-permission');
|
|
294
325
|
}), []);
|
|
@@ -437,6 +468,7 @@ export function App({ ctx }) {
|
|
|
437
468
|
finally {
|
|
438
469
|
abortRef.current = null;
|
|
439
470
|
permissionResolveRef.current = null;
|
|
471
|
+
permissionOpenedAtRef.current = null;
|
|
440
472
|
setPermission(null);
|
|
441
473
|
clearActiveTools();
|
|
442
474
|
setState('idle');
|
|
@@ -587,13 +619,23 @@ export function App({ ctx }) {
|
|
|
587
619
|
setDetected(next);
|
|
588
620
|
// The disposition, not the key. `credential` never reaches a message.
|
|
589
621
|
pushMessage('system', disposition);
|
|
590
|
-
void hydrateSession({
|
|
622
|
+
void hydrateSession({
|
|
623
|
+
version: 3,
|
|
624
|
+
providers: [{ id: credential.entry.id }],
|
|
625
|
+
subagents: { active: [] },
|
|
626
|
+
}, next);
|
|
591
627
|
}, [detected, hydrateSession, pushMessage]);
|
|
592
628
|
const handlePickerSubmit = useCallback((selection) => {
|
|
629
|
+
// One member for now. The picker builds a longer chain in a later
|
|
630
|
+
// change; the shape it writes into is already the chain.
|
|
593
631
|
const prefs = {
|
|
594
|
-
version:
|
|
595
|
-
|
|
596
|
-
|
|
632
|
+
version: 3,
|
|
633
|
+
providers: [
|
|
634
|
+
{
|
|
635
|
+
id: selection.provider,
|
|
636
|
+
...(selection.model !== undefined ? { model: selection.model } : {}),
|
|
637
|
+
},
|
|
638
|
+
],
|
|
597
639
|
subagents: { active: [] },
|
|
598
640
|
};
|
|
599
641
|
try {
|
|
@@ -637,17 +679,29 @@ export function App({ ctx }) {
|
|
|
637
679
|
// A pending permission prompt owns the keyboard: y/n/a decide it.
|
|
638
680
|
if (permissionResolveRef.current) {
|
|
639
681
|
const ch = input.toLowerCase();
|
|
640
|
-
|
|
682
|
+
// Refusing is never deferred or gated — it is the direction a
|
|
683
|
+
// mistake is recoverable in, so it answers on the first press.
|
|
684
|
+
if (key.ctrl && input === 'c') {
|
|
641
685
|
resolvePermission({ kind: 'reject', feedback: 'User interrupted.' });
|
|
642
686
|
abortRef.current?.abort();
|
|
643
687
|
return;
|
|
644
688
|
}
|
|
645
|
-
if (ch === '
|
|
689
|
+
if (ch === 'n' || key.escape) {
|
|
690
|
+
resolvePermission({ kind: 'reject' });
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
// Enter is deliberately NOT an approval. It is the key people press
|
|
694
|
+
// to dismiss a thing that appeared, it is the key already in flight
|
|
695
|
+
// when a turn's follow-up was being typed, and it is named nowhere
|
|
696
|
+
// on this prompt — so reading it as consent approves a tool call the
|
|
697
|
+
// operator never looked at. `y` and `a` are the only approvals, and
|
|
698
|
+
// they must arrive after the prompt has been up long enough to read.
|
|
699
|
+
if (!approvalIsDeliberate(permissionOpenedAtRef.current, Date.now()))
|
|
700
|
+
return;
|
|
701
|
+
if (ch === 'y')
|
|
646
702
|
resolvePermission({ kind: 'approve' });
|
|
647
703
|
else if (ch === 'a')
|
|
648
704
|
resolvePermission({ kind: 'approve-all' });
|
|
649
|
-
else if (ch === 'n' || key.escape)
|
|
650
|
-
resolvePermission({ kind: 'reject' });
|
|
651
705
|
return;
|
|
652
706
|
}
|
|
653
707
|
// Esc interrupts a running turn (Ctrl+C stays reserved for exit). Mirrors
|
|
@@ -664,7 +718,7 @@ export function App({ ctx }) {
|
|
|
664
718
|
setExpanded((e) => !e);
|
|
665
719
|
return;
|
|
666
720
|
}
|
|
667
|
-
if (key.ctrl &&
|
|
721
|
+
if (key.ctrl && input === 'c') {
|
|
668
722
|
// A turn is running → first Ctrl+C interrupts it, not exits.
|
|
669
723
|
if (abortRef.current) {
|
|
670
724
|
abortRef.current.abort();
|
|
@@ -690,7 +744,7 @@ export function App({ ctx }) {
|
|
|
690
744
|
// Background is left natural — we inherit the terminal's own background
|
|
691
745
|
// and only theme the foreground. Forcing
|
|
692
746
|
// a filled bg left mismatched patches around bordered areas, so we don't.
|
|
693
|
-
return (_jsxs(Box, { flexDirection: "column", children: [phase !== 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, bypass: ctx.skipPermissions === true, cwd: ctx.cwd })) : null, _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'resume' ? (_jsx(ResumePicker, { conversations: resumeList, selected: selectedResume })) : phase === 'picker' ? (_jsx(Picker, { detected: detected, currentProvider: currentProvider, currentModel: session?.modelSummary ?? null, onSubmit: handlePickerSubmit, onCancel: handlePickerCancel, onCredential: handleTypedCredential })) : (_jsxs(_Fragment, { children: [_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: messages.filter((m) => !m.pending), pending: messages.find((m) => m.pending) ?? null, state: state, expanded: expanded, resetKey: resetKey, header: phase === 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, bypass: ctx.skipPermissions === true, cwd: ctx.cwd })) : undefined }) }), _jsx(LiveActivity, { activeTools: activeTools, thinking: state === 'thinking' && !messages.some((m) => m.pending) }), permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls })) : (_jsxs(ComposerFrame, { focus: state === 'idle' && phase === 'ready', children: [queued.length > 0 ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: ["\u23CE ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014 sending when ready"] }) })) : null, _jsx(Composer, { disabled: phase !== 'ready' || state === 'awaiting-permission', onSubmit: handleSubmit, userCommands: userCommands, history: history })] }))] })), _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, state: state, hint: hintForPhase(phase, state), usage: usage, context: context }) })] })] }));
|
|
747
|
+
return (_jsxs(Box, { flexDirection: "column", children: [phase !== 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, bypass: ctx.skipPermissions === true, cwd: ctx.cwd })) : null, _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'resume' ? (_jsx(ResumePicker, { conversations: resumeList, selected: selectedResume })) : phase === 'picker' ? (_jsx(Picker, { detected: detected, currentProvider: currentProvider, currentModel: session?.modelSummary ?? null, onSubmit: handlePickerSubmit, onCancel: handlePickerCancel, onCredential: handleTypedCredential })) : (_jsxs(_Fragment, { children: [_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: messages.filter((m) => !m.pending), pending: messages.find((m) => m.pending) ?? null, state: state, expanded: expanded, resetKey: resetKey, header: phase === 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, bypass: ctx.skipPermissions === true, cwd: ctx.cwd })) : undefined }) }), spacerRows > 0 ? _jsx(Box, { height: spacerRows }) : null, _jsx(LiveActivity, { activeTools: activeTools, thinking: state === 'thinking' && !messages.some((m) => m.pending) }), permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls })) : (_jsxs(ComposerFrame, { focus: state === 'idle' && phase === 'ready', children: [queued.length > 0 ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: ["\u23CE ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014 sending when ready"] }) })) : null, _jsx(Composer, { disabled: phase !== 'ready' || state === 'awaiting-permission', onSubmit: handleSubmit, userCommands: userCommands, history: history })] }))] })), _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, state: state, hint: hintForPhase(phase, state), usage: usage, context: context }) })] })] }));
|
|
694
748
|
}
|
|
695
749
|
function Banner({ version, session, bypass, cwd, }) {
|
|
696
750
|
const cols = process.stdout.columns ?? 80;
|
|
@@ -728,8 +782,10 @@ function hintForPhase(phase, state) {
|
|
|
728
782
|
return '↑↓ navigate · enter accept · esc cancel';
|
|
729
783
|
if (phase === 'unhealthy')
|
|
730
784
|
return 'Ctrl+C ×2 to exit';
|
|
785
|
+
// Enter is absent because Enter decides nothing here, deliberately — see the
|
|
786
|
+
// permission gate in the key handler above.
|
|
731
787
|
if (state === 'awaiting-permission')
|
|
732
|
-
return 'y approve · n reject · a approve all';
|
|
788
|
+
return 'y approve · n / esc reject · a approve all';
|
|
733
789
|
if (state !== 'idle')
|
|
734
790
|
return 'agent is working — esc to interrupt';
|
|
735
791
|
return '/help · @file / Ctrl+V to attach · Ctrl+C ×2 to exit';
|