@zergai/ztc 0.2.6-dev.36101352252 → 0.2.7-dev.36236183737
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/README.md +43 -17
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +19 -5
- package/dist/agent/agent.d.ts +5 -1
- package/dist/agent/agent.js +51 -8
- package/dist/agent/backends/account_pool.d.ts +5 -6
- package/dist/agent/backends/account_pool.js +39 -14
- package/dist/agent/backends/account_rotation.d.ts +3 -0
- package/dist/agent/backends/account_rotation.js +14 -3
- package/dist/agent/backends/codex_account_status.d.ts +42 -0
- package/dist/agent/backends/codex_account_status.js +196 -0
- package/dist/agent/backends/codex_app_server.d.ts +12 -2
- package/dist/agent/backends/codex_app_server.js +66 -6
- package/dist/agent/backends/index.d.ts +2 -0
- package/dist/agent/backends/index.js +13 -5
- package/dist/agent/backends/openai.d.ts +2 -2
- package/dist/agent/backends/openai.js +4 -3
- package/dist/agent/backends/openai_compatible.js +10 -10
- package/dist/agent/backends/openai_responses.d.ts +17 -5
- package/dist/agent/backends/openai_responses.js +2 -4
- package/dist/agent/backends/token_usage.js +1 -1
- package/dist/agent/backends/types.d.ts +3 -0
- package/dist/agent/commands/accounts.d.ts +7 -1
- package/dist/agent/commands/accounts.js +299 -101
- package/dist/agent/commands/config.js +11 -3
- package/dist/agent/commands/config_profile_switch.d.ts +2 -0
- package/dist/agent/commands/config_profile_switch.js +22 -0
- package/dist/agent/commands/context.js +4 -3
- package/dist/agent/commands/index.js +1 -2
- package/dist/agent/commands/input_mode.js +1 -1
- package/dist/agent/commands/model.js +7 -2
- package/dist/agent/commands/plan.d.ts +12 -0
- package/dist/agent/commands/plan.js +39 -28
- package/dist/agent/commands/profile.d.ts +1 -1
- package/dist/agent/commands/profile.js +34 -257
- package/dist/agent/commands/provider_choices.d.ts +12 -1
- package/dist/agent/commands/provider_choices.js +29 -2
- package/dist/agent/commands/provider_selection.d.ts +3 -0
- package/dist/agent/commands/provider_selection.js +63 -0
- package/dist/agent/commands/settings.d.ts +2 -0
- package/dist/agent/commands/settings.js +655 -50
- package/dist/agent/commands/types.d.ts +19 -2
- package/dist/agent/tools/ask_user.js +2 -2
- package/dist/app/agent_runner.d.ts +1 -1
- package/dist/app/agent_runner.js +2 -2
- package/dist/app/ask_user_response.d.ts +3 -0
- package/dist/app/ask_user_response.js +16 -0
- package/dist/app/command_context.js +29 -13
- package/dist/app/controller.d.ts +17 -2
- package/dist/app/controller.js +24 -1
- package/dist/app/selection_completion.d.ts +9 -3
- package/dist/app/selection_completion.js +54 -22
- package/dist/cli_args.js +10 -1
- package/dist/cli_main.js +85 -2
- package/dist/cli_ratatui.d.ts +22 -4
- package/dist/cli_ratatui.js +421 -146
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +20 -0
- package/dist/config/profiles.d.ts +9 -0
- package/dist/config/profiles.js +87 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +283 -40
- package/dist/headless.js +14 -0
- package/dist/panes/model_catalog.d.ts +1 -1
- package/dist/panes/model_catalog.js +24 -2
- package/dist/panes/ztc_launch.js +6 -0
- package/dist/router_auth.js +3 -2
- package/dist/session.d.ts +2 -0
- package/dist/session.js +3 -0
- package/dist/types.d.ts +23 -2
- package/dist/ui/views/app.d.ts +3 -1
- package/dist/ui/views/app.js +3 -2
- package/dist/ui/views/completion_menu.js +6 -6
- package/dist/ui/views/input_area.d.ts +2 -1
- package/dist/ui/views/input_area.js +9 -8
- package/dist/ui/views/status_bar.d.ts +2 -0
- package/dist/ui/views/status_bar.js +15 -4
- package/dist/ui/widgets/color_panel.d.ts +9 -0
- package/dist/ui/widgets/color_panel.js +41 -26
- package/dist/ui/widgets/navigator_panel.d.ts +9 -0
- package/dist/ui/widgets/navigator_panel.js +17 -16
- package/dist/ui/widgets/panel_frame.d.ts +83 -3
- package/dist/ui/widgets/panel_frame.js +107 -9
- package/dist/ui/widgets/questionnaire.d.ts +9 -0
- package/dist/ui/widgets/questionnaire.js +214 -16
- package/dist/ui/widgets/resume_panel.d.ts +9 -0
- package/dist/ui/widgets/resume_panel.js +18 -17
- package/dist/ui/widgets/rewind_panel.d.ts +9 -0
- package/dist/ui/widgets/rewind_panel.js +15 -16
- package/dist/ui/widgets/select.d.ts +6 -0
- package/dist/ui/widgets/select.js +47 -12
- package/dist/ui/widgets/settings_panel.d.ts +75 -0
- package/dist/ui/widgets/settings_panel.js +197 -39
- package/dist/ui/widgets/status_line_panel.d.ts +9 -0
- package/dist/ui/widgets/status_line_panel.js +18 -19
- package/dist/ui/widgets/transcript_preview.d.ts +8 -0
- package/dist/ui/widgets/transcript_preview.js +10 -0
- package/dist/utils/context.js +2 -0
- package/dist/utils/fireworks.d.ts +20 -0
- package/dist/utils/fireworks.js +23 -0
- package/dist/utils/models.d.ts +2 -1
- package/dist/utils/models.js +19 -0
- package/dist/web/accounts_mirror.d.ts +7 -0
- package/dist/web/accounts_mirror.js +59 -7
- package/dist/web/mirror_stream.js +19 -5
- package/dist/zlm-embed.mjs +5734 -5362
- package/package.json +2 -2
- package/dist/agent/commands/provider.d.ts +0 -2
- package/dist/agent/commands/provider.js +0 -75
|
@@ -18,6 +18,8 @@ export interface ConfigController {
|
|
|
18
18
|
clearSessionOverrides?: () => void;
|
|
19
19
|
withConfigScope?: <T>(scope: ZTCConfigScope, fn: () => T | Promise<T>) => Promise<T>;
|
|
20
20
|
refresh?: () => Promise<void>;
|
|
21
|
+
factoryReset?: () => Promise<void>;
|
|
22
|
+
switchProfile?: (name: string, activate: () => void | Promise<void>) => Promise<void>;
|
|
21
23
|
hasApiKey: () => boolean;
|
|
22
24
|
getApiKey: (provider?: string) => string | undefined;
|
|
23
25
|
setApiKey: (key: string, provider?: string) => void;
|
|
@@ -92,6 +94,7 @@ export interface ClipboardController {
|
|
|
92
94
|
export interface ModelsController {
|
|
93
95
|
list: (provider?: string, options?: {
|
|
94
96
|
signal?: AbortSignal;
|
|
97
|
+
authMode?: 'direct' | 'zrouter';
|
|
95
98
|
}) => Promise<string[]>;
|
|
96
99
|
}
|
|
97
100
|
export interface SkillsController {
|
|
@@ -267,12 +270,14 @@ export interface CommandContext {
|
|
|
267
270
|
*/
|
|
268
271
|
getContextWindow?: () => number | undefined;
|
|
269
272
|
/**
|
|
270
|
-
* Provider-reported cached input tokens from the last
|
|
271
|
-
*
|
|
273
|
+
* Provider-reported cached input tokens from the last model call, a subset of
|
|
274
|
+
* getCacheInputTokens(). Undefined when the provider reported no cache
|
|
272
275
|
* accounting — which is not the same as a 0% hit rate, so /context omits the
|
|
273
276
|
* line rather than reporting a measured miss.
|
|
274
277
|
*/
|
|
275
278
|
getCachedInputTokens?: () => number | undefined;
|
|
279
|
+
/** Raw provider input for the last model call, the cache rate denominator. */
|
|
280
|
+
getCacheInputTokens?: () => number | undefined;
|
|
276
281
|
/**
|
|
277
282
|
* Push a new context size to the bar/state after a CLIENT-side context change
|
|
278
283
|
* that the backend won't report (e.g. /clear -> 0, /compact -> reduced size).
|
|
@@ -285,6 +290,8 @@ export interface CommandContext {
|
|
|
285
290
|
reloadAgent: (options?: {
|
|
286
291
|
defer?: boolean;
|
|
287
292
|
}) => void;
|
|
293
|
+
/** Reconfigure this running session from another config profile. */
|
|
294
|
+
switchConfigProfile?: (name: string) => Promise<void>;
|
|
288
295
|
/**
|
|
289
296
|
* Apply a provider/model selection to the currently running process. This
|
|
290
297
|
* updates any session-local runtime override before recreating the agent, so
|
|
@@ -407,9 +414,19 @@ export interface SettingItem {
|
|
|
407
414
|
} | void;
|
|
408
415
|
}
|
|
409
416
|
export interface SettingsPanelController {
|
|
417
|
+
/** Release the panel before handing focus to a form or another widget. */
|
|
418
|
+
closeSettingsPanel?: () => void;
|
|
410
419
|
showSettingsPanel: (options: {
|
|
411
420
|
title?: string;
|
|
412
421
|
categories: SettingCategory[];
|
|
422
|
+
/** Render one category as a compact interaction page, without navigation chrome. */
|
|
423
|
+
layout?: 'page';
|
|
424
|
+
/** Validation or save error displayed below the page title. */
|
|
425
|
+
notice?: string;
|
|
426
|
+
/** Select this row when opening the page, for example after validation. */
|
|
427
|
+
initialSetting?: string;
|
|
428
|
+
/** Select a category without narrowing the native category list. */
|
|
429
|
+
initialCategory?: string;
|
|
413
430
|
initialFilter?: string;
|
|
414
431
|
onSettingChange: (setting: string, value: string) => void | Promise<void>;
|
|
415
432
|
/**
|
|
@@ -122,7 +122,7 @@ export const askUserTool = {
|
|
|
122
122
|
name: 'ask_user',
|
|
123
123
|
description: `Ask the user one or more questions with predefined options. Use this to get user preferences, clarify requirements, or confirm approach before proceeding.
|
|
124
124
|
|
|
125
|
-
The user will see a selection widget (or multi-question form for multiple questions) with the options you provide
|
|
125
|
+
The user will see a selection widget (or multi-question form for multiple questions) with the options you provide and a field for optional additional context. Wait for the user's response before continuing.
|
|
126
126
|
|
|
127
127
|
## Single question mode
|
|
128
128
|
Use "question" and "options_json" parameters.
|
|
@@ -155,7 +155,7 @@ Example: [{"question": "Which framework?", "options": [{"label": "React", "descr
|
|
|
155
155
|
},
|
|
156
156
|
allow_other: {
|
|
157
157
|
type: 'boolean',
|
|
158
|
-
description: '
|
|
158
|
+
description: 'Legacy browser-mirror custom-answer setting. The terminal questionnaire uses an optional note with the selected option.',
|
|
159
159
|
},
|
|
160
160
|
},
|
|
161
161
|
required: [],
|
|
@@ -52,7 +52,7 @@ export declare class AgentRunLifecycle {
|
|
|
52
52
|
* The callback should contain the renderer-specific event switch.
|
|
53
53
|
* @returns The agent response and whether it was cancelled
|
|
54
54
|
*/
|
|
55
|
-
run(agent: Agent, messages: Message[], onEvent: (event: AgentEvent) => void): Promise<AgentRunResult>;
|
|
55
|
+
run(agent: Agent, messages: Message[], onEvent: (event: AgentEvent) => void, takeSteering?: () => Message[]): Promise<AgentRunResult>;
|
|
56
56
|
/**
|
|
57
57
|
* Cancel the currently active run (if any).
|
|
58
58
|
* Aborts the in-flight API call and stops event delivery.
|
package/dist/app/agent_runner.js
CHANGED
|
@@ -140,7 +140,7 @@ export class AgentRunLifecycle {
|
|
|
140
140
|
* The callback should contain the renderer-specific event switch.
|
|
141
141
|
* @returns The agent response and whether it was cancelled
|
|
142
142
|
*/
|
|
143
|
-
async run(agent, messages, onEvent) {
|
|
143
|
+
async run(agent, messages, onEvent, takeSteering) {
|
|
144
144
|
// Abort previous run if still in flight to avoid concurrent API calls
|
|
145
145
|
if (this.abortController) {
|
|
146
146
|
this.abortController.abort();
|
|
@@ -160,7 +160,7 @@ export class AgentRunLifecycle {
|
|
|
160
160
|
// iterates. Renderers own their controller state and persist the visible
|
|
161
161
|
// final turn themselves, so keep those API-only mutations off the live
|
|
162
162
|
// transcript array.
|
|
163
|
-
const response = await agent.run(messages.slice(), controller.signal);
|
|
163
|
+
const response = await agent.run(messages.slice(), controller.signal, takeSteering);
|
|
164
164
|
const cancelled = !isActive();
|
|
165
165
|
return { response: cancelled ? null : response, cancelled };
|
|
166
166
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Keep the chosen option distinct from any prose the user adds. */
|
|
2
|
+
export function formatAskUserResponse(questions, answers) {
|
|
3
|
+
const values = questions.map((question, index) => {
|
|
4
|
+
const answer = answers[`q${index}`];
|
|
5
|
+
const note = answers[`q${index}_note`];
|
|
6
|
+
return {
|
|
7
|
+
question: question.question,
|
|
8
|
+
answer: Array.isArray(answer) ? answer.join(', ') : (answer || '(no answer)'),
|
|
9
|
+
...(typeof note === 'string' && note.trim() ? { note } : {}),
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
if (values.length !== 1)
|
|
13
|
+
return JSON.stringify(values);
|
|
14
|
+
const { answer, note } = values[0];
|
|
15
|
+
return note ? `${answer}\n\nAdditional context: ${note}` : answer;
|
|
16
|
+
}
|
|
@@ -30,7 +30,7 @@ export function routeUserInput(text) {
|
|
|
30
30
|
}
|
|
31
31
|
// Slash commands (but not file paths)
|
|
32
32
|
if (text.startsWith('/') && !FILE_PATH_PREFIXES.some(p => text.startsWith(p))) {
|
|
33
|
-
const parts = text.slice(1).split(/\s+/);
|
|
33
|
+
const parts = text.slice(1).trimEnd().split(/\s+/);
|
|
34
34
|
const cmd = parts[0];
|
|
35
35
|
if (!cmd.includes('/')) {
|
|
36
36
|
return { type: 'command', cmd, args: parts.slice(1) };
|
|
@@ -45,7 +45,8 @@ export function routeUserInput(text) {
|
|
|
45
45
|
export function buildModelsController(config) {
|
|
46
46
|
return {
|
|
47
47
|
list: async (provider, options) => {
|
|
48
|
-
const llm = config.resolveLLMBackendConfig({ provider
|
|
48
|
+
const llm = config.resolveLLMBackendConfig({ provider,
|
|
49
|
+
config: options?.authMode ? { ...config.get(), authMode: options.authMode } : undefined });
|
|
49
50
|
// Preserve discovery failures so callers can distinguish an unavailable
|
|
50
51
|
// catalog from a successful provider response with zero supported models.
|
|
51
52
|
return listModels({
|
|
@@ -174,10 +175,36 @@ export function buildAutoSelectWidgets() {
|
|
|
174
175
|
},
|
|
175
176
|
};
|
|
176
177
|
}
|
|
178
|
+
/** Session controls do not execute the plan or change project files. */
|
|
179
|
+
const PLAN_MODE_SESSION_COMMANDS = new Set([
|
|
180
|
+
'accounts',
|
|
181
|
+
'auth',
|
|
182
|
+
'config',
|
|
183
|
+
'context',
|
|
184
|
+
'effort',
|
|
185
|
+
'fast',
|
|
186
|
+
'help',
|
|
187
|
+
'keybindings',
|
|
188
|
+
'keymap',
|
|
189
|
+
'login',
|
|
190
|
+
'logout',
|
|
191
|
+
'model',
|
|
192
|
+
'models',
|
|
193
|
+
'permissions',
|
|
194
|
+
'profile',
|
|
195
|
+
'settings',
|
|
196
|
+
'shortcuts',
|
|
197
|
+
'skills',
|
|
198
|
+
'status',
|
|
199
|
+
'theme',
|
|
200
|
+
'thinking',
|
|
201
|
+
]);
|
|
177
202
|
export function getPlanModeCommandDenial(currentMode, cmd, args) {
|
|
178
203
|
if (currentMode !== 'plan')
|
|
179
204
|
return null;
|
|
180
205
|
const normalizedCmd = cmd.toLowerCase();
|
|
206
|
+
if (PLAN_MODE_SESSION_COMMANDS.has(normalizedCmd))
|
|
207
|
+
return null;
|
|
181
208
|
if (normalizedCmd === 'execute')
|
|
182
209
|
return null;
|
|
183
210
|
if (normalizedCmd === 'plan')
|
|
@@ -190,17 +217,6 @@ export function getPlanModeCommandDenial(currentMode, cmd, args) {
|
|
|
190
217
|
return null;
|
|
191
218
|
if (normalizedCmd === 'modes')
|
|
192
219
|
return null;
|
|
193
|
-
if (normalizedCmd === 'auth') {
|
|
194
|
-
if (args.length === 1 && args[0].toLowerCase() === 'login')
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
if (normalizedCmd === 'config') {
|
|
198
|
-
const subcommand = args[0]?.toLowerCase();
|
|
199
|
-
if (subcommand === 'provider' && args.length === 2)
|
|
200
|
-
return null;
|
|
201
|
-
if (subcommand === 'key' && (args.length === 2 || args.length === 3))
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
220
|
if (normalizedCmd === 'mode') {
|
|
205
221
|
if (args.length === 0)
|
|
206
222
|
return null;
|
package/dist/app/controller.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* The state is a single immutable object that gets updated via dispatch().
|
|
8
8
|
* Subscribers are notified whenever the state changes.
|
|
9
9
|
*/
|
|
10
|
-
import type { Message, AgentState, InputMode, MessageInput, QueueEntry, TodoStatus } from '../types.js';
|
|
10
|
+
import type { Message, AgentState, InputMode, MessageInput, QueueEntry, DeliveryIntent, TodoStatus } from '../types.js';
|
|
11
11
|
import type { InputState } from '../ui/core/input_state.js';
|
|
12
12
|
import type { ContextStatus } from '../utils/context.js';
|
|
13
13
|
import type { Skill } from '../skills/types.js';
|
|
@@ -262,13 +262,28 @@ export declare class AppController {
|
|
|
262
262
|
/**
|
|
263
263
|
* Enqueue a message. Returns true if accepted, false if queue is full.
|
|
264
264
|
* Single writer for queue mutations (together with dequeue/remove/clear).
|
|
265
|
+
*
|
|
266
|
+
* `delivery` records when the message should reach the model. It defaults to
|
|
267
|
+
* `end-of-turn`, the conservative choice: an entry whose intent was never
|
|
268
|
+
* stated must wait for the whole turn rather than slip into the middle of one.
|
|
265
269
|
*/
|
|
266
|
-
enqueueMessage(input: string | MessageInput): boolean;
|
|
270
|
+
enqueueMessage(input: string | MessageInput, delivery?: DeliveryIntent): boolean;
|
|
267
271
|
/**
|
|
268
272
|
* Remove the head of the queue and return it.
|
|
269
273
|
* Called by the flush-on-idle path.
|
|
270
274
|
*/
|
|
271
275
|
dequeueMessage(): QueueEntry | undefined;
|
|
276
|
+
/**
|
|
277
|
+
* Remove the OLDEST `subturn` entry and return it, leaving `end-of-turn`
|
|
278
|
+
* entries in place. Returns undefined when nothing is marked for sub-turn
|
|
279
|
+
* delivery.
|
|
280
|
+
*
|
|
281
|
+
* Enter-during-a-turn and Tab-during-a-turn are different promises: the first
|
|
282
|
+
* wants the next tool boundary, the second wants the end of the turn. Failing
|
|
283
|
+
* over to `dequeueMessage` here would let a Tab-queued prompt jump the queue
|
|
284
|
+
* and land mid-turn, so this returns nothing instead and lets the caller wait.
|
|
285
|
+
*/
|
|
286
|
+
dequeueSubturnMessage(): QueueEntry | undefined;
|
|
272
287
|
removeQueueEntry(id: string): void;
|
|
273
288
|
/** Rewrite a queued entry's text in place. Returns false when the id is no longer queued. */
|
|
274
289
|
updateQueueEntry(id: string, text: string): boolean;
|
package/dist/app/controller.js
CHANGED
|
@@ -413,8 +413,12 @@ export class AppController {
|
|
|
413
413
|
/**
|
|
414
414
|
* Enqueue a message. Returns true if accepted, false if queue is full.
|
|
415
415
|
* Single writer for queue mutations (together with dequeue/remove/clear).
|
|
416
|
+
*
|
|
417
|
+
* `delivery` records when the message should reach the model. It defaults to
|
|
418
|
+
* `end-of-turn`, the conservative choice: an entry whose intent was never
|
|
419
|
+
* stated must wait for the whole turn rather than slip into the middle of one.
|
|
416
420
|
*/
|
|
417
|
-
enqueueMessage(input) {
|
|
421
|
+
enqueueMessage(input, delivery = 'end-of-turn') {
|
|
418
422
|
if (this.state.queue.length >= MAX_QUEUE)
|
|
419
423
|
return false;
|
|
420
424
|
const text = typeof input === 'string' ? input : input.text;
|
|
@@ -423,6 +427,7 @@ export class AppController {
|
|
|
423
427
|
text,
|
|
424
428
|
attachments: typeof input === 'string' ? [] : input.attachments,
|
|
425
429
|
queuedAt: new Date(),
|
|
430
|
+
delivery,
|
|
426
431
|
};
|
|
427
432
|
this.dispatch({ type: 'ENQUEUE_MESSAGE', entry });
|
|
428
433
|
return true;
|
|
@@ -438,6 +443,24 @@ export class AppController {
|
|
|
438
443
|
this.dispatch({ type: 'DEQUEUE_MESSAGE' });
|
|
439
444
|
return head;
|
|
440
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Remove the OLDEST `subturn` entry and return it, leaving `end-of-turn`
|
|
448
|
+
* entries in place. Returns undefined when nothing is marked for sub-turn
|
|
449
|
+
* delivery.
|
|
450
|
+
*
|
|
451
|
+
* Enter-during-a-turn and Tab-during-a-turn are different promises: the first
|
|
452
|
+
* wants the next tool boundary, the second wants the end of the turn. Failing
|
|
453
|
+
* over to `dequeueMessage` here would let a Tab-queued prompt jump the queue
|
|
454
|
+
* and land mid-turn, so this returns nothing instead and lets the caller wait.
|
|
455
|
+
*/
|
|
456
|
+
dequeueSubturnMessage() {
|
|
457
|
+
const index = this.state.queue.findIndex(entry => entry.delivery === 'subturn');
|
|
458
|
+
if (index < 0)
|
|
459
|
+
return undefined;
|
|
460
|
+
const entry = this.state.queue[index];
|
|
461
|
+
this.dispatch({ type: 'REMOVE_QUEUE_ENTRY', id: entry.id });
|
|
462
|
+
return entry;
|
|
463
|
+
}
|
|
441
464
|
removeQueueEntry(id) {
|
|
442
465
|
this.dispatch({ type: 'REMOVE_QUEUE_ENTRY', id });
|
|
443
466
|
}
|
|
@@ -12,6 +12,9 @@ export interface SelectionCompletionView {
|
|
|
12
12
|
query?: string;
|
|
13
13
|
usage?: string;
|
|
14
14
|
provider?: string;
|
|
15
|
+
mixed?: boolean;
|
|
16
|
+
/** True when this model branch dispatches through Router, not a direct key. */
|
|
17
|
+
routed?: boolean;
|
|
15
18
|
candidates: SelectionCandidate[];
|
|
16
19
|
selected: number;
|
|
17
20
|
firstVisible?: number;
|
|
@@ -30,10 +33,13 @@ export declare class SelectionCompletion {
|
|
|
30
33
|
private changed;
|
|
31
34
|
private text;
|
|
32
35
|
private provider;
|
|
36
|
+
private catalogProvider;
|
|
37
|
+
private catalogRoute;
|
|
33
38
|
private parsed;
|
|
34
39
|
private candidates;
|
|
35
40
|
private selected;
|
|
36
41
|
private firstVisible;
|
|
42
|
+
private visibleRows;
|
|
37
43
|
private dismissedInput;
|
|
38
44
|
private discovered;
|
|
39
45
|
private loading;
|
|
@@ -44,9 +50,10 @@ export declare class SelectionCompletion {
|
|
|
44
50
|
private commands;
|
|
45
51
|
private currentModel?;
|
|
46
52
|
constructor(changed?: () => void);
|
|
47
|
-
update(text: string, provider: string, loadModels?: (signal: AbortSignal) => Promise<string[]>, commands?: SlashCommandUsage[], currentModel?: string): void;
|
|
53
|
+
update(text: string, provider: string, loadModels?: (signal: AbortSignal, provider: string, authMode?: 'direct' | 'zrouter') => Promise<string[]>, commands?: SlashCommandUsage[], currentModel?: string): void;
|
|
48
54
|
private filter;
|
|
49
55
|
private revealSelection;
|
|
56
|
+
setVisibleRows(rows: number): void;
|
|
50
57
|
/** Keep the menu closed for text the user did not type character by character
|
|
51
58
|
* (a prompt recalled from history). The candidate list would otherwise capture
|
|
52
59
|
* Up/Down, so the arrows could no longer walk history. Editing the text clears
|
|
@@ -66,8 +73,7 @@ export declare class SelectionCompletion {
|
|
|
66
73
|
}): {
|
|
67
74
|
handled: boolean;
|
|
68
75
|
text?: string;
|
|
69
|
-
submit?: boolean;
|
|
70
76
|
};
|
|
71
77
|
}
|
|
72
78
|
export declare const SELECTION_VISIBLE_ROWS = 4;
|
|
73
|
-
export declare function selectionCompletionRows(view?: SelectionCompletionView | null): number;
|
|
79
|
+
export declare function selectionCompletionRows(view?: SelectionCompletionView | null, availableRows?: number): number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MODEL_CATALOG } from '../panes/model_catalog.js';
|
|
2
|
-
import { canonicalProvider, PROVIDER_CHOICES
|
|
2
|
+
import { canRouteThroughRouter, canonicalProvider, PROVIDER_CHOICES } from '../agent/commands/provider_choices.js';
|
|
3
3
|
import { resolveSlashUsage } from '../agent/commands/usage_spec.js';
|
|
4
4
|
import { splitInputArgs } from '../agent/commands/usage_parser.js';
|
|
5
5
|
const fold = (value) => value.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
|
|
@@ -42,14 +42,6 @@ function commandUsage(text, commands) {
|
|
|
42
42
|
function parseSelectionInput(text, commands) {
|
|
43
43
|
if (commands.length && /^\/[\w-]*$/.test(text))
|
|
44
44
|
return { kind: 'command', prefix: '/', query: text.slice(1) };
|
|
45
|
-
const provider = text.match(/^\/provider\s+([^\n]*)$/i);
|
|
46
|
-
if (provider) {
|
|
47
|
-
const routed = provider[1].match(/^(\S+)\s+(.*)$/);
|
|
48
|
-
if (routed && canonicalProvider(routed[1]) === 'zergrouter') {
|
|
49
|
-
return { kind: 'provider', prefix: `/provider ${routed[1]} `, query: routed[2], routerOnly: true };
|
|
50
|
-
}
|
|
51
|
-
return { kind: 'provider', prefix: '/provider ', query: provider[1] };
|
|
52
|
-
}
|
|
53
45
|
const model = text.match(/^\/(model|models)\s+([^\n]*)$/i);
|
|
54
46
|
if (!model)
|
|
55
47
|
return commandUsage(text, commands);
|
|
@@ -60,7 +52,23 @@ function parseSelectionInput(text, commands) {
|
|
|
60
52
|
return { kind: 'model', prefix: `/${model[1]} set `, query: arg.replace(/^set\s+/i, '') };
|
|
61
53
|
if (model[1].toLowerCase() === 'models' || arg.toLowerCase() === 'set')
|
|
62
54
|
return commandUsage(text, commands);
|
|
63
|
-
|
|
55
|
+
const first = arg.match(/^(\S+)(?:\s+(.*))?$/);
|
|
56
|
+
const choice = first && canonicalProvider(first[1]);
|
|
57
|
+
if (choice === 'zergrouter' && first?.[2] !== undefined) {
|
|
58
|
+
const upstream = first[2].match(/^(\S+)(?:\s+(.*))?$/);
|
|
59
|
+
// Router's catalog is the source of truth, so an upstream it serves is
|
|
60
|
+
// accepted even when ZTC ships no PROVIDER_CHOICES entry for it yet. Only
|
|
61
|
+
// the exclusion rule (local logins, nesting, private endpoints) blocks one.
|
|
62
|
+
const selected = upstream && (canonicalProvider(upstream[1]) ?? upstream[1]);
|
|
63
|
+
if (selected && canRouteThroughRouter(selected) && upstream?.[2] !== undefined) {
|
|
64
|
+
return { kind: 'model', prefix: `/model ${first[1]} ${upstream[1]} `, query: upstream[2], modelProvider: selected, routed: true };
|
|
65
|
+
}
|
|
66
|
+
return { kind: 'provider', prefix: `/model ${first[1]} `, query: first[2], routerOnly: true };
|
|
67
|
+
}
|
|
68
|
+
if (choice && first?.[2] !== undefined) {
|
|
69
|
+
return { kind: 'model', prefix: `/model ${first[1]} `, query: first[2], modelProvider: choice, routed: choice === 'modal' };
|
|
70
|
+
}
|
|
71
|
+
return { kind: 'provider', prefix: '/model ', query: arg, includeModels: true };
|
|
64
72
|
}
|
|
65
73
|
export function modelSelections(provider, discovered) {
|
|
66
74
|
const normalized = canonicalProvider(provider) || provider;
|
|
@@ -102,10 +110,13 @@ export class SelectionCompletion {
|
|
|
102
110
|
changed;
|
|
103
111
|
text = '';
|
|
104
112
|
provider = '';
|
|
113
|
+
catalogProvider = '';
|
|
114
|
+
catalogRoute;
|
|
105
115
|
parsed = null;
|
|
106
116
|
candidates = [];
|
|
107
117
|
selected = 0;
|
|
108
118
|
firstVisible = 0;
|
|
119
|
+
visibleRows = SELECTION_VISIBLE_ROWS;
|
|
109
120
|
dismissedInput = null;
|
|
110
121
|
discovered;
|
|
111
122
|
loading = false;
|
|
@@ -122,7 +133,9 @@ export class SelectionCompletion {
|
|
|
122
133
|
this.commands = commands;
|
|
123
134
|
this.currentModel = currentModel;
|
|
124
135
|
const parsed = parseSelectionInput(text, commands);
|
|
125
|
-
|
|
136
|
+
const catalogProvider = parsed?.modelProvider ?? provider;
|
|
137
|
+
if (provider !== this.provider || catalogProvider !== this.catalogProvider
|
|
138
|
+
|| parsed?.routed !== this.catalogRoute || parsed?.kind !== this.parsed?.kind) {
|
|
126
139
|
this.requestController?.abort();
|
|
127
140
|
this.generation++;
|
|
128
141
|
this.requested = false;
|
|
@@ -139,15 +152,18 @@ export class SelectionCompletion {
|
|
|
139
152
|
this.dismissedInput = null;
|
|
140
153
|
this.text = text;
|
|
141
154
|
this.provider = provider;
|
|
155
|
+
this.catalogProvider = catalogProvider;
|
|
156
|
+
this.catalogRoute = parsed?.routed;
|
|
142
157
|
this.parsed = parsed;
|
|
143
158
|
this.filter();
|
|
144
|
-
if (parsed?.kind === 'model' && !this.requested && loadModels) {
|
|
159
|
+
if ((parsed?.kind === 'model' || parsed?.includeModels) && !this.requested && loadModels) {
|
|
145
160
|
this.requested = true;
|
|
146
161
|
this.loading = true;
|
|
147
162
|
this.requestController = new AbortController();
|
|
148
163
|
const signal = this.requestController.signal;
|
|
149
164
|
const generation = this.generation;
|
|
150
|
-
|
|
165
|
+
const authMode = parsed?.routed === undefined ? undefined : parsed.routed ? 'zrouter' : 'direct';
|
|
166
|
+
void Promise.resolve().then(() => loadModels(signal, catalogProvider, authMode)).then(models => {
|
|
151
167
|
if (generation !== this.generation)
|
|
152
168
|
return;
|
|
153
169
|
this.discovered = models;
|
|
@@ -165,10 +181,14 @@ export class SelectionCompletion {
|
|
|
165
181
|
}
|
|
166
182
|
filter() {
|
|
167
183
|
const selectedValue = this.candidates[this.selected]?.value;
|
|
184
|
+
const models = modelSelections(this.catalogProvider, this.discovered)
|
|
185
|
+
.map(model => ({ ...model, current: this.catalogProvider === this.provider && model.value === this.currentModel }));
|
|
186
|
+
const providers = PROVIDER_CHOICES.filter(p => !this.parsed?.routerOnly || canRouteThroughRouter(p.id))
|
|
187
|
+
.map(p => ({ value: p.id, label: p.label, description: p.description, aliases: p.aliases,
|
|
188
|
+
current: p.id === this.provider, needsArgument: true }));
|
|
168
189
|
const source = this.parsed?.kind === 'command' ? commandSelections(this.commands)
|
|
169
|
-
: this.parsed?.kind === 'model' ?
|
|
170
|
-
:
|
|
171
|
-
.map(p => ({ value: p.id, label: p.label, description: p.description, aliases: p.aliases, current: p.id === this.provider }));
|
|
190
|
+
: this.parsed?.kind === 'model' ? models
|
|
191
|
+
: this.parsed?.includeModels ? [...providers, ...models] : providers;
|
|
172
192
|
this.candidates = this.parsed && this.parsed.kind !== 'usage' ? fuzzySelections(source, this.parsed.query) : [];
|
|
173
193
|
const previousIndex = this.candidates.findIndex(c => c.value === selectedValue);
|
|
174
194
|
this.selected = previousIndex >= 0 ? previousIndex : 0;
|
|
@@ -176,8 +196,13 @@ export class SelectionCompletion {
|
|
|
176
196
|
}
|
|
177
197
|
revealSelection() {
|
|
178
198
|
this.firstVisible = Math.max(0, Math.min(this.firstVisible, this.selected));
|
|
179
|
-
if (this.selected >= this.firstVisible +
|
|
180
|
-
this.firstVisible = this.selected -
|
|
199
|
+
if (this.selected >= this.firstVisible + this.visibleRows)
|
|
200
|
+
this.firstVisible = this.selected - this.visibleRows + 1;
|
|
201
|
+
this.firstVisible = Math.min(this.firstVisible, Math.max(0, this.candidates.length - this.visibleRows));
|
|
202
|
+
}
|
|
203
|
+
setVisibleRows(rows) {
|
|
204
|
+
this.visibleRows = Math.max(1, Math.floor(rows));
|
|
205
|
+
this.revealSelection();
|
|
181
206
|
}
|
|
182
207
|
/** Keep the menu closed for text the user did not type character by character
|
|
183
208
|
* (a prompt recalled from history). The candidate list would otherwise capture
|
|
@@ -193,7 +218,9 @@ export class SelectionCompletion {
|
|
|
193
218
|
return null;
|
|
194
219
|
return { candidates: this.candidates, selected: this.selected, firstVisible: this.firstVisible, loading: this.loading, unavailable: this.unavailable,
|
|
195
220
|
kind: this.parsed.kind, query: this.parsed.query, usage: this.parsed.usage,
|
|
196
|
-
provider: this.parsed.kind === 'model' ? this.
|
|
221
|
+
provider: this.parsed.kind === 'model' ? this.catalogProvider : undefined,
|
|
222
|
+
routed: this.parsed.kind === 'model' ? this.catalogRoute : undefined,
|
|
223
|
+
mixed: this.parsed.includeModels };
|
|
197
224
|
}
|
|
198
225
|
handleKey(key) {
|
|
199
226
|
if (!this.parsed || key.shift || key.ctrl || key.meta)
|
|
@@ -225,15 +252,20 @@ export class SelectionCompletion {
|
|
|
225
252
|
if (this.parsed.kind === 'command' && (key.tab || candidate.needsArgument)) {
|
|
226
253
|
return { handled: true, text: `${completed} ` };
|
|
227
254
|
}
|
|
255
|
+
if (this.parsed.kind === 'provider' && candidate.needsArgument) {
|
|
256
|
+
return { handled: true, text: `${completed} ` };
|
|
257
|
+
}
|
|
258
|
+
if (key.return && completed === this.text)
|
|
259
|
+
return { handled: false };
|
|
228
260
|
this.dismissedInput = completed;
|
|
229
|
-
return { handled: true, text: completed
|
|
261
|
+
return { handled: true, text: completed };
|
|
230
262
|
}
|
|
231
263
|
return { handled: false };
|
|
232
264
|
}
|
|
233
265
|
}
|
|
234
266
|
export const SELECTION_VISIBLE_ROWS = 4;
|
|
235
|
-
export function selectionCompletionRows(view) {
|
|
267
|
+
export function selectionCompletionRows(view, availableRows = SELECTION_VISIBLE_ROWS + 1) {
|
|
236
268
|
if (view?.kind === 'usage')
|
|
237
269
|
return 1;
|
|
238
|
-
return view ? Math.
|
|
270
|
+
return view ? Math.min(availableRows, Math.max(1, view.candidates.length) + 1) : 0;
|
|
239
271
|
}
|
package/dist/cli_args.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CODEX_REASONING_EFFORTS } from './config/types.js';
|
|
2
2
|
import { getSupportedCodexReasoningEfforts, unsupportedEffortMessage } from './utils/models.js';
|
|
3
|
+
import { validateProfileName } from './config/profiles.js';
|
|
3
4
|
const BOOLEAN_FLAGS = new Set([
|
|
4
5
|
'--no-pin-task-header', '--help', '-h', '--version', '-v', '--list-sessions', '--plan', '--desktop', '--mux', '--no-mux', '--new-window', '--fullscreen', '--pane', '--human-sim-pane', '--zlicky', '--computer-use', '--ml', '--sfx-list', '--voyager-loop', '--mirror', '--open-mirror', '--mirror-open', '--no-browser-open', '--navigator-hidden', '--headless-mirror', '--headless-mirror-open', '--headless-interactive', '--headless', '--json', '--json-stream', '--quiet', '-q', '--timestamps', '--verbose', '--colony',
|
|
5
6
|
]);
|
|
6
7
|
const REQUIRED_VALUE_FLAGS = new Set([
|
|
7
|
-
'--cwd', '--name', '--session-id', '--session-file', '--mode', '--agent-domain', '--domain-paths', '--embedded', '--embedded-rom', '--embedded-workflow', '--embedded-runtime-tick-ms', '--embedded-runtime-frame-budget', '--bench', '--bench-workflow', '--num-zergs', '--provider', '--model', '--effort', '--thinking', '--emulation', '--prompt', '--max-iterations', '--timeout', '--output', '--voyager-task', '--voyager-max-rounds', '--mirror-port', '--navigator-group', '--connect-zerg',
|
|
8
|
+
'--cwd', '--name', '--session-id', '--session-file', '--mode', '--agent-domain', '--domain-paths', '--embedded', '--embedded-rom', '--embedded-workflow', '--embedded-runtime-tick-ms', '--embedded-runtime-frame-budget', '--bench', '--bench-workflow', '--num-zergs', '--provider', '--model', '--profile', '--effort', '--thinking', '--emulation', '--prompt', '--max-iterations', '--timeout', '--output', '--voyager-task', '--voyager-max-rounds', '--mirror-port', '--navigator-group', '--connect-zerg',
|
|
8
9
|
]);
|
|
9
10
|
const OPTIONAL_VALUE_FLAGS = new Set(['--resume', '--continue', '-c', '--sfx']);
|
|
10
11
|
/** Separate prompt text from options before the legacy flag consumers see it. */
|
|
@@ -87,6 +88,14 @@ export function validateCliArgs(args) {
|
|
|
87
88
|
value: next,
|
|
88
89
|
};
|
|
89
90
|
}
|
|
91
|
+
if (token === '--profile') {
|
|
92
|
+
try {
|
|
93
|
+
validateProfileName(next);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return { ok: false, message: error.message, error: 'invalid_profile', flag: token, value: next };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
90
99
|
if (token === '--effort') {
|
|
91
100
|
if (!CODEX_REASONING_EFFORTS.includes(next)) {
|
|
92
101
|
return { ok: false, message: `--effort must be one of: ${CODEX_REASONING_EFFORTS.join(', ')}.`, error: 'invalid_effort', flag: token, value: next };
|
package/dist/cli_main.js
CHANGED
|
@@ -30,13 +30,71 @@ import { computeHeadlessMirror } from './panes/headless_mode.js';
|
|
|
30
30
|
import { parsePromptInvocation, requiresPlanSafeRenderer, validateCliArgs, } from './cli_args.js';
|
|
31
31
|
import { parseHeadlessInteractionMode } from './app/headless_interaction.js';
|
|
32
32
|
import { buildCliCommonStarts } from './onboarding/first_run.js';
|
|
33
|
+
import { validateProfileName } from './config/profiles.js';
|
|
33
34
|
// Parse command line args
|
|
34
35
|
const args = process.argv.slice(2);
|
|
36
|
+
const profileExplicitAtLaunch = args.includes('--profile') || process.env.ZTC_PROFILE !== undefined;
|
|
37
|
+
const profileFlags = args.flatMap((arg, index) => arg === '--profile' ? [index] : []);
|
|
38
|
+
if (profileFlags.length > 1) {
|
|
39
|
+
console.error('[ztc] Specify --profile only once.');
|
|
40
|
+
process.exit(2);
|
|
41
|
+
}
|
|
42
|
+
if (profileFlags.length === 1) {
|
|
43
|
+
const value = args[profileFlags[0] + 1];
|
|
44
|
+
try {
|
|
45
|
+
process.env.ZTC_PROFILE = validateProfileName(value || '');
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error(`[ztc] ${error.message}`);
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
validateProfileName(process.env.ZTC_PROFILE || 'default');
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error(`[ztc] ${error.message}`);
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
|
59
|
+
const commandArgs = profileFlags.length === 0
|
|
60
|
+
? args
|
|
61
|
+
: args.filter((_, index) => index !== profileFlags[0] && index !== profileFlags[0] + 1);
|
|
35
62
|
// Device login must work without a TTY, provider credentials, or agent startup.
|
|
36
|
-
if (
|
|
63
|
+
if (commandArgs[0] === 'auth') {
|
|
37
64
|
try {
|
|
38
65
|
const { runRouterAuthCommand } = await import('./router_auth.js');
|
|
39
|
-
await runRouterAuthCommand(
|
|
66
|
+
await runRouterAuthCommand(commandArgs.slice(1));
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error(`[ztc] ${error.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
if (commandArgs[0] === 'config' && commandArgs[1] === 'reset') {
|
|
75
|
+
if (commandArgs.length !== 2) {
|
|
76
|
+
console.error('Usage: ztc config reset [--profile <name>]');
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const { configStore } = await import('./config.js');
|
|
81
|
+
const { createInterface } = await import('node:readline/promises');
|
|
82
|
+
const path = configStore.getConfigPath();
|
|
83
|
+
console.log(`Factory reset settings and ZRouter sign-in for ${path}. This deletes the API keys, OpenAI-compatible endpoint, account registrations, voice keys and MCP servers saved there. Sessions and managed account directories will be kept.`);
|
|
84
|
+
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
85
|
+
let answer;
|
|
86
|
+
try {
|
|
87
|
+
answer = await input.question('Type RESET to confirm: ');
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
input.close();
|
|
91
|
+
}
|
|
92
|
+
if (answer !== 'RESET') {
|
|
93
|
+
console.log('Factory reset cancelled.');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
await configStore.factoryReset();
|
|
97
|
+
console.log(`Factory reset complete: ${path}`);
|
|
40
98
|
}
|
|
41
99
|
catch (error) {
|
|
42
100
|
console.error(`[ztc] ${error.message}`);
|
|
@@ -177,6 +235,7 @@ ZTC - Zerg Terminal Client v${version}
|
|
|
177
235
|
|
|
178
236
|
Usage: ztc [options] [prompt]
|
|
179
237
|
ztc auth <login|status|logout>
|
|
238
|
+
ztc config reset [--profile <name>]
|
|
180
239
|
ztc resume [id]
|
|
181
240
|
ztc --branch <workspace-name> [options]
|
|
182
241
|
|
|
@@ -239,6 +298,8 @@ Options:
|
|
|
239
298
|
--bench-workflow <id>
|
|
240
299
|
Run a bench workflow after opening the workspace
|
|
241
300
|
--provider <name> LLM provider override (anthropic, claude_code, openai, codex_app_server, codex_legacy, gemini, inception, kimi, kimi_code, etc.; codex is a legacy alias)
|
|
301
|
+
--profile <name> Use named settings in ~/.ztc/profiles/<name>/config.json
|
|
302
|
+
(default: ~/.ztc/config.json). Sessions stay shared.
|
|
242
303
|
--model <name> LLM model override (e.g., claude-sonnet-4-20250514, gpt-4o)
|
|
243
304
|
--effort <level> Override saved reasoning effort for this process
|
|
244
305
|
--thinking <on|off> Enable/disable reasoning when the model supports it
|
|
@@ -889,6 +950,27 @@ async function main() {
|
|
|
889
950
|
// any non-colony-child process.
|
|
890
951
|
if (installColonyParentWatchdog())
|
|
891
952
|
debugLog('[main] Colony parent watchdog armed');
|
|
953
|
+
// Resolve a saved session's profile before loading config, credentials,
|
|
954
|
+
// skills, or MCP. An explicit --profile or ZTC_PROFILE always wins.
|
|
955
|
+
if (!profileExplicitAtLaunch && !explicitSessionFile) {
|
|
956
|
+
const lookup = resumeSessionId || explicitSessionId || nameFlag;
|
|
957
|
+
if (lookup) {
|
|
958
|
+
const { loadMostRecentSession, loadSessionByIdOrName } = await import('./session.js');
|
|
959
|
+
const saved = lookup === 'most-recent'
|
|
960
|
+
? await loadMostRecentSession(process.cwd())
|
|
961
|
+
: await loadSessionByIdOrName(lookup, process.cwd());
|
|
962
|
+
if (saved?.metadata.configProfile) {
|
|
963
|
+
try {
|
|
964
|
+
process.env.ZTC_PROFILE = validateProfileName(saved.metadata.configProfile);
|
|
965
|
+
}
|
|
966
|
+
catch (error) {
|
|
967
|
+
console.error(`[ztc] Saved session has an invalid config profile: ${error.message}`);
|
|
968
|
+
process.exitCode = 2;
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
892
974
|
const { configStore, shouldRefreshSuperuserStatus } = await import('./config.js');
|
|
893
975
|
// Raw (pre-load) config read, same as the historical module-scope read; the
|
|
894
976
|
// --max-iterations flag parsed at module scope wins over the config value.
|
|
@@ -950,6 +1032,7 @@ async function main() {
|
|
|
950
1032
|
// Config-level opt-in mirrors the --colony flag (which set ZTC_COLONY_MCP).
|
|
951
1033
|
if (configStore.get().mcp?.colonyAutoAttach && !process.env.ZTC_COLONY_MCP) {
|
|
952
1034
|
process.env.ZTC_COLONY_MCP = '1';
|
|
1035
|
+
process.env.ZTC_COLONY_MCP_CONFIG_OWNED = '1';
|
|
953
1036
|
}
|
|
954
1037
|
// Only load the MCP client (SDK + zod tree) when there is actually something
|
|
955
1038
|
// to connect: configured servers or a colony opt-in. initMcpServers with an
|