@namzu/cli 4.2.0 → 4.3.1

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.
@@ -22,6 +22,27 @@ export interface ProviderChainCheckOptions {
22
22
  * is the worst moment to find out the key was never set. So every member's
23
23
  * credential is resolved here, on a day when nothing is broken.
24
24
  *
25
+ * ## Two ways a chain is unusable, and this reports both
26
+ *
27
+ * A credential is the obvious one. The other is a capability DISAGREEMENT: a
28
+ * run negotiates tools, vision and documents once, against the primary, and
29
+ * keeps that answer across a swap — so a chain whose members declare different
30
+ * abilities would land a run on a member holding a request shaped for someone
31
+ * else. `createAgentSession` refuses such a chain outright, which means an
32
+ * operator who has one learns about it by trying to start a session.
33
+ *
34
+ * That check used to be unreachable from here. Reading what a provider declares
35
+ * requires its package to be registered, and the only registration path lived
36
+ * inside the interactive session module — so `doctor` could report which
37
+ * members had keys and not whether the chain it was describing could run at
38
+ * all. A diagnostic that cannot see what the thing it diagnoses sees is
39
+ * checking the wrong thing. `ensureRegistered` now lives beside the registry
40
+ * (`integrations/providers/register.ts`) and both reach it.
41
+ *
42
+ * The cost is real and worth naming: this check dynamically imports the driver
43
+ * package of every member in the chain. That is the price of reading a
44
+ * declaration, and it is paid on a command whose entire job is to look.
45
+ *
25
46
  * Exported separately from the `DoctorCheck` so it can be driven with an
26
47
  * explicit home and environment. The check itself is still exercised through
27
48
  * `providerChainCheck.run`, because a helper proven in isolation says nothing
@@ -1 +1 @@
1
- {"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/chain.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEhE,OAAO,EAAE,KAAK,eAAe,EAAqB,MAAM,0CAA0C,CAAA;AAIlG,MAAM,WAAW,yBAAyB;IACzC,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAA;IACrC,uFAAuF;IACvF,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,GAAE,yBAA8B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA+F5B;AAED,eAAO,MAAM,kBAAkB,EAAE,WAIhC,CAAA"}
1
+ {"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/chain.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAMhE,OAAO,EAAE,KAAK,eAAe,EAAqB,MAAM,0CAA0C,CAAA;AAKlG,MAAM,WAAW,yBAAyB;IACzC,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAA;IACrC,uFAAuF;IACvF,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,GAAE,yBAA8B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAoJ5B;AAED,eAAO,MAAM,kBAAkB,EAAE,WAIhC,CAAA"}
@@ -1,6 +1,8 @@
1
1
  import { homedir } from 'node:os';
2
+ import { chainCapabilityDisagreements, unresolvedMembers, } from '../../integrations/providers/chain-capabilities.js';
2
3
  import { discoverProviders } from '../../integrations/providers/discover.js';
3
4
  import { preferencesPath, readPreferences } from '../../integrations/providers/preferences.js';
5
+ import { resolveChainCapabilities } from '../../integrations/providers/register.js';
4
6
  import { PROVIDER_REGISTRY } from '../../integrations/providers/registry.js';
5
7
  /**
6
8
  * The configured provider chain, in the operator's declared order.
@@ -17,6 +19,27 @@ import { PROVIDER_REGISTRY } from '../../integrations/providers/registry.js';
17
19
  * is the worst moment to find out the key was never set. So every member's
18
20
  * credential is resolved here, on a day when nothing is broken.
19
21
  *
22
+ * ## Two ways a chain is unusable, and this reports both
23
+ *
24
+ * A credential is the obvious one. The other is a capability DISAGREEMENT: a
25
+ * run negotiates tools, vision and documents once, against the primary, and
26
+ * keeps that answer across a swap — so a chain whose members declare different
27
+ * abilities would land a run on a member holding a request shaped for someone
28
+ * else. `createAgentSession` refuses such a chain outright, which means an
29
+ * operator who has one learns about it by trying to start a session.
30
+ *
31
+ * That check used to be unreachable from here. Reading what a provider declares
32
+ * requires its package to be registered, and the only registration path lived
33
+ * inside the interactive session module — so `doctor` could report which
34
+ * members had keys and not whether the chain it was describing could run at
35
+ * all. A diagnostic that cannot see what the thing it diagnoses sees is
36
+ * checking the wrong thing. `ensureRegistered` now lives beside the registry
37
+ * (`integrations/providers/register.ts`) and both reach it.
38
+ *
39
+ * The cost is real and worth naming: this check dynamically imports the driver
40
+ * package of every member in the chain. That is the price of reading a
41
+ * declaration, and it is paid on a command whose entire job is to look.
42
+ *
20
43
  * Exported separately from the `DoctorCheck` so it can be driven with an
21
44
  * explicit home and environment. The check itself is still exercised through
22
45
  * `providerChainCheck.run`, because a helper proven in isolation says nothing
@@ -91,23 +114,65 @@ export async function describeProviderChain(options = {}) {
91
114
  lines.push(`${index + 1}. ${position} · ${entry.label} · ${model} · ${state}`);
92
115
  }
93
116
  const chain = lines.join('\n');
94
- if (unusable === 0) {
117
+ // What each member DECLARES. Type-level, so no credential is needed — which
118
+ // is what makes it answerable about the fallback nobody has configured yet,
119
+ // the member most worth asking about.
120
+ const resolved = await resolveChainCapabilities(members);
121
+ const disagreements = chainCapabilityDisagreements(members, resolved);
122
+ const accepted = read.prefs.allowCapabilityMismatch === true;
123
+ const unreadable = unresolvedMembers(members, resolved);
124
+ const primaryUnreadable = resolved[0]?.kind === 'unresolved';
125
+ const sections = [chain];
126
+ if (disagreements.length > 0) {
127
+ sections.push(accepted
128
+ ? 'The members declare different capabilities, and you have accepted that:'
129
+ : 'The members declare different capabilities, so a session will be REFUSED:', ...disagreements.map((d) => ` - ${d.sentence}`));
130
+ }
131
+ if (unreadable.length > 0) {
132
+ // Kept separate from the disagreements above, for the reason the session
133
+ // keeps them separate: a member whose declaration could not be read is not
134
+ // a member that disagrees, and reporting it as one would name a conflict
135
+ // nobody established.
136
+ sections.push('Could not read what these members declare:', ...unreadable.map((line) => ` - ${line}`));
137
+ }
138
+ const message = sections.join('\n');
139
+ // Ordered by what stops a run. An unaccepted disagreement and an unusable
140
+ // primary both mean no session starts at all; everything else leaves namzu
141
+ // working with less than the operator declared.
142
+ if (disagreements.length > 0 && !accepted) {
143
+ return {
144
+ status: 'fail',
145
+ message: `provider chain cannot be honoured as written:\n${message}`,
146
+ remediation: 'Drop the member that disagrees, or set "allowCapabilityMismatch": true in your preferences to accept the limitation. namzu will not choose between advertising abilities a fallback lacks and costing your primary a capability on every run.',
147
+ };
148
+ }
149
+ if (primaryUnusable || primaryUnreadable) {
150
+ return {
151
+ status: 'fail',
152
+ message: `${unusable} of ${members.length} chain member(s) cannot be used:\n${message}`,
153
+ remediation: primaryUnusable
154
+ ? 'The primary provider has no usable credential, so no run can start. Set its key, or run `namzu` to pick a provider that has one.'
155
+ : 'The primary provider could not be loaded, so no run can start. Run `namzu` to pick a provider that can.',
156
+ };
157
+ }
158
+ if (unusable > 0 || unreadable.length > 0 || disagreements.length > 0) {
95
159
  return {
96
- status: 'pass',
97
- message: members.length === 1
98
- ? `1 provider configured (no fallback):\n${chain}`
99
- : `${members.length} providers configured, in order:\n${chain}`,
160
+ // `warn`, not `fail`: the primary still runs, so namzu is usable and the
161
+ // operator is not blocked by a degraded spare.
162
+ status: 'warn',
163
+ message: unusable > 0
164
+ ? `${unusable} of ${members.length} chain member(s) cannot be used:\n${message}`
165
+ : `provider chain usable, with limitations:\n${message}`,
166
+ remediation: unusable > 0
167
+ ? 'The primary still works, so runs will start. But a fallback with no credential is not a fallback — set its key, or take it out of the chain.'
168
+ : 'The primary still works. A fallback that declares less than your primary will serve shorter or less capable turns if the chain ever falls over to it.',
100
169
  };
101
170
  }
102
171
  return {
103
- // `warn`, not `fail`, when only a fallback is broken: the primary still
104
- // runs, so namzu is usable and the operator is not blocked by a
105
- // degraded spare. A broken PRIMARY stops every run, and reads as such.
106
- status: primaryUnusable ? 'fail' : 'warn',
107
- message: `${unusable} of ${members.length} chain member(s) cannot be used:\n${chain}`,
108
- remediation: primaryUnusable
109
- ? 'The primary provider has no usable credential, so no run can start. Set its key, or run `namzu` to pick a provider that has one.'
110
- : 'The primary still works, so runs will start. But a fallback with no credential is not a fallback — set its key, or take it out of the chain.',
172
+ status: 'pass',
173
+ message: members.length === 1
174
+ ? `1 provider configured (no fallback):\n${message}`
175
+ : `${members.length} providers configured, in order:\n${message}`,
111
176
  };
112
177
  }
113
178
  export const providerChainCheck = {
@@ -1 +1 @@
1
- {"version":3,"file":"chain.js","sourceRoot":"","sources":["../../../src/doctor/checks/chain.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chain.js","sourceRoot":"","sources":["../../../src/doctor/checks/chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAIjC,OAAO,EACN,4BAA4B,EAC5B,iBAAiB,GACjB,MAAM,oDAAoD,CAAA;AAC3D,OAAO,EAAwB,iBAAiB,EAAE,MAAM,0CAA0C,CAAA;AAClG,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAA;AAC9F,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAA;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAA;AAU5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,UAAqC,EAAE;IAEvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,CAAA;IACtC,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IAElC,IAAI,IAAwC,CAAA;IAC5C,IAAI,CAAC;QACJ,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,kBAAkB,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YACtF,WAAW,EAAE,oEAAoE;SACjF,CAAA;IACF,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO;YACN,MAAM,EAAE,cAAc;YACtB,OAAO,EAAE,iCAAiC,IAAI,kBAAkB;YAChE,WAAW,EAAE,oEAAoE;SACjF,CAAA;IACF,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;QACpC,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,4BAA4B,IAAI,CAAC,MAAM,EAAE;YAClD,WAAW,EAAE,uDAAuD;SACpE,CAAA;IACF,CAAC;IAED,IAAI,QAAuD,CAAA;IAC3D,IAAI,CAAC;QACJ,QAAQ,GAAG,MAAM,iBAAiB,CAAC;YAClC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnD,CAAC,CAAA;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,uEAAuE;QACvE,iEAAiE;QACjE,iBAAiB;QACjB,OAAO;YACN,MAAM,EAAE,cAAc;YACtB,OAAO,EAAE,yDACR,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAChD,EAAE;SACF,CAAA;IACF,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAA;IACpC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,IAAI,eAAe,GAAG,KAAK,CAAA;IAE3B,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC1C,MAAM,QAAQ,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,KAAK,EAAE,CAAA;QAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,YAAY,YAAY,CAAA;QAC/D,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAA;QAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACzE,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,QAAQ,EAAE,CAAA;YACV,IAAI,KAAK,KAAK,CAAC;gBAAE,eAAe,GAAG,IAAI,CAAA;QACxC,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc;YACjC,CAAC,CAAC,MAAM;gBACP,CAAC,CAAC,kBAAkB;gBACpB,CAAC,CAAC,eAAe;YAClB,CAAC,CAAC,MAAM;gBACP,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,eAAe,CAAA;QACnB,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,QAAQ,MAAM,KAAK,CAAC,KAAK,MAAM,KAAK,MAAM,KAAK,EAAE,CAAC,CAAA;IAC/E,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAE9B,4EAA4E;IAC5E,4EAA4E;IAC5E,sCAAsC;IACtC,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC,OAAO,CAAC,CAAA;IACxD,MAAM,aAAa,GAAG,4BAA4B,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,uBAAuB,KAAK,IAAI,CAAA;IAC5D,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IACvD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,YAAY,CAAA;IAE5D,MAAM,QAAQ,GAAa,CAAC,KAAK,CAAC,CAAA;IAClC,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,QAAQ,CAAC,IAAI,CACZ,QAAQ;YACP,CAAC,CAAC,yEAAyE;YAC3E,CAAC,CAAC,2EAA2E,EAC9E,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,CAChD,CAAA;IACF,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,sBAAsB;QACtB,QAAQ,CAAC,IAAI,CACZ,4CAA4C,EAC5C,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAC1C,CAAA;IACF,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEnC,0EAA0E;IAC1E,2EAA2E;IAC3E,gDAAgD;IAChD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC3C,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,kDAAkD,OAAO,EAAE;YACpE,WAAW,EACV,+OAA+O;SAChP,CAAA;IACF,CAAC;IACD,IAAI,eAAe,IAAI,iBAAiB,EAAE,CAAC;QAC1C,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,QAAQ,OAAO,OAAO,CAAC,MAAM,qCAAqC,OAAO,EAAE;YACvF,WAAW,EAAE,eAAe;gBAC3B,CAAC,CAAC,kIAAkI;gBACpI,CAAC,CAAC,yGAAyG;SAC5G,CAAA;IACF,CAAC;IACD,IAAI,QAAQ,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvE,OAAO;YACN,yEAAyE;YACzE,+CAA+C;YAC/C,MAAM,EAAE,MAAM;YACd,OAAO,EACN,QAAQ,GAAG,CAAC;gBACX,CAAC,CAAC,GAAG,QAAQ,OAAO,OAAO,CAAC,MAAM,qCAAqC,OAAO,EAAE;gBAChF,CAAC,CAAC,6CAA6C,OAAO,EAAE;YAC1D,WAAW,EACV,QAAQ,GAAG,CAAC;gBACX,CAAC,CAAC,8IAA8I;gBAChJ,CAAC,CAAC,uJAAuJ;SAC3J,CAAA;IACF,CAAC;IAED,OAAO;QACN,MAAM,EAAE,MAAM;QACd,OAAO,EACN,OAAO,CAAC,MAAM,KAAK,CAAC;YACnB,CAAC,CAAC,yCAAyC,OAAO,EAAE;YACpD,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,qCAAqC,OAAO,EAAE;KACnE,CAAA;AACF,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAgB;IAC9C,EAAE,EAAE,iBAAiB;IACrB,QAAQ,EAAE,WAAW;IACrB,GAAG,EAAE,CAAC,GAAG,EAA8B,EAAE,CAAC,qBAAqB,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;CACjF,CAAA"}
@@ -4,4 +4,5 @@ export { ensureFreshAnthropicToken, type OAuthMetadata, refreshAgentOAuthToken,
4
4
  export { type CapabilityDisagreement, chainCapabilityDisagreements, describeAcceptedMismatch, describeCapabilityRefusal, type MemberCapabilities, unresolvedMembers, } from './chain-capabilities.js';
5
5
  export { chainPositionName, type Preferences, PREFERENCES_FILE_VERSION, PreferencesError, preferencesPath, primaryProvider, type ProviderChoice, type ReadResult, readPreferences, writePreferences, } from './preferences.js';
6
6
  export { ALL_PROVIDER_IDS, PROVIDER_REGISTRY, type ProviderId, type ProviderRegistryEntry, type SdkProviderType, } from './registry.js';
7
+ export { ensureRegistered, isRegistered, resolveChainCapabilities } from './register.js';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,iBAAiB,EACjB,YAAY,GACZ,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,KAAK,oBAAoB,EACzB,qBAAqB,EACrB,2BAA2B,GAC3B,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,yBAAyB,EACzB,KAAK,aAAa,EAClB,sBAAsB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACN,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,wBAAwB,EACxB,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,iBAAiB,GACjB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACN,iBAAiB,EACjB,KAAK,WAAW,EAChB,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,eAAe,EACf,gBAAgB,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,eAAe,GACpB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,iBAAiB,EACjB,YAAY,GACZ,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,KAAK,oBAAoB,EACzB,qBAAqB,EACrB,2BAA2B,GAC3B,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,yBAAyB,EACzB,KAAK,aAAa,EAClB,sBAAsB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACN,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,wBAAwB,EACxB,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,iBAAiB,GACjB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACN,iBAAiB,EACjB,KAAK,WAAW,EAChB,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,eAAe,EACf,gBAAgB,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,eAAe,GACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA"}
@@ -4,4 +4,5 @@ export { ensureFreshAnthropicToken, refreshAgentOAuthToken, } from './oauth.js';
4
4
  export { chainCapabilityDisagreements, describeAcceptedMismatch, describeCapabilityRefusal, unresolvedMembers, } from './chain-capabilities.js';
5
5
  export { chainPositionName, PREFERENCES_FILE_VERSION, PreferencesError, preferencesPath, primaryProvider, readPreferences, writePreferences, } from './preferences.js';
6
6
  export { ALL_PROVIDER_IDS, PROVIDER_REGISTRY, } from './registry.js';
7
+ export { ensureRegistered, isRegistered, resolveChainCapabilities } from './register.js';
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,iBAAiB,EACjB,YAAY,GACZ,MAAM,eAAe,CAAA;AACtB,OAAO,EAEN,qBAAqB,EACrB,2BAA2B,GAC3B,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,yBAAyB,EAEzB,sBAAsB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EAEN,4BAA4B,EAC5B,wBAAwB,EACxB,yBAAyB,EAEzB,iBAAiB,GACjB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACN,iBAAiB,EAEjB,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,eAAe,EAGf,eAAe,EACf,gBAAgB,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,GAIjB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,iBAAiB,EACjB,YAAY,GACZ,MAAM,eAAe,CAAA;AACtB,OAAO,EAEN,qBAAqB,EACrB,2BAA2B,GAC3B,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,yBAAyB,EAEzB,sBAAsB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EAEN,4BAA4B,EAC5B,wBAAwB,EACxB,yBAAyB,EAEzB,iBAAiB,GACjB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACN,iBAAiB,EAEjB,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,eAAe,EAGf,eAAe,EACf,gBAAgB,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,GAIjB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Put a driver package into the SDK's `ProviderRegistry`, once per process.
3
+ *
4
+ * This is the only place the wired driver packages are imported, so it decides
5
+ * what a `namzu` invocation actually pulls in and when. The imports are dynamic
6
+ * for that reason: a run on one provider does not load the other three, and a
7
+ * command that never touches a model loads none.
8
+ *
9
+ * ## Why it lives here rather than in the session module
10
+ *
11
+ * It used to be module-private inside the interactive session, which made
12
+ * reading what a provider DECLARES a privilege of having started a session.
13
+ * Registration is a precondition of that read — the SDK cannot report the
14
+ * capabilities of a package nobody imported — so `namzu doctor`, whose whole
15
+ * job is to answer questions about the setup before a session exists, could not
16
+ * ask. The alternative was for the diagnostic to import the session module and
17
+ * with it the query runtime, the subagent runtime, MCP, memory and skills, for
18
+ * one map lookup.
19
+ *
20
+ * So it sits beside `PROVIDER_REGISTRY`, which is the data it switches over,
21
+ * and both the session and the doctor reach it without either importing the
22
+ * other.
23
+ *
24
+ * ## One set, and that is load-bearing
25
+ *
26
+ * `registered` makes registration idempotent. It must exist exactly once: two
27
+ * copies would each believe a provider was unregistered and register it twice,
28
+ * and the SDK's registry rejects that with a duplicate-provider error. That is
29
+ * the whole reason this is a module rather than a function anyone may copy —
30
+ * the state is the API as much as the function is.
31
+ */
32
+ import type { MemberCapabilities } from './chain-capabilities.js';
33
+ import type { ProviderChoice } from './preferences.js';
34
+ import { type ProviderId } from './registry.js';
35
+ /**
36
+ * Import and register the driver for `id`, unless it is already registered.
37
+ *
38
+ * Throws for a provider that has a registry entry but no wiring here. That
39
+ * asymmetry is a known defect, not a design: the entry, the label, the default
40
+ * model and the discovery probe all cooperate to advertise a provider that
41
+ * cannot be built. See issue #257.
42
+ */
43
+ export declare function ensureRegistered(id: ProviderId): Promise<void>;
44
+ /**
45
+ * Has this provider's driver been registered in THIS process?
46
+ *
47
+ * Read by the chain builder to skip a member whose registration failed, so the
48
+ * failure is reported once — as an unresolved capability — rather than twice in
49
+ * different words.
50
+ */
51
+ export declare function isRegistered(id: ProviderId): boolean;
52
+ /**
53
+ * What each member of the chain DECLARES it can do.
54
+ *
55
+ * Type-level, via the registry, so nothing is constructed and no credential is
56
+ * needed — which is the whole point: the member most worth checking is the
57
+ * fallback nobody has set up yet, and a check that demanded a key would be
58
+ * unusable in exactly that case.
59
+ *
60
+ * A member that cannot be registered is reported as unresolved rather than
61
+ * assumed to agree. Registration is also why this cannot be a pure function: a
62
+ * provider package is only imported when something needs it.
63
+ */
64
+ export declare function resolveChainCapabilities(members: readonly ProviderChoice[]): Promise<readonly MemberCapabilities[]>;
65
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAMH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAIlE;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BpE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAEpD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,SAAS,cAAc,EAAE,GAChC,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAuBxC"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Put a driver package into the SDK's `ProviderRegistry`, once per process.
3
+ *
4
+ * This is the only place the wired driver packages are imported, so it decides
5
+ * what a `namzu` invocation actually pulls in and when. The imports are dynamic
6
+ * for that reason: a run on one provider does not load the other three, and a
7
+ * command that never touches a model loads none.
8
+ *
9
+ * ## Why it lives here rather than in the session module
10
+ *
11
+ * It used to be module-private inside the interactive session, which made
12
+ * reading what a provider DECLARES a privilege of having started a session.
13
+ * Registration is a precondition of that read — the SDK cannot report the
14
+ * capabilities of a package nobody imported — so `namzu doctor`, whose whole
15
+ * job is to answer questions about the setup before a session exists, could not
16
+ * ask. The alternative was for the diagnostic to import the session module and
17
+ * with it the query runtime, the subagent runtime, MCP, memory and skills, for
18
+ * one map lookup.
19
+ *
20
+ * So it sits beside `PROVIDER_REGISTRY`, which is the data it switches over,
21
+ * and both the session and the doctor reach it without either importing the
22
+ * other.
23
+ *
24
+ * ## One set, and that is load-bearing
25
+ *
26
+ * `registered` makes registration idempotent. It must exist exactly once: two
27
+ * copies would each believe a provider was unregistered and register it twice,
28
+ * and the SDK's registry rejects that with a duplicate-provider error. That is
29
+ * the whole reason this is a module rather than a function anyone may copy —
30
+ * the state is the API as much as the function is.
31
+ */
32
+ import { ProviderRegistry } from '@namzu/sdk';
33
+ import { resolveProviderCapabilities } from '@namzu/sdk';
34
+ import { PROVIDER_REGISTRY } from './registry.js';
35
+ const registered = new Set();
36
+ /**
37
+ * Import and register the driver for `id`, unless it is already registered.
38
+ *
39
+ * Throws for a provider that has a registry entry but no wiring here. That
40
+ * asymmetry is a known defect, not a design: the entry, the label, the default
41
+ * model and the discovery probe all cooperate to advertise a provider that
42
+ * cannot be built. See issue #257.
43
+ */
44
+ export async function ensureRegistered(id) {
45
+ if (registered.has(id))
46
+ return;
47
+ switch (id) {
48
+ case 'anthropic': {
49
+ const mod = await import('@namzu/anthropic');
50
+ mod.registerAnthropic();
51
+ break;
52
+ }
53
+ case 'openai': {
54
+ const mod = await import('@namzu/openai');
55
+ mod.registerOpenAI();
56
+ break;
57
+ }
58
+ case 'openrouter': {
59
+ const mod = await import('@namzu/openrouter');
60
+ mod.registerOpenRouter();
61
+ break;
62
+ }
63
+ case 'ollama': {
64
+ const mod = await import('@namzu/ollama');
65
+ mod.registerOllama();
66
+ break;
67
+ }
68
+ default:
69
+ throw new Error(`provider "${id}" is not wired yet; pick another or wait for support`);
70
+ }
71
+ registered.add(id);
72
+ }
73
+ /**
74
+ * Has this provider's driver been registered in THIS process?
75
+ *
76
+ * Read by the chain builder to skip a member whose registration failed, so the
77
+ * failure is reported once — as an unresolved capability — rather than twice in
78
+ * different words.
79
+ */
80
+ export function isRegistered(id) {
81
+ return registered.has(id);
82
+ }
83
+ /**
84
+ * What each member of the chain DECLARES it can do.
85
+ *
86
+ * Type-level, via the registry, so nothing is constructed and no credential is
87
+ * needed — which is the whole point: the member most worth checking is the
88
+ * fallback nobody has set up yet, and a check that demanded a key would be
89
+ * unusable in exactly that case.
90
+ *
91
+ * A member that cannot be registered is reported as unresolved rather than
92
+ * assumed to agree. Registration is also why this cannot be a pure function: a
93
+ * provider package is only imported when something needs it.
94
+ */
95
+ export async function resolveChainCapabilities(members) {
96
+ const out = [];
97
+ for (const member of members) {
98
+ if (!(member.id in PROVIDER_REGISTRY)) {
99
+ out.push({ kind: 'unresolved', reason: `"${member.id}" is not a provider namzu knows` });
100
+ continue;
101
+ }
102
+ try {
103
+ await ensureRegistered(member.id);
104
+ out.push({
105
+ kind: 'known',
106
+ capabilities: resolveProviderCapabilities({
107
+ capabilities: ProviderRegistry.getCapabilities(member.id),
108
+ }),
109
+ });
110
+ }
111
+ catch (err) {
112
+ out.push({
113
+ kind: 'unresolved',
114
+ reason: err instanceof Error ? err.message : String(err),
115
+ });
116
+ }
117
+ }
118
+ return out;
119
+ }
120
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/integrations/providers/register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAIxD,OAAO,EAAE,iBAAiB,EAAmB,MAAM,eAAe,CAAA;AAElE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAc,CAAA;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAc;IACpD,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAM;IAC9B,QAAQ,EAAE,EAAE,CAAC;QACZ,KAAK,WAAW,CAAC,CAAC,CAAC;YAClB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;YAC5C,GAAG,CAAC,iBAAiB,EAAE,CAAA;YACvB,MAAK;QACN,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACf,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;YACzC,GAAG,CAAC,cAAc,EAAE,CAAA;YACpB,MAAK;QACN,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YACnB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAA;YAC7C,GAAG,CAAC,kBAAkB,EAAE,CAAA;YACxB,MAAK;QACN,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACf,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;YACzC,GAAG,CAAC,cAAc,EAAE,CAAA;YACpB,MAAK;QACN,CAAC;QACD;YACC,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,sDAAsD,CAAC,CAAA;IACxF,CAAC;IACD,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,EAAc;IAC1C,OAAO,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;AAC1B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC7C,OAAkC;IAElC,MAAM,GAAG,GAAyB,EAAE,CAAA;IACpC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,iBAAiB,CAAC,EAAE,CAAC;YACvC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,MAAM,CAAC,EAAE,iCAAiC,EAAE,CAAC,CAAA;YACxF,SAAQ;QACT,CAAC;QACD,IAAI,CAAC;YACJ,MAAM,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACjC,GAAG,CAAC,IAAI,CAAC;gBACR,IAAI,EAAE,OAAO;gBACb,YAAY,EAAE,2BAA2B,CAAC;oBACzC,YAAY,EAAE,gBAAgB,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;iBACK,CAAC;aAChE,CAAC,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,IAAI,CAAC;gBACR,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAA;QACH,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAA;AACX,CAAC"}
@@ -1 +1 @@
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,2CAq8BpC"}
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,2CAq/BpC"}
package/dist/tui/App.js CHANGED
@@ -682,11 +682,49 @@ export function App({ ctx }) {
682
682
  }
683
683
  void hydrateSession(prefs, detected);
684
684
  }, [detected, hydrateSession, pushMessage]);
685
+ /**
686
+ * Leaving the picker returns to whatever was on screen before it.
687
+ *
688
+ * The picker has two entry points and used to have one exit. Opened by
689
+ * `/model` from a working session, cancelling sent the operator to
690
+ * `unhealthy` — a phase with a disabled composer, from which `/model` cannot
691
+ * be typed again — so declining to change model threw away the session they
692
+ * already had.
693
+ *
694
+ * `unhealthy` means namzu tried and cannot serve: it is set when a probe
695
+ * throws, and when a session comes up with no provider, both carrying an
696
+ * `errorHint` saying what failed. Cancelling is not a failure, and borrowing
697
+ * the failure phase to express it is what made a working session look broken.
698
+ *
699
+ * With nothing behind the picker — first run, no session — there is no screen
700
+ * to return to, so leaving the picker is leaving the program. That is also
701
+ * what the empty picker's own footer has always claimed `esc` does.
702
+ */
685
703
  const handlePickerCancel = useCallback(() => {
686
- setPhase('unhealthy');
687
- pushMessage('system', 'Picker cancelled. Set an LLM credential (ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / start Ollama) and restart namzu.');
688
- }, [pushMessage]);
704
+ if (session?.hasProvider) {
705
+ setPhase('ready');
706
+ return;
707
+ }
708
+ exit();
709
+ }, [session, exit]);
689
710
  useInput((input, key) => {
711
+ // The provider picker owns its own keyboard — arrows, digits, enter,
712
+ // esc and the typed-key screen are all its business. Ctrl+C is not: it
713
+ // is the one key that must work on every screen, and this handler used
714
+ // to be switched off for the whole picker phase. Ink runs with
715
+ // `exitOnCtrlC: false` and raw mode means the tty raises no SIGINT, so
716
+ // nothing else was going to answer it.
717
+ //
718
+ // One press, not the two the ready screen asks for. The ladder works by
719
+ // printing "press again" into the transcript, and the transcript is not
720
+ // rendered during the picker — so the old behaviour was not quite "the
721
+ // key is dead" but the worse "the first press does something invisible
722
+ // and the second exits", with nothing on screen naming either.
723
+ if (phase === 'picker') {
724
+ if (key.ctrl && input === 'c')
725
+ exit();
726
+ return;
727
+ }
690
728
  // Resume picker owns the keyboard while open.
691
729
  if (phase === 'resume') {
692
730
  if (key.upArrow)
@@ -793,11 +831,18 @@ export function App({ ctx }) {
793
831
  exitArmedRef.current = false;
794
832
  }, 2000);
795
833
  }
796
- }, { isActive: phase !== 'picker' });
834
+ },
835
+ // Active on every phase. It was gated off during the picker, which is what
836
+ // left that screen without a usable exit; the picker branch above returns
837
+ // immediately, so the Picker still owns everything except Ctrl+C.
838
+ { isActive: true });
797
839
  // Background is left natural — we inherit the terminal's own background
798
840
  // and only theme the foreground. Forcing
799
841
  // a filled bg left mismatched patches around bordered areas, so we don't.
800
- 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 }) })] })] }));
842
+ 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 }) : null, _jsxs(ComposerFrame, { focus: state === 'idle' && phase === 'ready', hidden: permission !== null, children: [queued.length > 0 && permission === null ? (_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', hidden: permission !== null,
843
+ // A turn is running, so Esc is the interrupt and not
844
+ // the composer's clear.
845
+ escapeInterrupts: state === 'thinking' || state === 'tool', 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, session?.hasProvider === true), usage: usage, context: context }) })] })] }));
801
846
  }
802
847
  function Banner({ version, session, bypass, cwd, }) {
803
848
  const cols = process.stdout.columns ?? 80;
@@ -812,10 +857,10 @@ function TranscriptFrame({ children }) {
812
857
  // Borderless, edge-to-edge — the message glyph gutter provides structure.
813
858
  return _jsx(Box, { flexDirection: "column", children: children });
814
859
  }
815
- function ComposerFrame({ focus, children, }) {
860
+ function ComposerFrame({ focus, hidden = false, children, }) {
816
861
  // Input-field look: a rounded rule above and below the composer, no side
817
862
  // borders, so the input reads as a field rather than a heavy box.
818
- return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderTop: true, borderBottom: true, borderLeft: false, borderRight: false, borderColor: focus ? theme.border.focus : theme.border.default, marginTop: 1, children: children }));
863
+ return (_jsx(Box, { flexDirection: "column", ...(hidden ? {} : { borderStyle: 'round' }), borderTop: true, borderBottom: true, borderLeft: false, borderRight: false, borderColor: focus ? theme.border.focus : theme.border.default, marginTop: hidden ? 0 : 1, children: children }));
819
864
  }
820
865
  // Tool call label: the tool name title-cased, then its most identifying
821
866
  // argument — `Bash(ls -la)`, `Read(file.ts)`. A bare tool name in a transcript
@@ -824,7 +869,9 @@ function formatToolCall(toolName, summary) {
824
869
  const display = toolName.length > 0 ? toolName[0]?.toUpperCase() + toolName.slice(1) : toolName;
825
870
  return summary.length > 0 ? `${display}(${summary})` : display;
826
871
  }
827
- function hintForPhase(phase, state) {
872
+ function hintForPhase(phase, state,
873
+ /** Whether there is a working session behind the picker to go back to. */
874
+ canReturn = false) {
828
875
  // Enter is absent because Enter grants nothing here, deliberately — see the
829
876
  // trust branch in the key handler above.
830
877
  if (phase === 'trust')
@@ -833,8 +880,14 @@ function hintForPhase(phase, state) {
833
880
  return '↑↓ navigate · enter resume · esc cancel';
834
881
  if (phase === 'probing')
835
882
  return 'discovering providers…';
836
- if (phase === 'picker')
837
- return '↑↓ navigate · enter accept · esc cancel';
883
+ // Esc does two different things here depending on how the picker was reached,
884
+ // so the hint says which. Naming Ctrl+C matters more here than anywhere else:
885
+ // it is the screen a new user meets before they know the program has keys.
886
+ if (phase === 'picker') {
887
+ return canReturn
888
+ ? '↑↓ navigate · enter accept · esc keep current · Ctrl+C exit'
889
+ : '↑↓ navigate · enter accept · esc or Ctrl+C exit';
890
+ }
838
891
  if (phase === 'unhealthy')
839
892
  return 'Ctrl+C ×2 to exit';
840
893
  // Enter is absent because Enter decides nothing here, deliberately — see the