u-foo 2.5.8 → 2.5.9
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 +2 -0
- package/README.zh-CN.md +3 -1
- package/bin/ufoo.js +6 -1
- package/bin/ukimi.js +70 -0
- package/package.json +2 -1
- package/src/agents/activity/activityDetector.js +7 -0
- package/src/agents/launch/launcher.js +6 -5
- package/src/agents/launch/readyDetector.js +21 -0
- package/src/agents/prompts/defaultBootstrap.js +20 -3
- package/src/agents/providers/credentials/index.js +7 -0
- package/src/agents/providers/credentials/kimi.js +342 -0
- package/src/agents/providers/directAuthStatus.js +94 -0
- package/src/app/chat/commandExecutor.js +10 -5
- package/src/app/chat/commands.js +1 -0
- package/src/app/chat/dashboardView.js +1 -0
- package/src/app/chat/multiWindow/index.js +88 -46
- package/src/app/chat/multiWindow/renderer.js +57 -8
- package/src/app/cli/run.js +5 -3
- package/src/code/nativeRunner.js +57 -6
- package/src/config.js +8 -0
- package/src/orchestration/groups/validateTemplate.js +1 -1
- package/src/runtime/daemon/groupOrchestrator.js +6 -0
- package/src/runtime/daemon/index.js +12 -3
- package/src/runtime/daemon/ops.js +7 -1
- package/src/runtime/daemon/providerSessions.js +41 -2
- package/src/tools/schemaFixtures.js +1 -1
- package/src/ui/format/index.js +14 -2
- package/src/ui/ink/ChatApp.js +321 -64
- package/src/ui/ink/chatReducer.js +32 -8
- package/src/ui/runInk.js +18 -9
|
@@ -12,6 +12,10 @@ const {
|
|
|
12
12
|
resolveClaudeOauthPaths,
|
|
13
13
|
resolveClaudeUpstreamCredentials,
|
|
14
14
|
} = require("./credentials/claude");
|
|
15
|
+
const {
|
|
16
|
+
resolveKimiCredentialPaths,
|
|
17
|
+
resolveKimiUpstreamCredentials,
|
|
18
|
+
} = require("./credentials/kimi");
|
|
15
19
|
|
|
16
20
|
function normalizeRefreshWindowMs(value) {
|
|
17
21
|
const num = Number(value);
|
|
@@ -27,6 +31,7 @@ function normalizeDirectAuthProvider(value = "") {
|
|
|
27
31
|
const text = String(value || "").trim().toLowerCase();
|
|
28
32
|
if (text === "claude" || text === "claude-cli" || text === "claude-code" || text === "anthropic") return "claude";
|
|
29
33
|
if (text === "agy" || text === "agy-cli" || text === "antigravity") return "agy";
|
|
34
|
+
if (text === "kimi" || text === "kimi-code" || text === "moonshot") return "kimi";
|
|
30
35
|
return "codex";
|
|
31
36
|
}
|
|
32
37
|
|
|
@@ -272,6 +277,41 @@ async function inspectAgyDirectAuth({
|
|
|
272
277
|
};
|
|
273
278
|
}
|
|
274
279
|
|
|
280
|
+
async function inspectKimiDirectAuth({
|
|
281
|
+
env = process.env,
|
|
282
|
+
fetchImpl = global.fetch,
|
|
283
|
+
autoRefresh = false,
|
|
284
|
+
} = {}) {
|
|
285
|
+
const paths = resolveKimiCredentialPaths({ env });
|
|
286
|
+
try {
|
|
287
|
+
const credential = await resolveKimiUpstreamCredentials({
|
|
288
|
+
env,
|
|
289
|
+
fetchImpl,
|
|
290
|
+
autoRefresh,
|
|
291
|
+
});
|
|
292
|
+
return {
|
|
293
|
+
ok: true,
|
|
294
|
+
provider: "kimi",
|
|
295
|
+
transport: "openai-chat",
|
|
296
|
+
credentialKind: String(credential.credentialKind || ""),
|
|
297
|
+
source: String(credential.source || ""),
|
|
298
|
+
state: String(credential.state || ""),
|
|
299
|
+
refreshable: credential.refreshable === true,
|
|
300
|
+
expiresAt: String(credential.expiresAt || ""),
|
|
301
|
+
credentialPath: String(credential.credentialPath || paths.credentialPath || ""),
|
|
302
|
+
};
|
|
303
|
+
} catch (err) {
|
|
304
|
+
return {
|
|
305
|
+
ok: false,
|
|
306
|
+
provider: "kimi",
|
|
307
|
+
error: err && err.message ? err.message : "Kimi direct API credentials are unavailable",
|
|
308
|
+
errorCode: normalizeErrorCode(err, "KIMI_AUTH_STATUS_FAILED"),
|
|
309
|
+
credentialPath: paths.credentialPath || "",
|
|
310
|
+
hint: "Run `kimi` once to sign in or set UFOO_UCODE_API_KEY; ufoo-agent will not fall back to the CLI.",
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
275
315
|
async function inspectDirectAuthStatus(options = {}) {
|
|
276
316
|
const { projectRoot, loadConfigImpl = loadConfig, provider = "" } = options;
|
|
277
317
|
const config = loadConfigImpl(projectRoot) || {};
|
|
@@ -286,6 +326,9 @@ async function inspectDirectAuthStatus(options = {}) {
|
|
|
286
326
|
if (selected === "agy") {
|
|
287
327
|
return inspectAgyDirectAuth(nextOptions);
|
|
288
328
|
}
|
|
329
|
+
if (selected === "kimi") {
|
|
330
|
+
return inspectKimiDirectAuth(nextOptions);
|
|
331
|
+
}
|
|
289
332
|
return inspectCodexDirectAuth(nextOptions);
|
|
290
333
|
}
|
|
291
334
|
|
|
@@ -423,6 +466,52 @@ function formatAgyDirectAuthStatus(status = {}, options = {}) {
|
|
|
423
466
|
return lines;
|
|
424
467
|
}
|
|
425
468
|
|
|
469
|
+
function formatKimiDirectAuthStatus(status = {}, options = {}) {
|
|
470
|
+
if (options.compact === true) {
|
|
471
|
+
if (status.ok) {
|
|
472
|
+
const credential = status.credentialKind || "credential";
|
|
473
|
+
const transport = status.transport || "openai-chat";
|
|
474
|
+
const state = status.state || "unknown";
|
|
475
|
+
const details = [
|
|
476
|
+
status.source || "",
|
|
477
|
+
status.expiresAt ? `expires ${formatCompactExpires(status.expiresAt)}` : "",
|
|
478
|
+
status.refreshable ? "refreshable" : "",
|
|
479
|
+
].filter(Boolean);
|
|
480
|
+
const lines = [
|
|
481
|
+
`Kimi API: OK · ${credential}/${transport} · ${state}`,
|
|
482
|
+
];
|
|
483
|
+
if (details.length > 0) lines.push(` ${details.join(" · ")}`);
|
|
484
|
+
return lines;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const hint = String(status.hint || "Run `kimi` once to sign in or set UFOO_UCODE_API_KEY.").replace(/;.*$/, ".");
|
|
488
|
+
return [
|
|
489
|
+
`Kimi API: FAIL · ${status.errorCode || "KIMI_AUTH_STATUS_FAILED"}`,
|
|
490
|
+
` ${status.error || "Kimi direct API credentials are unavailable"} · ${hint}`,
|
|
491
|
+
];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (status.ok) {
|
|
495
|
+
const state = status.state || "unknown";
|
|
496
|
+
const lines = [
|
|
497
|
+
`Kimi direct API: OK (${status.transport || "openai-chat"}, ${status.credentialKind || "credential"}, ${state})`,
|
|
498
|
+
` - source: ${status.source || "(unknown)"}`,
|
|
499
|
+
];
|
|
500
|
+
if (status.expiresAt) lines.push(` - expires: ${status.expiresAt}`);
|
|
501
|
+
if (status.credentialPath) lines.push(` - path: ${status.credentialPath}`);
|
|
502
|
+
if (status.refreshable) lines.push(" - refreshable: yes");
|
|
503
|
+
return lines;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const lines = [
|
|
507
|
+
`Kimi direct API: FAIL (${status.errorCode || "KIMI_AUTH_STATUS_FAILED"})`,
|
|
508
|
+
` - ${status.error || "Kimi direct API credentials are unavailable"}`,
|
|
509
|
+
];
|
|
510
|
+
if (status.credentialPath) lines.push(` - expected path: ${status.credentialPath}`);
|
|
511
|
+
if (status.hint) lines.push(` - ${status.hint}`);
|
|
512
|
+
return lines;
|
|
513
|
+
}
|
|
514
|
+
|
|
426
515
|
function formatDirectAuthStatus(status = {}, options = {}) {
|
|
427
516
|
if (status.provider === "claude") {
|
|
428
517
|
return formatClaudeDirectAuthStatus(status, options);
|
|
@@ -430,6 +519,9 @@ function formatDirectAuthStatus(status = {}, options = {}) {
|
|
|
430
519
|
if (status.provider === "agy") {
|
|
431
520
|
return formatAgyDirectAuthStatus(status, options);
|
|
432
521
|
}
|
|
522
|
+
if (status.provider === "kimi") {
|
|
523
|
+
return formatKimiDirectAuthStatus(status, options);
|
|
524
|
+
}
|
|
433
525
|
return formatCodexDirectAuthStatus(status, options);
|
|
434
526
|
}
|
|
435
527
|
|
|
@@ -438,10 +530,12 @@ module.exports = {
|
|
|
438
530
|
inspectCodexDirectAuth,
|
|
439
531
|
inspectClaudeDirectAuth,
|
|
440
532
|
inspectAgyDirectAuth,
|
|
533
|
+
inspectKimiDirectAuth,
|
|
441
534
|
formatDirectAuthStatus,
|
|
442
535
|
formatCodexDirectAuthStatus,
|
|
443
536
|
formatClaudeDirectAuthStatus,
|
|
444
537
|
formatAgyDirectAuthStatus,
|
|
538
|
+
formatKimiDirectAuthStatus,
|
|
445
539
|
normalizeDirectAuthProvider,
|
|
446
540
|
classifyAgyLogTail,
|
|
447
541
|
};
|
|
@@ -59,6 +59,9 @@ function normalizeSettingsProvider(value = "", fallback = "codex-cli") {
|
|
|
59
59
|
if (text === "agy" || text === "agy-cli" || text === "antigravity") {
|
|
60
60
|
return "agy-cli";
|
|
61
61
|
}
|
|
62
|
+
if (text === "kimi" || text === "kimi-cli" || text === "kimi-code" || text === "ukimi") {
|
|
63
|
+
return "kimi-cli";
|
|
64
|
+
}
|
|
62
65
|
if (text === "codex" || text === "codex-cli" || text === "codex-code" || text === "openai") {
|
|
63
66
|
return "codex-cli";
|
|
64
67
|
}
|
|
@@ -69,6 +72,7 @@ function agentProviderKey(value = "") {
|
|
|
69
72
|
const provider = normalizeSettingsProvider(value);
|
|
70
73
|
if (provider === "claude-cli") return "claude";
|
|
71
74
|
if (provider === "agy-cli") return "agy";
|
|
75
|
+
if (provider === "kimi-cli") return "kimi";
|
|
72
76
|
return "codex";
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -597,7 +601,7 @@ function createCommandExecutor(options = {}) {
|
|
|
597
601
|
if (args.length === 0) {
|
|
598
602
|
logMessage(
|
|
599
603
|
"error",
|
|
600
|
-
"{white-fg}✗{/white-fg} Usage: /launch <claude|codex|agy|ucode> [nickname=<name>] [profile=<id>] [count=<n>] [scope=inplace|window]"
|
|
604
|
+
"{white-fg}✗{/white-fg} Usage: /launch <claude|codex|agy|kimi|ucode> [nickname=<name>] [profile=<id>] [count=<n>] [scope=inplace|window]"
|
|
601
605
|
);
|
|
602
606
|
return;
|
|
603
607
|
}
|
|
@@ -606,8 +610,9 @@ function createCommandExecutor(options = {}) {
|
|
|
606
610
|
// Accept friendly aliases the same way `ufoo launch` does in cli.js.
|
|
607
611
|
let agentType = agentTypeInput;
|
|
608
612
|
if (agentTypeInput === "antigravity" || agentTypeInput === "uagy") agentType = "agy";
|
|
609
|
-
if (
|
|
610
|
-
|
|
613
|
+
if (agentTypeInput === "kimi-cli" || agentTypeInput === "kimi-code" || agentTypeInput === "ukimi") agentType = "kimi";
|
|
614
|
+
if (agentType !== "claude" && agentType !== "codex" && agentType !== "agy" && agentType !== "kimi" && agentType !== "ucode") {
|
|
615
|
+
logMessage("error", "{white-fg}✗{/white-fg} Unknown agent type. Use: claude, codex, agy, kimi, or ucode");
|
|
611
616
|
return;
|
|
612
617
|
}
|
|
613
618
|
const normalizedAgent = agentType === "ucode" ? "ufoo" : agentType;
|
|
@@ -1380,11 +1385,11 @@ function createCommandExecutor(options = {}) {
|
|
|
1380
1385
|
return;
|
|
1381
1386
|
}
|
|
1382
1387
|
|
|
1383
|
-
if (action === "codex" || action === "claude" || action === "agy") {
|
|
1388
|
+
if (action === "codex" || action === "claude" || action === "agy" || action === "kimi") {
|
|
1384
1389
|
const kv = parseKeyValueArgs(args.slice(1));
|
|
1385
1390
|
const provider = action === "claude"
|
|
1386
1391
|
? "claude-cli"
|
|
1387
|
-
: (action === "agy" ? "agy-cli" : "codex-cli");
|
|
1392
|
+
: (action === "agy" ? "agy-cli" : (action === "kimi" ? "kimi-cli" : "codex-cli"));
|
|
1388
1393
|
const model = String(kv.model || defaultAgentModelForProvider(provider)).trim();
|
|
1389
1394
|
saveConfig(projectRoot, {
|
|
1390
1395
|
agentProvider: provider,
|
package/src/app/chat/commands.js
CHANGED
|
@@ -228,6 +228,7 @@ function normalizeAgentLabel(value = "") {
|
|
|
228
228
|
if (raw === "claude" || raw === "uclaude") return "claude";
|
|
229
229
|
if (raw === "codex" || raw === "ucodex") return "codex";
|
|
230
230
|
if (raw === "agy" || raw === "antigravity" || raw === "uagy") return "agy";
|
|
231
|
+
if (raw === "kimi" || raw === "kimi-cli" || raw === "kimi-code" || raw === "ukimi") return "kimi";
|
|
231
232
|
if (raw === "ucode" || raw === "ufoo") return "ufoo";
|
|
232
233
|
return raw || "agent";
|
|
233
234
|
}
|
|
@@ -5,6 +5,7 @@ const DEFAULT_MODE_OPTIONS = ["auto", "host", "terminal", "tmux", "internal"];
|
|
|
5
5
|
function providerLabel(value) {
|
|
6
6
|
if (value === "claude-cli") return "claude";
|
|
7
7
|
if (value === "agy-cli" || value === "agy" || value === "antigravity") return "agy";
|
|
8
|
+
if (value === "kimi-cli" || value === "kimi" || value === "kimi-code") return "kimi";
|
|
8
9
|
if (value === "ucode" || value === "ufoo" || value === "ufoo-code") return "ucode";
|
|
9
10
|
return "codex";
|
|
10
11
|
}
|
|
@@ -2,6 +2,11 @@ const { calculatePaneLayout } = require("./paneLayout");
|
|
|
2
2
|
const { createPaneManager } = require("./paneManager");
|
|
3
3
|
const { createRenderer } = require("./renderer");
|
|
4
4
|
|
|
5
|
+
// Pane output bursts are coalesced into short batches; full chrome repaints
|
|
6
|
+
// are throttled with a trailing frame so the final state always renders.
|
|
7
|
+
const PANE_OUTPUT_BATCH_MS = 50;
|
|
8
|
+
const RENDER_ALL_MIN_INTERVAL_MS = 100;
|
|
9
|
+
|
|
5
10
|
function createMultiWindowController(options = {}) {
|
|
6
11
|
const {
|
|
7
12
|
processStdout = process.stdout,
|
|
@@ -30,6 +35,9 @@ function createMultiWindowController(options = {}) {
|
|
|
30
35
|
let active = false;
|
|
31
36
|
let renderThrottleTimer = null;
|
|
32
37
|
let dirtyPanes = new Set();
|
|
38
|
+
let renderAllTimer = null;
|
|
39
|
+
let renderAllTrailing = false;
|
|
40
|
+
let lastRenderAllAt = 0;
|
|
33
41
|
let lastCompletionPopup = null;
|
|
34
42
|
const renderer = createRenderer({ write: (d) => processStdout.write(d) });
|
|
35
43
|
const paneManager = createPaneManager({
|
|
@@ -37,18 +45,14 @@ function createMultiWindowController(options = {}) {
|
|
|
37
45
|
onInternalSubmit,
|
|
38
46
|
onPaneOutput: (agentId) => {
|
|
39
47
|
if (!active) return;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
dirtyPanes.clear();
|
|
49
|
-
for (const id of panes) renderSinglePane(id);
|
|
50
|
-
}, 200);
|
|
51
|
-
}
|
|
48
|
+
dirtyPanes.add(agentId);
|
|
49
|
+
if (!renderThrottleTimer) {
|
|
50
|
+
renderThrottleTimer = setTimeout(() => {
|
|
51
|
+
renderThrottleTimer = null;
|
|
52
|
+
const panes = [...dirtyPanes];
|
|
53
|
+
dirtyPanes.clear();
|
|
54
|
+
for (const id of panes) renderSinglePane(id);
|
|
55
|
+
}, PANE_OUTPUT_BATCH_MS);
|
|
52
56
|
}
|
|
53
57
|
},
|
|
54
58
|
});
|
|
@@ -62,7 +66,7 @@ function createMultiWindowController(options = {}) {
|
|
|
62
66
|
renderer.hideCursor();
|
|
63
67
|
renderer.clear();
|
|
64
68
|
syncAgents();
|
|
65
|
-
|
|
69
|
+
renderAllNow();
|
|
66
70
|
return true;
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -74,6 +78,11 @@ function createMultiWindowController(options = {}) {
|
|
|
74
78
|
renderThrottleTimer = null;
|
|
75
79
|
dirtyPanes.clear();
|
|
76
80
|
}
|
|
81
|
+
if (renderAllTimer) {
|
|
82
|
+
clearTimeout(renderAllTimer);
|
|
83
|
+
renderAllTimer = null;
|
|
84
|
+
renderAllTrailing = false;
|
|
85
|
+
}
|
|
77
86
|
paneManager.disconnectAll();
|
|
78
87
|
renderer.showCursor();
|
|
79
88
|
restoreTerminal();
|
|
@@ -130,13 +139,41 @@ function createMultiWindowController(options = {}) {
|
|
|
130
139
|
}
|
|
131
140
|
}
|
|
132
141
|
|
|
133
|
-
function
|
|
142
|
+
function renderAllNow() {
|
|
134
143
|
if (!active) return;
|
|
144
|
+
lastRenderAllAt = Date.now();
|
|
135
145
|
try {
|
|
136
146
|
const agents = paneManager.getAgentIds();
|
|
137
147
|
const layout = calculatePaneLayout(getCols(), getRows(), agents.length);
|
|
138
148
|
const cols = getCols();
|
|
139
149
|
|
|
150
|
+
// Resolve the completion popup up front so stale rows are cleared
|
|
151
|
+
// before anything repaints (clearing also invalidates renderer caches).
|
|
152
|
+
const cmp = getCompletions();
|
|
153
|
+
let nextCompletionPopup = null;
|
|
154
|
+
let nextCompletionItems = null;
|
|
155
|
+
let nextCompletionStart = 0;
|
|
156
|
+
if (cmp && Array.isArray(cmp.items) && cmp.items.length > 0 && layout.inputPane) {
|
|
157
|
+
const start = Math.min(cmp.windowStart || 0, Math.max(0, cmp.items.length - (cmp.pageSize || 8)));
|
|
158
|
+
const end = Math.min(cmp.items.length, start + (cmp.pageSize || 8));
|
|
159
|
+
const visible = cmp.items.slice(start, end);
|
|
160
|
+
const popupTop = layout.inputPane.top - visible.length - 1;
|
|
161
|
+
if (popupTop >= 0) {
|
|
162
|
+
nextCompletionPopup = { top: popupTop, left: 0, width: cols, height: visible.length + 1 };
|
|
163
|
+
nextCompletionItems = visible;
|
|
164
|
+
nextCompletionStart = start;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const prevPopup = lastCompletionPopup;
|
|
168
|
+
const samePopupRect = prevPopup && nextCompletionPopup &&
|
|
169
|
+
prevPopup.top === nextCompletionPopup.top &&
|
|
170
|
+
prevPopup.width === nextCompletionPopup.width &&
|
|
171
|
+
prevPopup.height === nextCompletionPopup.height &&
|
|
172
|
+
(prevPopup.left || 0) === (nextCompletionPopup.left || 0);
|
|
173
|
+
if (prevPopup && !samePopupRect && typeof renderer.clearRows === "function") {
|
|
174
|
+
renderer.clearRows(prevPopup.top, prevPopup.height, prevPopup.width, prevPopup.left || 0);
|
|
175
|
+
}
|
|
176
|
+
|
|
140
177
|
renderer.renderChatLog(layout.chatPane, getChatLogLines());
|
|
141
178
|
|
|
142
179
|
const focused = getTerminalFocused() ? paneManager.getFocused() : null;
|
|
@@ -173,35 +210,19 @@ function createMultiWindowController(options = {}) {
|
|
|
173
210
|
renderer.renderDashboard(layout.dashboardPane, lines);
|
|
174
211
|
}
|
|
175
212
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const visible = cmp.items.slice(start, end);
|
|
190
|
-
const popupTop = layout.inputPane.top - visible.length - 1;
|
|
191
|
-
if (popupTop >= 0) {
|
|
192
|
-
nextCompletionPopup = { top: popupTop, left: 0, width: cols, height: visible.length + 1 };
|
|
193
|
-
renderer.renderSeparator({ top: popupTop, left: 0, width: cols });
|
|
194
|
-
for (let i = 0; i < visible.length; i++) {
|
|
195
|
-
const idx = start + i;
|
|
196
|
-
const selected = idx === cmp.index;
|
|
197
|
-
const label = visible[i].label || "";
|
|
198
|
-
const desc = visible[i].description || "";
|
|
199
|
-
const line = selected
|
|
200
|
-
? `\x1b[7;36m${label}\x1b[0m \x1b[90m${desc}\x1b[0m`
|
|
201
|
-
: `\x1b[90m${label} ${desc}\x1b[0m`;
|
|
202
|
-
const pad = Math.max(0, cols - renderer.visibleLength(line));
|
|
203
|
-
renderer.write(renderer.moveTo(popupTop + 1 + i, 0) + line + " ".repeat(pad) + "\x1b[0m");
|
|
204
|
-
}
|
|
213
|
+
if (nextCompletionPopup && nextCompletionItems) {
|
|
214
|
+
const popupTop = nextCompletionPopup.top;
|
|
215
|
+
renderer.renderSeparator({ top: popupTop, left: 0, width: cols });
|
|
216
|
+
for (let i = 0; i < nextCompletionItems.length; i++) {
|
|
217
|
+
const idx = nextCompletionStart + i;
|
|
218
|
+
const selected = idx === cmp.index;
|
|
219
|
+
const label = nextCompletionItems[i].label || "";
|
|
220
|
+
const desc = nextCompletionItems[i].description || "";
|
|
221
|
+
const line = selected
|
|
222
|
+
? `\x1b[7;36m${label}\x1b[0m \x1b[90m${desc}\x1b[0m`
|
|
223
|
+
: `\x1b[90m${label} ${desc}\x1b[0m`;
|
|
224
|
+
const pad = Math.max(0, cols - renderer.visibleLength(line));
|
|
225
|
+
renderer.write(renderer.moveTo(popupTop + 1 + i, 0) + line + " ".repeat(pad) + "\x1b[0m");
|
|
205
226
|
}
|
|
206
227
|
}
|
|
207
228
|
lastCompletionPopup = nextCompletionPopup;
|
|
@@ -210,6 +231,27 @@ function createMultiWindowController(options = {}) {
|
|
|
210
231
|
}
|
|
211
232
|
}
|
|
212
233
|
|
|
234
|
+
// Throttled entry point for external callers: renders immediately when idle,
|
|
235
|
+
// otherwise coalesces bursts into one trailing frame so state stays consistent.
|
|
236
|
+
function renderAll() {
|
|
237
|
+
if (!active) return;
|
|
238
|
+
const elapsed = Date.now() - lastRenderAllAt;
|
|
239
|
+
if (!renderAllTimer && elapsed >= RENDER_ALL_MIN_INTERVAL_MS) {
|
|
240
|
+
renderAllNow();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
renderAllTrailing = true;
|
|
244
|
+
if (!renderAllTimer) {
|
|
245
|
+
renderAllTimer = setTimeout(() => {
|
|
246
|
+
renderAllTimer = null;
|
|
247
|
+
if (!renderAllTrailing) return;
|
|
248
|
+
renderAllTrailing = false;
|
|
249
|
+
renderAllNow();
|
|
250
|
+
}, Math.max(RENDER_ALL_MIN_INTERVAL_MS - elapsed, 16));
|
|
251
|
+
if (typeof renderAllTimer.unref === "function") renderAllTimer.unref();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
213
255
|
function handleKey(key) {
|
|
214
256
|
if (!active) return false;
|
|
215
257
|
|
|
@@ -219,7 +261,7 @@ function createMultiWindowController(options = {}) {
|
|
|
219
261
|
|
|
220
262
|
if (key.name === "w" && key.ctrl) {
|
|
221
263
|
paneManager.cycleFocus();
|
|
222
|
-
|
|
264
|
+
renderAllNow();
|
|
223
265
|
return true;
|
|
224
266
|
}
|
|
225
267
|
|
|
@@ -237,14 +279,14 @@ function createMultiWindowController(options = {}) {
|
|
|
237
279
|
if (!agents.includes(agentId)) return;
|
|
238
280
|
paneManager.setFocused(agentId);
|
|
239
281
|
onFocusAgent(agentId);
|
|
240
|
-
|
|
282
|
+
renderAllNow();
|
|
241
283
|
}
|
|
242
284
|
|
|
243
285
|
function handleResize() {
|
|
244
286
|
if (!active) return;
|
|
245
287
|
syncAgents();
|
|
246
288
|
renderer.clear();
|
|
247
|
-
|
|
289
|
+
renderAllNow();
|
|
248
290
|
}
|
|
249
291
|
|
|
250
292
|
function isActive() { return active; }
|
|
@@ -10,10 +10,36 @@ function createRenderer(options = {}) {
|
|
|
10
10
|
write: rawWrite = process.stdout.write.bind(process.stdout),
|
|
11
11
|
} = options;
|
|
12
12
|
|
|
13
|
+
// Last-write caches used to skip redundant output. Both are reset by
|
|
14
|
+
// clear()/clearRows() so a blanked region is always repainted.
|
|
15
|
+
let slotCache = new Map();
|
|
16
|
+
let paneSigs = new WeakMap();
|
|
17
|
+
|
|
13
18
|
function write(data) {
|
|
14
19
|
try { rawWrite(data); } catch {}
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
function writeIfChanged(slot, data, top, height) {
|
|
23
|
+
const prev = slotCache.get(slot);
|
|
24
|
+
if (prev && prev.data === data && prev.top === top) return;
|
|
25
|
+
slotCache.set(slot, { data, top, height });
|
|
26
|
+
write(data);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function paneSignature(pane, focused, extra) {
|
|
30
|
+
return [pane.top, pane.left, pane.width, pane.height, focused ? 1 : 0, extra || ""].join("|");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function shouldSkipPane(vt, sig, force) {
|
|
34
|
+
if (force || paneSigs.get(vt) !== sig) return false;
|
|
35
|
+
return typeof vt.isDirty === "function" ? !vt.isDirty() : false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function markPaneRendered(vt, sig) {
|
|
39
|
+
paneSigs.set(vt, sig);
|
|
40
|
+
if (vt && typeof vt.clearDirty === "function") vt.clearDirty();
|
|
41
|
+
}
|
|
42
|
+
|
|
17
43
|
function moveTo(row, col) {
|
|
18
44
|
return `\x1b[${row + 1};${col + 1}H`;
|
|
19
45
|
}
|
|
@@ -59,7 +85,9 @@ function createRenderer(options = {}) {
|
|
|
59
85
|
);
|
|
60
86
|
}
|
|
61
87
|
|
|
62
|
-
function renderPane(vt, pane, focused, label) {
|
|
88
|
+
function renderPane(vt, pane, focused, label, opts = {}) {
|
|
89
|
+
const sig = paneSignature(pane, focused, label || "");
|
|
90
|
+
if (shouldSkipPane(vt, sig, opts.force)) return;
|
|
63
91
|
const { buffer, rows, cols, cursorRow, cursorCol } = vt.getScreen();
|
|
64
92
|
const { top, left, width, height } = pane;
|
|
65
93
|
|
|
@@ -108,6 +136,7 @@ function createRenderer(options = {}) {
|
|
|
108
136
|
out += moveTo(top + height - 1, left) + borderColor + botLine + reset;
|
|
109
137
|
|
|
110
138
|
write(out);
|
|
139
|
+
markPaneRendered(vt, sig);
|
|
111
140
|
}
|
|
112
141
|
|
|
113
142
|
function renderCells(cells, maxWidth, cursorCol = -1) {
|
|
@@ -147,7 +176,13 @@ function createRenderer(options = {}) {
|
|
|
147
176
|
return `${color}${truncated}${" ".repeat(pad)}${reset}`;
|
|
148
177
|
}
|
|
149
178
|
|
|
150
|
-
function renderInternalPane(vt, pane, focused, info = {}) {
|
|
179
|
+
function renderInternalPane(vt, pane, focused, info = {}, opts = {}) {
|
|
180
|
+
const extra = JSON.stringify([
|
|
181
|
+
info.label || "", info.status || "", info.detail || "",
|
|
182
|
+
info.input || "", info.cursor ?? "",
|
|
183
|
+
]);
|
|
184
|
+
const sig = paneSignature(pane, focused, extra);
|
|
185
|
+
if (shouldSkipPane(vt, sig, opts.force)) return;
|
|
151
186
|
const { buffer, rows, cols } = vt.getScreen();
|
|
152
187
|
const { top, left, width, height } = pane;
|
|
153
188
|
|
|
@@ -217,6 +252,7 @@ function createRenderer(options = {}) {
|
|
|
217
252
|
out += moveTo(top + height - 1, left) + borderColor + botLine + reset;
|
|
218
253
|
|
|
219
254
|
write(out);
|
|
255
|
+
markPaneRendered(vt, sig);
|
|
220
256
|
}
|
|
221
257
|
|
|
222
258
|
function stripControl(str) {
|
|
@@ -288,14 +324,14 @@ function createRenderer(options = {}) {
|
|
|
288
324
|
out += truncated + reset + " ".repeat(pad);
|
|
289
325
|
out += dim + BOX.v + reset;
|
|
290
326
|
}
|
|
291
|
-
|
|
327
|
+
writeIfChanged("chatlog", out, top, height);
|
|
292
328
|
}
|
|
293
329
|
|
|
294
330
|
function renderSeparator(pane, highlighted) {
|
|
295
331
|
const { top, left, width } = pane;
|
|
296
332
|
const reset = "\x1b[0m";
|
|
297
333
|
const color = highlighted ? "\x1b[36m" : "\x1b[90m";
|
|
298
|
-
|
|
334
|
+
writeIfChanged(`sep:${top}:${left}`, moveTo(top, left) + color + "─".repeat(width) + reset, top, 1);
|
|
299
335
|
}
|
|
300
336
|
|
|
301
337
|
function renderStatusLine(pane, text) {
|
|
@@ -304,17 +340,19 @@ function createRenderer(options = {}) {
|
|
|
304
340
|
const dim = "\x1b[90m";
|
|
305
341
|
const truncated = truncateVisible(text || "", width);
|
|
306
342
|
const pad = Math.max(0, width - visibleLength(truncated));
|
|
307
|
-
|
|
343
|
+
writeIfChanged("status", moveTo(top, left) + dim + truncated + " ".repeat(pad) + reset, top, 1);
|
|
308
344
|
}
|
|
309
345
|
|
|
310
346
|
function renderDashboard(pane, lines) {
|
|
311
347
|
const { top, left, width } = pane;
|
|
312
348
|
const reset = "\x1b[0m";
|
|
349
|
+
let out = "";
|
|
313
350
|
for (let i = 0; i < lines.length; i++) {
|
|
314
351
|
const line = lines[i] || "";
|
|
315
352
|
const pad = Math.max(0, width - visibleLength(line));
|
|
316
|
-
|
|
353
|
+
out += moveTo(top + i, left) + line + " ".repeat(pad) + reset;
|
|
317
354
|
}
|
|
355
|
+
writeIfChanged("dashboard", out, top, lines.length);
|
|
318
356
|
}
|
|
319
357
|
|
|
320
358
|
function renderInputPrompt(pane, prefix, draft, cursor) {
|
|
@@ -331,17 +369,28 @@ function createRenderer(options = {}) {
|
|
|
331
369
|
const promptLine = cyan + prefixStr + reset + before + inverse + cursorChar + reset + after;
|
|
332
370
|
const truncated = truncateVisible(promptLine, width);
|
|
333
371
|
const pad = Math.max(0, width - visibleLength(truncated));
|
|
334
|
-
|
|
372
|
+
writeIfChanged("input", moveTo(top, left) + truncated + " ".repeat(pad) + reset, top, 1);
|
|
335
373
|
}
|
|
336
374
|
|
|
337
375
|
function hideCursor() { write("\x1b[?25l"); }
|
|
338
376
|
function showCursor() { write("\x1b[?25h"); }
|
|
339
|
-
function clear() {
|
|
377
|
+
function clear() {
|
|
378
|
+
slotCache = new Map();
|
|
379
|
+
paneSigs = new WeakMap();
|
|
380
|
+
write("\x1b[2J\x1b[H");
|
|
381
|
+
}
|
|
340
382
|
|
|
341
383
|
function clearRows(top, count, width, left = 0) {
|
|
342
384
|
const rows = Math.max(0, Number(count) || 0);
|
|
343
385
|
const cols = Math.max(0, Number(width) || 0);
|
|
344
386
|
if (rows === 0 || cols === 0) return;
|
|
387
|
+
// Blanked rows must be repainted on the next pass: drop every cached
|
|
388
|
+
// slot overlapping the cleared range and reset pane signatures (kept
|
|
389
|
+
// in a WeakMap, so they cannot be filtered selectively).
|
|
390
|
+
for (const [slot, entry] of slotCache) {
|
|
391
|
+
if (entry.top < top + rows && top < entry.top + entry.height) slotCache.delete(slot);
|
|
392
|
+
}
|
|
393
|
+
paneSigs = new WeakMap();
|
|
345
394
|
let out = "";
|
|
346
395
|
const blank = " ".repeat(cols);
|
|
347
396
|
for (let i = 0; i < rows; i++) {
|
package/src/app/cli/run.js
CHANGED
|
@@ -733,8 +733,8 @@ async function runCli(argv) {
|
|
|
733
733
|
|
|
734
734
|
program
|
|
735
735
|
.command("launch")
|
|
736
|
-
.description("Launch an agent (uclaude, ucodex, uagy, ucode)")
|
|
737
|
-
.argument("<agent>", "Agent type: uclaude|ucodex|uagy|ucode|claude|codex|agy")
|
|
736
|
+
.description("Launch an agent (uclaude, ucodex, uagy, ukimi, ucode)")
|
|
737
|
+
.argument("<agent>", "Agent type: uclaude|ucodex|uagy|ukimi|ucode|claude|codex|agy|kimi")
|
|
738
738
|
.argument("[nickname]", "Optional nickname for the agent")
|
|
739
739
|
.option("--profile <id>", "Prompt profile to assign after launch")
|
|
740
740
|
.action(async (agent, nickname, opts) => {
|
|
@@ -751,11 +751,13 @@ async function runCli(argv) {
|
|
|
751
751
|
normalizedAgent = "codex";
|
|
752
752
|
} else if (agentLower === "uagy" || agentLower === "agy" || agentLower === "antigravity") {
|
|
753
753
|
normalizedAgent = "agy";
|
|
754
|
+
} else if (agentLower === "ukimi" || agentLower === "kimi" || agentLower === "kimi-cli" || agentLower === "kimi-code") {
|
|
755
|
+
normalizedAgent = "kimi";
|
|
754
756
|
} else if (agentLower === "ucode" || agentLower === "ufoo-code" || agentLower === "ufoo") {
|
|
755
757
|
normalizedAgent = "ucode";
|
|
756
758
|
} else {
|
|
757
759
|
console.error(`Unknown agent type: ${agent}`);
|
|
758
|
-
console.error("Valid types: uclaude, ucodex, uagy, ucode, claude, codex, agy");
|
|
760
|
+
console.error("Valid types: uclaude, ucodex, uagy, ukimi, ucode, claude, codex, agy, kimi");
|
|
759
761
|
process.exitCode = 1;
|
|
760
762
|
return;
|
|
761
763
|
}
|