zerg-ztc 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +71 -13
- package/dist/App.js.map +1 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +3 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/commands/index.d.ts.map +1 -1
- package/dist/agent/commands/index.js +3 -1
- package/dist/agent/commands/index.js.map +1 -1
- package/dist/agent/commands/input_mode.d.ts +3 -0
- package/dist/agent/commands/input_mode.d.ts.map +1 -0
- package/dist/agent/commands/input_mode.js +21 -0
- package/dist/agent/commands/input_mode.js.map +1 -0
- package/dist/agent/commands/keybindings.d.ts +3 -0
- package/dist/agent/commands/keybindings.d.ts.map +1 -0
- package/dist/agent/commands/keybindings.js +38 -0
- package/dist/agent/commands/keybindings.js.map +1 -0
- package/dist/agent/commands/types.d.ts +2 -0
- package/dist/agent/commands/types.d.ts.map +1 -1
- package/dist/cli.js +38 -1
- package/dist/cli.js.map +1 -1
- package/dist/components/FullScreen.d.ts.map +1 -1
- package/dist/components/FullScreen.js +29 -29
- package/dist/components/FullScreen.js.map +1 -1
- package/dist/components/InputArea.d.ts.map +1 -1
- package/dist/components/InputArea.js +476 -19
- package/dist/components/InputArea.js.map +1 -1
- package/dist/components/StatusBar.d.ts +1 -0
- package/dist/components/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar.js +2 -1
- package/dist/components/StatusBar.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/core/input_segments.d.ts +1 -0
- package/dist/ui/core/input_segments.d.ts.map +1 -1
- package/dist/ui/core/input_segments.js +46 -14
- package/dist/ui/core/input_segments.js.map +1 -1
- package/dist/ui/core/types.d.ts +1 -0
- package/dist/ui/core/types.d.ts.map +1 -1
- package/dist/ui/ink/render.d.ts +3 -1
- package/dist/ui/ink/render.d.ts.map +1 -1
- package/dist/ui/ink/render.js +7 -5
- package/dist/ui/ink/render.js.map +1 -1
- package/dist/ui/views/app.d.ts +2 -1
- package/dist/ui/views/app.d.ts.map +1 -1
- package/dist/ui/views/app.js +2 -1
- package/dist/ui/views/app.js.map +1 -1
- package/dist/ui/views/header.d.ts.map +1 -1
- package/dist/ui/views/header.js +8 -5
- package/dist/ui/views/header.js.map +1 -1
- package/dist/ui/views/status_bar.d.ts +2 -1
- package/dist/ui/views/status_bar.d.ts.map +1 -1
- package/dist/ui/views/status_bar.js +5 -1
- package/dist/ui/views/status_bar.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +71 -13
- package/src/agent/agent.ts +3 -1
- package/src/agent/commands/index.ts +4 -0
- package/src/agent/commands/input_mode.ts +22 -0
- package/src/agent/commands/keybindings.ts +40 -0
- package/src/agent/commands/types.ts +2 -0
- package/src/cli.tsx +43 -1
- package/src/components/FullScreen.tsx +39 -34
- package/src/components/InputArea.tsx +489 -19
- package/src/components/StatusBar.tsx +3 -0
- package/src/types.ts +1 -0
- package/src/ui/core/input_segments.ts +49 -14
- package/src/ui/core/types.ts +1 -0
- package/src/ui/ink/render.tsx +16 -5
- package/src/ui/views/app.ts +3 -0
- package/src/ui/views/header.ts +8 -5
- package/src/ui/views/status_bar.ts +6 -0
package/dist/ui/views/header.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { box, text } from '../core/index.js';
|
|
2
2
|
export function buildHeaderView({ title = 'Zerg Terminal Client', version = '0.1.0', dateLabel, showHelp = true, debug = false }) {
|
|
3
|
+
// Single-row header to minimize vertical space
|
|
3
4
|
return box([
|
|
4
5
|
box([
|
|
5
6
|
text(title, { bold: true }),
|
|
7
|
+
text(' ', {}),
|
|
6
8
|
text('ZTC', { color: 'gray', dimColor: true })
|
|
7
|
-
], {
|
|
8
|
-
flexDirection: 'column'
|
|
9
|
-
}),
|
|
9
|
+
], { flexDirection: 'row' }),
|
|
10
10
|
showHelp
|
|
11
11
|
? box([
|
|
12
12
|
dateLabel ? text(dateLabel, { color: 'gray', dimColor: true }) : text('', {}),
|
|
@@ -19,10 +19,13 @@ export function buildHeaderView({ title = 'Zerg Terminal Client', version = '0.1
|
|
|
19
19
|
], { flexDirection: 'row' })
|
|
20
20
|
: box([], { flexDirection: 'row' })
|
|
21
21
|
], {
|
|
22
|
-
flexDirection: '
|
|
22
|
+
flexDirection: 'row',
|
|
23
23
|
justifyContent: 'space-between',
|
|
24
24
|
paddingX: 1,
|
|
25
|
-
|
|
25
|
+
height: 1,
|
|
26
|
+
flexShrink: 0,
|
|
27
|
+
borderStyle: debug ? 'single' : undefined,
|
|
28
|
+
borderColor: debug ? 'cyan' : undefined
|
|
26
29
|
});
|
|
27
30
|
}
|
|
28
31
|
//# sourceMappingURL=header.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../../src/ui/views/header.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAc,MAAM,kBAAkB,CAAC;AAUzD,MAAM,UAAU,eAAe,CAAC,EAC9B,KAAK,GAAG,sBAAsB,EAC9B,OAAO,GAAG,OAAO,EACjB,SAAS,EACT,QAAQ,GAAG,IAAI,EACf,KAAK,GAAG,KAAK,EACD;IACZ,OAAO,GAAG,CAAC;QACT,GAAG,CAAC;YACF,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAC3B,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;SAC/C,EAAE
|
|
1
|
+
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../../src/ui/views/header.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAc,MAAM,kBAAkB,CAAC;AAUzD,MAAM,UAAU,eAAe,CAAC,EAC9B,KAAK,GAAG,sBAAsB,EAC9B,OAAO,GAAG,OAAO,EACjB,SAAS,EACT,QAAQ,GAAG,IAAI,EACf,KAAK,GAAG,KAAK,EACD;IACZ,+CAA+C;IAC/C,OAAO,GAAG,CAAC;QACT,GAAG,CAAC;YACF,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAC3B,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;SAC/C,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QAC5B,QAAQ;YACN,CAAC,CAAC,GAAG,CAAC;gBACF,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;gBAC7E,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;gBACzE,IAAI,CAAC,IAAI,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACtD,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBAC9C,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACtD,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBAC9C,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;aACjD,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;YAC9B,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;KACtC,EAAE;QACD,aAAa,EAAE,KAAK;QACpB,cAAc,EAAE,eAAe;QAC/B,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,CAAC;QACT,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QACzC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;KACxC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -10,9 +10,10 @@ interface StatusBarProps {
|
|
|
10
10
|
provider?: string;
|
|
11
11
|
model?: string;
|
|
12
12
|
emulationId?: string;
|
|
13
|
+
inputMode?: 'queue' | 'interrupt';
|
|
13
14
|
toast?: string | null;
|
|
14
15
|
debug?: boolean;
|
|
15
16
|
}
|
|
16
|
-
export declare function buildStatusBarView({ state, sessionId, version, connectionStatus, contextLength, contextEstimated, provider, model, emulationId, toast, debug }: StatusBarProps): LayoutNode;
|
|
17
|
+
export declare function buildStatusBarView({ state, sessionId, version, connectionStatus, contextLength, contextEstimated, provider, model, emulationId, inputMode, toast, debug }: StatusBarProps): LayoutNode;
|
|
17
18
|
export {};
|
|
18
19
|
//# sourceMappingURL=status_bar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status_bar.d.ts","sourceRoot":"","sources":["../../../src/ui/views/status_bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAa,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEzD,UAAU,cAAc;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,WAAW,GAAG,cAAc,GAAG,YAAY,CAAC;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAaD,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,SAAS,EACT,OAAiB,EACjB,gBAA8B,EAC9B,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAa,EACd,EAAE,cAAc,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"status_bar.d.ts","sourceRoot":"","sources":["../../../src/ui/views/status_bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAa,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEzD,UAAU,cAAc;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,WAAW,GAAG,cAAc,GAAG,YAAY,CAAC;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAaD,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,SAAS,EACT,OAAiB,EACjB,gBAA8B,EAC9B,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,KAAK,EACL,WAAW,EACX,SAAS,EACT,KAAK,EACL,KAAa,EACd,EAAE,cAAc,GAAG,UAAU,CAmE7B"}
|
|
@@ -9,7 +9,7 @@ function getStatusConfig(state) {
|
|
|
9
9
|
};
|
|
10
10
|
return configs[state.status];
|
|
11
11
|
}
|
|
12
|
-
export function buildStatusBarView({ state, sessionId, version = '0.1.0', connectionStatus = 'connected', contextLength, contextEstimated, provider, model, emulationId, toast, debug = false }) {
|
|
12
|
+
export function buildStatusBarView({ state, sessionId, version = '0.1.0', connectionStatus = 'connected', contextLength, contextEstimated, provider, model, emulationId, inputMode, toast, debug = false }) {
|
|
13
13
|
const config = getStatusConfig(state);
|
|
14
14
|
const isActive = state.status !== 'idle' && state.status !== 'error';
|
|
15
15
|
const connectionColors = {
|
|
@@ -45,6 +45,8 @@ export function buildStatusBarView({ state, sessionId, version = '0.1.0', connec
|
|
|
45
45
|
(provider || model) ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
46
46
|
emulationId ? text(`emu:${emulationId}`, { color: 'gray', dimColor: true }) : text('', {}),
|
|
47
47
|
emulationId ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
48
|
+
inputMode ? text(`mode:${inputMode}`, { color: 'gray', dimColor: true }) : text('', {}),
|
|
49
|
+
inputMode ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
48
50
|
state.tokensUsed !== undefined && state.tokensUsed > 0
|
|
49
51
|
? text(`${state.tokensUsed.toLocaleString()} tok`, { color: 'gray', dimColor: true })
|
|
50
52
|
: text('', {}),
|
|
@@ -65,6 +67,8 @@ export function buildStatusBarView({ state, sessionId, version = '0.1.0', connec
|
|
|
65
67
|
flexDirection: 'row',
|
|
66
68
|
justifyContent: 'space-between',
|
|
67
69
|
paddingX: 1,
|
|
70
|
+
height: 1,
|
|
71
|
+
flexShrink: 0,
|
|
68
72
|
borderStyle: debug ? 'single' : undefined,
|
|
69
73
|
borderColor: debug ? 'gray' : undefined
|
|
70
74
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status_bar.js","sourceRoot":"","sources":["../../../src/ui/views/status_bar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAc,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"status_bar.js","sourceRoot":"","sources":["../../../src/ui/views/status_bar.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { buildAppView } from './ui/views/app.js';
|
|
|
5
5
|
import { useMirror } from './web/mirror_hook.js';
|
|
6
6
|
import { Agent } from './agent/index.js';
|
|
7
7
|
import { commands, type CommandContext } from './agent/commands/index.js';
|
|
8
|
-
import { Message, AgentState } from './types.js';
|
|
8
|
+
import { Message, AgentState, InputMode } from './types.js';
|
|
9
9
|
import { InputState } from './ui/core/input_state.js';
|
|
10
10
|
import { configStore } from './config.js';
|
|
11
11
|
import { createInputBus } from './ui/core/input.js';
|
|
@@ -68,11 +68,16 @@ export const App: React.FC = () => {
|
|
|
68
68
|
|
|
69
69
|
// State
|
|
70
70
|
const [messages, setMessages] = useState<Message[]>([getWelcomeMessage()]);
|
|
71
|
+
const messagesRef = useRef<Message[]>(messages);
|
|
71
72
|
const [agentState, setAgentState] = useState<AgentState>({ status: 'idle' });
|
|
72
73
|
const [sessionId] = useState(generateId());
|
|
73
74
|
const [agent, setAgent] = useState<Agent | null>(createAgent);
|
|
74
75
|
const [expandToolOutputs, setExpandToolOutputs] = useState(false);
|
|
75
76
|
const [skills, setSkills] = useState<Skill[]>([]);
|
|
77
|
+
const [inputMode, setInputMode] = useState<InputMode>('queue');
|
|
78
|
+
const queueRef = useRef<string[]>([]);
|
|
79
|
+
const activeRunIdRef = useRef<string | null>(null);
|
|
80
|
+
const runCounterRef = useRef(0);
|
|
76
81
|
|
|
77
82
|
React.useEffect(() => {
|
|
78
83
|
let active = true;
|
|
@@ -117,6 +122,9 @@ export const App: React.FC = () => {
|
|
|
117
122
|
renderCount.current += 1;
|
|
118
123
|
debugLog(`App render #${renderCount.current} (messages=${messages.length}, state=${agentState.status})`);
|
|
119
124
|
});
|
|
125
|
+
React.useEffect(() => {
|
|
126
|
+
messagesRef.current = messages;
|
|
127
|
+
}, [messages]);
|
|
120
128
|
const [debug, setDebug] = useState(false);
|
|
121
129
|
|
|
122
130
|
const inputHeight = 5; // 1 input line + 4 suggestion lines
|
|
@@ -193,15 +201,22 @@ export const App: React.FC = () => {
|
|
|
193
201
|
return lower.includes('overloaded') || lower.includes('529') || lower.includes('429') || lower.includes('rate limit');
|
|
194
202
|
}, []);
|
|
195
203
|
|
|
196
|
-
const runWithRetry = useCallback(async (
|
|
204
|
+
const runWithRetry = useCallback(async (
|
|
205
|
+
requestMessages: Message[],
|
|
206
|
+
runAgent: Agent,
|
|
207
|
+
isManual = false,
|
|
208
|
+
isActive?: () => boolean
|
|
209
|
+
) => {
|
|
197
210
|
const maxRetries = 3;
|
|
198
211
|
let attempt = 0;
|
|
199
212
|
setRetryAvailable(false);
|
|
200
213
|
while (attempt < maxRetries) {
|
|
201
214
|
attempt += 1;
|
|
215
|
+
if (isActive && !isActive()) return;
|
|
202
216
|
try {
|
|
203
217
|
streamedResponse.current = false;
|
|
204
218
|
const result = await runAgent.run(requestMessages);
|
|
219
|
+
if (isActive && !isActive()) return;
|
|
205
220
|
if (!streamedResponse.current) {
|
|
206
221
|
addMessage({
|
|
207
222
|
role: 'assistant',
|
|
@@ -220,6 +235,7 @@ export const App: React.FC = () => {
|
|
|
220
235
|
return;
|
|
221
236
|
} catch (err) {
|
|
222
237
|
const errorMsg = (err as Error).message || 'Agent error';
|
|
238
|
+
if (isActive && !isActive()) return;
|
|
223
239
|
if (!isRetryableError(errorMsg) || attempt >= maxRetries) {
|
|
224
240
|
throw err;
|
|
225
241
|
}
|
|
@@ -242,9 +258,13 @@ export const App: React.FC = () => {
|
|
|
242
258
|
addMessage({ role: 'system', content: 'No previous request to retry.' });
|
|
243
259
|
return;
|
|
244
260
|
}
|
|
261
|
+
const runId = `${Date.now()}_${runCounterRef.current++}`;
|
|
262
|
+
activeRunIdRef.current = runId;
|
|
263
|
+
const isActive = () => activeRunIdRef.current === runId;
|
|
245
264
|
setAgentState({ status: 'thinking', startedAt: new Date() });
|
|
246
|
-
void runWithRetry(last.messages, last.agent, true).catch((err) => {
|
|
265
|
+
void runWithRetry(last.messages, last.agent, true, isActive).catch((err) => {
|
|
247
266
|
const message = (err as Error).message || 'Agent error';
|
|
267
|
+
if (!isActive()) return;
|
|
248
268
|
addMessage({ role: 'system', content: `Error: ${message}` });
|
|
249
269
|
if (isRetryableError(message)) {
|
|
250
270
|
addMessage({ role: 'system', content: 'Retries exhausted. Use /retry to try again.' });
|
|
@@ -282,8 +302,10 @@ export const App: React.FC = () => {
|
|
|
282
302
|
},
|
|
283
303
|
skills: {
|
|
284
304
|
list: async () => getSkillRegistry()
|
|
285
|
-
}
|
|
286
|
-
|
|
305
|
+
},
|
|
306
|
+
getInputMode: () => inputMode,
|
|
307
|
+
setInputMode: (mode) => setInputMode(mode)
|
|
308
|
+
}), [addMessage, clearMessages, getMessages, reloadAgent, exit, shellController, retryLast, inputMode]);
|
|
287
309
|
|
|
288
310
|
// Handle commands
|
|
289
311
|
const handleCommand = useCallback((cmd: string, args: string[]) => {
|
|
@@ -322,6 +344,22 @@ export const App: React.FC = () => {
|
|
|
322
344
|
return;
|
|
323
345
|
}
|
|
324
346
|
|
|
347
|
+
const busy = agentState.status !== 'idle' && agentState.status !== 'error';
|
|
348
|
+
if (busy) {
|
|
349
|
+
if (inputMode === 'queue') {
|
|
350
|
+
queueRef.current.push(text);
|
|
351
|
+
addMessage({
|
|
352
|
+
role: 'system',
|
|
353
|
+
content: `Queued (${queueRef.current.length})`
|
|
354
|
+
});
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
addMessage({
|
|
358
|
+
role: 'system',
|
|
359
|
+
content: 'Interrupting current response...'
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
325
363
|
let currentAgent = agent;
|
|
326
364
|
|
|
327
365
|
if (skills.length > 0) {
|
|
@@ -373,12 +411,18 @@ export const App: React.FC = () => {
|
|
|
373
411
|
timestamp: new Date()
|
|
374
412
|
};
|
|
375
413
|
setMessages(prev => [...prev, userMsg]);
|
|
376
|
-
const requestMessages = [...
|
|
414
|
+
const requestMessages = [...messagesRef.current, userMsg];
|
|
377
415
|
lastRequestRef.current = { messages: requestMessages, agent: currentAgent };
|
|
378
416
|
|
|
417
|
+
const runId = `${Date.now()}_${runCounterRef.current++}`;
|
|
418
|
+
activeRunIdRef.current = runId;
|
|
419
|
+
const isActive = () => activeRunIdRef.current === runId;
|
|
379
420
|
setAgentState({ status: 'thinking', startedAt: new Date() });
|
|
421
|
+
streamingMessageId.current = null;
|
|
422
|
+
streamedResponse.current = false;
|
|
380
423
|
|
|
381
424
|
const cleanup = currentAgent.on((event) => {
|
|
425
|
+
if (!isActive()) return;
|
|
382
426
|
switch (event.type) {
|
|
383
427
|
case 'thinking_start':
|
|
384
428
|
setAgentState(s => ({ ...s, status: 'thinking' }));
|
|
@@ -475,9 +519,10 @@ export const App: React.FC = () => {
|
|
|
475
519
|
});
|
|
476
520
|
|
|
477
521
|
try {
|
|
478
|
-
await runWithRetry(requestMessages, currentAgent);
|
|
522
|
+
await runWithRetry(requestMessages, currentAgent, false, isActive);
|
|
479
523
|
} catch (err) {
|
|
480
524
|
const errorMsg = (err as Error).message;
|
|
525
|
+
if (!isActive()) return;
|
|
481
526
|
|
|
482
527
|
const isAuthError = errorMsg.includes('401') || errorMsg.includes('authentication');
|
|
483
528
|
if (isAuthError) {
|
|
@@ -504,11 +549,22 @@ export const App: React.FC = () => {
|
|
|
504
549
|
setAgentState({ status: 'idle' });
|
|
505
550
|
}, 3000);
|
|
506
551
|
} finally {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
552
|
+
if (isActive()) {
|
|
553
|
+
streamedResponse.current = false;
|
|
554
|
+
streamingMessageId.current = null;
|
|
555
|
+
activeRunIdRef.current = null;
|
|
556
|
+
cleanup();
|
|
557
|
+
if (inputMode === 'queue' && queueRef.current.length > 0) {
|
|
558
|
+
const next = queueRef.current.shift();
|
|
559
|
+
if (next) {
|
|
560
|
+
void handleSubmit(next);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
cleanup();
|
|
565
|
+
}
|
|
510
566
|
}
|
|
511
|
-
}, [
|
|
567
|
+
}, [agent, agentState.status, addMessage, handleCommand, inputMode, isRetryableError, runWithRetry]);
|
|
512
568
|
|
|
513
569
|
const layoutTree = useMemo(() => {
|
|
514
570
|
if (!mirrorEnabled) return null;
|
|
@@ -526,11 +582,12 @@ export const App: React.FC = () => {
|
|
|
526
582
|
provider,
|
|
527
583
|
model,
|
|
528
584
|
emulationId,
|
|
585
|
+
inputMode,
|
|
529
586
|
toast,
|
|
530
587
|
debug,
|
|
531
588
|
expandToolOutputs
|
|
532
589
|
});
|
|
533
|
-
}, [mirrorEnabled, messages, agentState, inputSnapshot, sessionId, rows, contextLength, contextEstimated, provider, model, emulationId, toast, debug, expandToolOutputs]);
|
|
590
|
+
}, [mirrorEnabled, messages, agentState, inputSnapshot, sessionId, rows, contextLength, contextEstimated, provider, model, emulationId, inputMode, toast, debug, expandToolOutputs]);
|
|
534
591
|
|
|
535
592
|
const showToast = useCallback((message: string) => {
|
|
536
593
|
setToast(message);
|
|
@@ -563,7 +620,7 @@ export const App: React.FC = () => {
|
|
|
563
620
|
onToast={showToast}
|
|
564
621
|
cols={columns}
|
|
565
622
|
inputBus={inputBus}
|
|
566
|
-
disabled={
|
|
623
|
+
disabled={false}
|
|
567
624
|
debug={debug}
|
|
568
625
|
placeholder={
|
|
569
626
|
!configStore.hasApiKey() ? 'Set API key with /config key <key>' :
|
|
@@ -584,6 +641,7 @@ export const App: React.FC = () => {
|
|
|
584
641
|
provider={provider}
|
|
585
642
|
model={model}
|
|
586
643
|
emulationId={emulationId}
|
|
644
|
+
inputMode={inputMode}
|
|
587
645
|
toast={toast}
|
|
588
646
|
debug={debug}
|
|
589
647
|
/>
|
package/src/agent/agent.ts
CHANGED
|
@@ -77,7 +77,9 @@ You have access to tools for:
|
|
|
77
77
|
- Running shell commands
|
|
78
78
|
- Querying the Zerg system
|
|
79
79
|
|
|
80
|
-
Be concise and helpful. When using tools, explain what you're doing briefly. If a task requires multiple steps, proceed through them systematically
|
|
80
|
+
Be concise and helpful. When using tools, explain what you're doing briefly. If a task requires multiple steps, proceed through them systematically.
|
|
81
|
+
|
|
82
|
+
When a user intent maps to an available slash command, invoke the command directly (just the command) instead of explaining how to do it. Prefer executing commands over describing them.`;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
// Event handling
|
|
@@ -12,6 +12,8 @@ import { modelCommand } from './model.js';
|
|
|
12
12
|
import { permissionsCommand } from './permissions.js';
|
|
13
13
|
import { skillsCommand } from './skills.js';
|
|
14
14
|
import { retryCommand } from './retry.js';
|
|
15
|
+
import { inputModeCommand } from './input_mode.js';
|
|
16
|
+
import { keybindingsCommand } from './keybindings.js';
|
|
15
17
|
import { Command } from './types.js';
|
|
16
18
|
|
|
17
19
|
const commandList: Command[] = [];
|
|
@@ -32,6 +34,8 @@ commandList.push(
|
|
|
32
34
|
modelCommand,
|
|
33
35
|
permissionsCommand,
|
|
34
36
|
skillsCommand,
|
|
37
|
+
keybindingsCommand,
|
|
38
|
+
inputModeCommand,
|
|
35
39
|
retryCommand,
|
|
36
40
|
exitCommand
|
|
37
41
|
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const inputModeCommand: Command = {
|
|
4
|
+
name: 'inputmode',
|
|
5
|
+
description: 'Set input mode while an agent is running',
|
|
6
|
+
usage: '<queue|interrupt>',
|
|
7
|
+
handler: (args, ctx) => {
|
|
8
|
+
const mode = (args[0] || '').toLowerCase();
|
|
9
|
+
if (mode !== 'queue' && mode !== 'interrupt') {
|
|
10
|
+
ctx.addMessage({
|
|
11
|
+
role: 'system',
|
|
12
|
+
content: `Current input mode: ${ctx.getInputMode()}\n\nUsage: /inputmode <queue|interrupt>`
|
|
13
|
+
});
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
ctx.setInputMode(mode);
|
|
17
|
+
ctx.addMessage({
|
|
18
|
+
role: 'system',
|
|
19
|
+
content: `✓ Input mode set: ${mode}`
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command } from './types.js';
|
|
2
|
+
|
|
3
|
+
const lines = [
|
|
4
|
+
'Keybindings (readline-style):',
|
|
5
|
+
'',
|
|
6
|
+
'Movement:',
|
|
7
|
+
' Ctrl+A start of line',
|
|
8
|
+
' Ctrl+E end of line',
|
|
9
|
+
' Ctrl+B move left',
|
|
10
|
+
' Ctrl+F move right',
|
|
11
|
+
' Alt+B word left',
|
|
12
|
+
' Alt+F word right',
|
|
13
|
+
'',
|
|
14
|
+
'Editing:',
|
|
15
|
+
' Ctrl+U kill to start',
|
|
16
|
+
' Ctrl+K kill to end',
|
|
17
|
+
' Ctrl+W kill previous word',
|
|
18
|
+
' Alt+D kill next word',
|
|
19
|
+
' Ctrl+D delete forward',
|
|
20
|
+
' Ctrl+Y yank',
|
|
21
|
+
' Alt+Y yank-pop',
|
|
22
|
+
' Ctrl+T transpose chars',
|
|
23
|
+
' Alt+T transpose words',
|
|
24
|
+
'',
|
|
25
|
+
'History:',
|
|
26
|
+
' Ctrl+P previous',
|
|
27
|
+
' Ctrl+N next',
|
|
28
|
+
' Up/Down arrows also work',
|
|
29
|
+
'',
|
|
30
|
+
'Input mode:',
|
|
31
|
+
' /inputmode queue|interrupt'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const keybindingsCommand: Command = {
|
|
35
|
+
name: 'keybindings',
|
|
36
|
+
description: 'Show keybindings',
|
|
37
|
+
handler: (_args, ctx) => {
|
|
38
|
+
ctx.addMessage({ role: 'system', content: lines.join('\n') });
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -68,6 +68,8 @@ export interface CommandContext {
|
|
|
68
68
|
clipboard: ClipboardController;
|
|
69
69
|
models: ModelsController;
|
|
70
70
|
skills: SkillsController;
|
|
71
|
+
getInputMode: () => 'queue' | 'interrupt';
|
|
72
|
+
setInputMode: (mode: 'queue' | 'interrupt') => void;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export interface Command {
|
package/src/cli.tsx
CHANGED
|
@@ -75,12 +75,54 @@ if (cwdIndex >= 0) {
|
|
|
75
75
|
async function main(): Promise<void> {
|
|
76
76
|
await configStore.load(true);
|
|
77
77
|
|
|
78
|
+
const isTty = Boolean(process.stdout.isTTY);
|
|
79
|
+
const useAltScreen = isTty && process.env.ZTC_ALT_SCREEN !== '0' && process.env.ZTC_NO_ALT_SCREEN !== '1';
|
|
80
|
+
|
|
81
|
+
if (useAltScreen) {
|
|
82
|
+
process.stdout.write('\x1b[?1049h');
|
|
83
|
+
process.stdout.write('\x1b[?25l');
|
|
84
|
+
process.stdout.write('\x1b[2J');
|
|
85
|
+
process.stdout.write('\x1b[H');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Enable bracketed paste mode so we can detect paste boundaries
|
|
89
|
+
process.stdout.write('\x1b[?2004h');
|
|
90
|
+
|
|
91
|
+
// Enable Kitty keyboard protocol (progressive enhancement)
|
|
92
|
+
// This allows detection of Cmd+V and other modifier combinations
|
|
93
|
+
// Flags: 1 = disambiguate escape codes, 2 = report event types, 4 = report alternate keys, 8 = report all keys
|
|
94
|
+
// Using flags=1 for basic disambiguation which reports Cmd+V as CSI sequence
|
|
95
|
+
process.stdout.write('\x1b[>1u');
|
|
96
|
+
|
|
97
|
+
// Wrap stdout.write to use synchronized output (reduces flicker)
|
|
98
|
+
// DECSM/DECRM 2026 tells terminal to batch updates
|
|
99
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
100
|
+
let syncPending = false;
|
|
101
|
+
process.stdout.write = function(chunk: any, encoding?: any, callback?: any): boolean {
|
|
102
|
+
if (!syncPending) {
|
|
103
|
+
syncPending = true;
|
|
104
|
+
originalWrite('\x1b[?2026h'); // Begin synchronized update
|
|
105
|
+
setImmediate(() => {
|
|
106
|
+
originalWrite('\x1b[?2026l'); // End synchronized update
|
|
107
|
+
syncPending = false;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return originalWrite(chunk, encoding, callback);
|
|
111
|
+
} as typeof process.stdout.write;
|
|
112
|
+
|
|
78
113
|
// Render the app
|
|
79
|
-
// Note: FullScreen component handles alternate screen buffer
|
|
80
114
|
const { waitUntilExit } = render(<App />);
|
|
81
115
|
|
|
82
116
|
// Handle clean exit
|
|
83
117
|
waitUntilExit().then(() => {
|
|
118
|
+
// Disable Kitty keyboard protocol
|
|
119
|
+
process.stdout.write('\x1b[<u');
|
|
120
|
+
// Disable bracketed paste mode
|
|
121
|
+
process.stdout.write('\x1b[?2004l');
|
|
122
|
+
if (useAltScreen) {
|
|
123
|
+
process.stdout.write('\x1b[?25h');
|
|
124
|
+
process.stdout.write('\x1b[?1049l');
|
|
125
|
+
}
|
|
84
126
|
console.log('Goodbye from ZTC! 👋');
|
|
85
127
|
process.exit(0);
|
|
86
128
|
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { PropsWithChildren, useState, useEffect } from 'react';
|
|
2
2
|
import { Box } from 'ink';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* FullScreen component that uses the alternate screen buffer.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* This prevents flickering by:
|
|
8
8
|
* 1. Switching to the alternate screen buffer (like vim, htop, less do)
|
|
9
9
|
* 2. Tracking terminal resize events
|
|
10
10
|
* 3. Restoring the original terminal state on exit
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* ANSI escape codes:
|
|
13
13
|
* - \x1b[?1049h - Enter alternate screen buffer
|
|
14
14
|
* - \x1b[?1049l - Exit alternate screen buffer
|
|
@@ -20,41 +20,22 @@ interface FullScreenProps extends PropsWithChildren {
|
|
|
20
20
|
debug?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
function getTerminalSize() {
|
|
24
|
+
return {
|
|
25
|
+
columns: process.stdout.columns || 80,
|
|
26
|
+
rows: process.stdout.rows || 24,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
export const FullScreen: React.FC<FullScreenProps> = ({ children, debug = false }) => {
|
|
29
|
-
const
|
|
30
|
-
const useAltScreen = isTty && process.env.ZTC_ALT_SCREEN === '1';
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (!useAltScreen) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Enter alternate screen buffer
|
|
38
|
-
process.stdout.write('\x1b[?1049h');
|
|
39
|
-
// Hide cursor (we'll draw our own)
|
|
40
|
-
process.stdout.write('\x1b[?25l');
|
|
41
|
-
// Clear the alternate screen
|
|
42
|
-
process.stdout.write('\x1b[2J');
|
|
43
|
-
// Move cursor to top-left
|
|
44
|
-
process.stdout.write('\x1b[H');
|
|
45
|
-
|
|
46
|
-
// Cleanup: restore terminal state
|
|
47
|
-
return () => {
|
|
48
|
-
// Show cursor
|
|
49
|
-
process.stdout.write('\x1b[?25h');
|
|
50
|
-
// Exit alternate screen buffer (restores original content)
|
|
51
|
-
process.stdout.write('\x1b[?1049l');
|
|
52
|
-
};
|
|
53
|
-
}, [useAltScreen]);
|
|
31
|
+
const { rows, columns } = useScreenSize();
|
|
54
32
|
|
|
55
33
|
return (
|
|
56
|
-
<Box
|
|
34
|
+
<Box
|
|
57
35
|
flexDirection="column"
|
|
36
|
+
width={columns}
|
|
37
|
+
height={rows}
|
|
38
|
+
overflow="hidden"
|
|
58
39
|
borderStyle={debug ? 'single' : undefined}
|
|
59
40
|
borderColor={debug ? 'red' : undefined}
|
|
60
41
|
>
|
|
@@ -67,7 +48,31 @@ export const FullScreen: React.FC<FullScreenProps> = ({ children, debug = false
|
|
|
67
48
|
* Hook to get current screen size with resize tracking
|
|
68
49
|
*/
|
|
69
50
|
export function useScreenSize() {
|
|
70
|
-
|
|
51
|
+
const [size, setSize] = useState(getTerminalSize);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const updateSize = () => {
|
|
55
|
+
const current = getTerminalSize();
|
|
56
|
+
setSize(prev => {
|
|
57
|
+
if (prev.rows !== current.rows || prev.columns !== current.columns) {
|
|
58
|
+
return current;
|
|
59
|
+
}
|
|
60
|
+
return prev;
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
process.stdout.on('resize', updateSize);
|
|
65
|
+
|
|
66
|
+
// Also check on an interval as a fallback for some terminals
|
|
67
|
+
const interval = setInterval(updateSize, 1000);
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
process.stdout.off('resize', updateSize);
|
|
71
|
+
clearInterval(interval);
|
|
72
|
+
};
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
return size;
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
export default FullScreen;
|