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
package/src/code/nativeRunner.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const { randomUUID } = require("crypto");
|
|
2
2
|
const { loadConfig, defaultAgentModelForProvider, sameModelProvider } = require("../config");
|
|
3
|
+
const {
|
|
4
|
+
readKimiAccessToken,
|
|
5
|
+
resolveKimiUpstreamCredentials,
|
|
6
|
+
} = require("../agents/providers/credentials/kimi");
|
|
3
7
|
const { runToolCall } = require("./dispatch");
|
|
4
8
|
const { getReadToolDescription } = require("../agents/prompts/native/toolDescriptions/read");
|
|
5
9
|
const { getWriteToolDescription } = require("../agents/prompts/native/toolDescriptions/write");
|
|
@@ -9,6 +13,8 @@ const { getBashToolDescription } = require("../agents/prompts/native/toolDescrip
|
|
|
9
13
|
const CORE_TOOL_NAMES = new Set(["read", "write", "edit", "bash"]);
|
|
10
14
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
11
15
|
const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
16
|
+
const DEFAULT_KIMI_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
17
|
+
const DEFAULT_KIMI_MODEL = "k3";
|
|
12
18
|
// Claude Code SDK defaults to no turn limit; built-in agents cap at 30 (DreamTask)
|
|
13
19
|
// to 200 (fork). We count individual tool calls (not turns), so 100 leaves headroom
|
|
14
20
|
// for non-trivial tasks while still catching runaway loops. Override via env.
|
|
@@ -107,6 +113,7 @@ function normalizeProvider(value = "") {
|
|
|
107
113
|
if (!text) return "";
|
|
108
114
|
if (text === "codex" || text === "codex-cli" || text === "codex-code") return "openai";
|
|
109
115
|
if (text === "claude" || text === "claude-cli" || text === "claude-code") return "anthropic";
|
|
116
|
+
if (text === "kimi" || text === "kimi-code" || text === "moonshot") return "kimi";
|
|
110
117
|
if (text === "openai" || text === "anthropic") return text;
|
|
111
118
|
return text;
|
|
112
119
|
}
|
|
@@ -116,6 +123,7 @@ function resolveTransport({ provider = "", baseUrl = "" } = {}) {
|
|
|
116
123
|
const url = String(baseUrl || "").trim().toLowerCase();
|
|
117
124
|
|
|
118
125
|
if (normalizedProvider === "anthropic") return "anthropic-messages";
|
|
126
|
+
if (normalizedProvider === "kimi") return "openai-chat";
|
|
119
127
|
if (url.includes("anthropic.com")) return "anthropic-messages";
|
|
120
128
|
if (/\/messages(?:$|[/?#])/.test(url) && !/\/chat\/completions(?:$|[/?#])/.test(url)) {
|
|
121
129
|
return "anthropic-messages";
|
|
@@ -141,12 +149,14 @@ function resolveRuntimeConfig({ workspaceRoot = process.cwd(), provider = "", mo
|
|
|
141
149
|
model
|
|
142
150
|
|| process.env.UFOO_UCODE_MODEL
|
|
143
151
|
|| configuredModel
|
|
144
|
-
|| defaultAgentModelForProvider(selectedProvider)
|
|
152
|
+
|| (selectedProvider === "kimi" ? DEFAULT_KIMI_MODEL : defaultAgentModelForProvider(selectedProvider))
|
|
145
153
|
).trim();
|
|
146
154
|
|
|
147
155
|
const defaultBaseUrl = selectedProvider === "anthropic"
|
|
148
156
|
? String(process.env.ANTHROPIC_BASE_URL || DEFAULT_ANTHROPIC_BASE_URL)
|
|
149
|
-
:
|
|
157
|
+
: selectedProvider === "kimi"
|
|
158
|
+
? DEFAULT_KIMI_BASE_URL
|
|
159
|
+
: String(process.env.OPENAI_BASE_URL || DEFAULT_OPENAI_BASE_URL);
|
|
150
160
|
|
|
151
161
|
const baseUrl = String(
|
|
152
162
|
process.env.UFOO_UCODE_BASE_URL
|
|
@@ -154,19 +164,38 @@ function resolveRuntimeConfig({ workspaceRoot = process.cwd(), provider = "", mo
|
|
|
154
164
|
|| defaultBaseUrl
|
|
155
165
|
).trim();
|
|
156
166
|
|
|
157
|
-
const
|
|
167
|
+
const explicitApiKey = String(
|
|
158
168
|
process.env.UFOO_UCODE_API_KEY
|
|
159
169
|
|| config.ucodeApiKey
|
|
160
|
-
|| (selectedProvider === "openai" ? process.env.OPENAI_API_KEY : "")
|
|
161
|
-
|| (selectedProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY : "")
|
|
162
170
|
|| ""
|
|
163
171
|
).trim();
|
|
172
|
+
let apiKey = explicitApiKey;
|
|
173
|
+
let apiKeySource = explicitApiKey ? "explicit" : "";
|
|
174
|
+
let kimiCredentialState = "";
|
|
175
|
+
if (!apiKey && selectedProvider === "kimi") {
|
|
176
|
+
const credential = readKimiAccessToken({ env: process.env });
|
|
177
|
+
if (credential && credential.accessToken) {
|
|
178
|
+
apiKey = String(credential.accessToken).trim();
|
|
179
|
+
apiKeySource = "kimi-credential";
|
|
180
|
+
kimiCredentialState = String(credential.state || "");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!apiKey) {
|
|
184
|
+
apiKey = String(
|
|
185
|
+
(selectedProvider === "openai" ? process.env.OPENAI_API_KEY : "")
|
|
186
|
+
|| (selectedProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY : "")
|
|
187
|
+
|| ""
|
|
188
|
+
).trim();
|
|
189
|
+
if (apiKey) apiKeySource = "env";
|
|
190
|
+
}
|
|
164
191
|
|
|
165
192
|
return {
|
|
166
193
|
provider: selectedProvider,
|
|
167
194
|
model: selectedModel,
|
|
168
195
|
baseUrl,
|
|
169
196
|
apiKey,
|
|
197
|
+
apiKeySource,
|
|
198
|
+
kimiCredentialState,
|
|
170
199
|
transport: resolveTransport({ provider: selectedProvider, baseUrl }),
|
|
171
200
|
};
|
|
172
201
|
}
|
|
@@ -532,6 +561,7 @@ async function runOpenAiLikeTurn({
|
|
|
532
561
|
url = "",
|
|
533
562
|
apiKey = "",
|
|
534
563
|
model = "",
|
|
564
|
+
provider = "",
|
|
535
565
|
messages = [],
|
|
536
566
|
onTextDelta = null,
|
|
537
567
|
onThinkingDelta = null,
|
|
@@ -546,7 +576,8 @@ async function runOpenAiLikeTurn({
|
|
|
546
576
|
tools: buildCoreToolSpecs(),
|
|
547
577
|
tool_choice: "auto",
|
|
548
578
|
stream: true,
|
|
549
|
-
temperature
|
|
579
|
+
// Kimi k3 rejects any temperature other than 1.
|
|
580
|
+
temperature: normalizeProvider(provider) === "kimi" ? 1 : 0,
|
|
550
581
|
};
|
|
551
582
|
|
|
552
583
|
const headers = {
|
|
@@ -1075,6 +1106,7 @@ async function runNativeLoop({
|
|
|
1075
1106
|
model = "",
|
|
1076
1107
|
baseUrl = "",
|
|
1077
1108
|
apiKey = "",
|
|
1109
|
+
provider = "",
|
|
1078
1110
|
timeoutMs = 300000,
|
|
1079
1111
|
onStreamDelta = null,
|
|
1080
1112
|
onThinkingDelta = null,
|
|
@@ -1109,6 +1141,7 @@ async function runNativeLoop({
|
|
|
1109
1141
|
url: requestUrl,
|
|
1110
1142
|
apiKey,
|
|
1111
1143
|
model: requestModel,
|
|
1144
|
+
provider,
|
|
1112
1145
|
systemPrompt,
|
|
1113
1146
|
messages,
|
|
1114
1147
|
signal,
|
|
@@ -1235,6 +1268,23 @@ async function runNativeAgentTask({
|
|
|
1235
1268
|
model,
|
|
1236
1269
|
});
|
|
1237
1270
|
|
|
1271
|
+
// Kimi tokens expire; resolveRuntimeConfig reads the credential file
|
|
1272
|
+
// synchronously, so refresh it here (async) when the key came from that
|
|
1273
|
+
// file and the token is outside the fresh window.
|
|
1274
|
+
if (
|
|
1275
|
+
runtime.provider === "kimi"
|
|
1276
|
+
&& runtime.apiKeySource === "kimi-credential"
|
|
1277
|
+
&& runtime.kimiCredentialState !== "fresh"
|
|
1278
|
+
) {
|
|
1279
|
+
try {
|
|
1280
|
+
const credential = await resolveKimiUpstreamCredentials({ env: process.env });
|
|
1281
|
+
const token = String(credential && credential.accessToken || "").trim();
|
|
1282
|
+
if (token) runtime.apiKey = token;
|
|
1283
|
+
} catch {
|
|
1284
|
+
// Keep the file token; the request itself will surface auth failures.
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1238
1288
|
const transport = TRANSPORTS[runtime.transport] || TRANSPORTS["openai-chat"];
|
|
1239
1289
|
|
|
1240
1290
|
const runResult = await runNativeLoop({
|
|
@@ -1246,6 +1296,7 @@ async function runNativeAgentTask({
|
|
|
1246
1296
|
model: runtime.model,
|
|
1247
1297
|
baseUrl: runtime.baseUrl,
|
|
1248
1298
|
apiKey: runtime.apiKey,
|
|
1299
|
+
provider: runtime.provider,
|
|
1249
1300
|
timeoutMs,
|
|
1250
1301
|
onStreamDelta: trackingStreamDelta,
|
|
1251
1302
|
onThinkingDelta,
|
package/src/config.js
CHANGED
|
@@ -13,11 +13,15 @@ const SETTINGS_MODEL_DEFAULTS = Object.freeze({
|
|
|
13
13
|
// command-line flag for model, so the value here is a placeholder for
|
|
14
14
|
// display; we never pass it on the agy command line.
|
|
15
15
|
agy: "",
|
|
16
|
+
// kimi reads its model from its own config.toml (default_model); ufoo
|
|
17
|
+
// never injects -m/--model, so keep the placeholder empty like agy.
|
|
18
|
+
kimi: "",
|
|
16
19
|
}),
|
|
17
20
|
router: Object.freeze({
|
|
18
21
|
codex: "gpt-5.3-codex-spark",
|
|
19
22
|
claude: "sonnet-4.7",
|
|
20
23
|
agy: "",
|
|
24
|
+
kimi: "",
|
|
21
25
|
}),
|
|
22
26
|
});
|
|
23
27
|
|
|
@@ -57,6 +61,7 @@ function normalizeLaunchMode(value) {
|
|
|
57
61
|
function normalizeAgentProvider(value) {
|
|
58
62
|
if (value === "claude-cli") return "claude-cli";
|
|
59
63
|
if (value === "agy-cli" || value === "agy" || value === "antigravity") return "agy-cli";
|
|
64
|
+
if (value === "kimi-cli" || value === "kimi") return "kimi-cli";
|
|
60
65
|
return "codex-cli";
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -64,6 +69,7 @@ function providerKey(value = "") {
|
|
|
64
69
|
const text = String(value || "").trim().toLowerCase();
|
|
65
70
|
if (text === "claude" || text === "claude-cli" || text === "claude-code" || text === "anthropic") return "claude";
|
|
66
71
|
if (text === "agy" || text === "agy-cli" || text === "antigravity") return "agy";
|
|
72
|
+
if (text === "kimi" || text === "kimi-cli" || text === "kimi-code") return "kimi";
|
|
67
73
|
return "codex";
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -81,6 +87,8 @@ function defaultRouterProviderForAgentProvider(value = "") {
|
|
|
81
87
|
// agy has no router-model API; fall back to codex (the controller still
|
|
82
88
|
// routes via codex/claude regardless of which agent provider runs).
|
|
83
89
|
if (key === "agy") return "codex";
|
|
90
|
+
// kimi likewise manages its own models; router falls back to codex.
|
|
91
|
+
if (key === "kimi") return "codex";
|
|
84
92
|
return "codex";
|
|
85
93
|
}
|
|
86
94
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const ALLOWED_AGENT_TYPES = new Set(["auto", "codex", "claude", "ucode", "agy"]);
|
|
3
|
+
const ALLOWED_AGENT_TYPES = new Set(["auto", "codex", "claude", "ucode", "agy", "kimi"]);
|
|
4
4
|
const { resolvePromptProfileReference } = require("./promptProfiles");
|
|
5
5
|
|
|
6
6
|
function isPlainObject(value) {
|
|
@@ -310,6 +310,7 @@ function resolveAutoAgentType(projectRoot, requestedType) {
|
|
|
310
310
|
if (provider === "claude-cli") return "claude";
|
|
311
311
|
if (provider === "ucode") return "ucode";
|
|
312
312
|
if (provider === "agy-cli") return "agy";
|
|
313
|
+
if (provider === "kimi-cli") return "kimi";
|
|
313
314
|
return "codex";
|
|
314
315
|
}
|
|
315
316
|
|
|
@@ -397,6 +398,8 @@ function buildExecutionPlan({
|
|
|
397
398
|
// not via post-launch injection.
|
|
398
399
|
: (resolvedType === "agy"
|
|
399
400
|
? "initial-prompt-arg"
|
|
401
|
+
// Kimi has no initial-prompt flag, so it falls through to
|
|
402
|
+
// post-launch PTY injection like any other future type.
|
|
400
403
|
: "post-launch-inject"))));
|
|
401
404
|
const bootstrapPrompt = bootstrapRequired
|
|
402
405
|
? composeGroupBootstrapPrompt({
|
|
@@ -923,6 +926,9 @@ function createGroupOrchestrator(options = {}) {
|
|
|
923
926
|
// Agy uses the same "send bootstrap as a launch flag" model, but its
|
|
924
927
|
// flag is `-i <text>` (alias for --prompt-interactive). Same strategy
|
|
925
928
|
// key, different arg shape.
|
|
929
|
+
//
|
|
930
|
+
// Kimi never enters this branch: it has no initial-prompt flag, so its
|
|
931
|
+
// bootstrap strategy is post-launch-inject (handled further below).
|
|
926
932
|
if (item.bootstrap_strategy === "initial-prompt-arg") {
|
|
927
933
|
member.bootstrap_attempted_at = nowIso();
|
|
928
934
|
member.bootstrap_error = "";
|
|
@@ -62,6 +62,7 @@ function normalizeBusAgentType(agentType = "") {
|
|
|
62
62
|
if (value === "codex") return "codex";
|
|
63
63
|
if (value === "claude" || value === "claude-code") return "claude-code";
|
|
64
64
|
if (value === "agy" || value === "antigravity") return "agy";
|
|
65
|
+
if (value === "kimi" || value === "kimi-cli" || value === "kimi-code") return "kimi";
|
|
65
66
|
if (value === "ufoo" || value === "ucode" || value === "ufoo-code") return "ufoo-code";
|
|
66
67
|
return value;
|
|
67
68
|
}
|
|
@@ -71,6 +72,7 @@ function normalizeLaunchAgent(agent = "") {
|
|
|
71
72
|
if (value === "codex") return "codex";
|
|
72
73
|
if (value === "claude" || value === "claude-code") return "claude";
|
|
73
74
|
if (value === "agy" || value === "antigravity") return "agy";
|
|
75
|
+
if (value === "kimi" || value === "kimi-cli" || value === "kimi-code") return "kimi";
|
|
74
76
|
if (value === "ufoo" || value === "ucode" || value === "ufoo-code") return "ufoo";
|
|
75
77
|
return "";
|
|
76
78
|
}
|
|
@@ -1685,9 +1687,9 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
|
|
|
1685
1687
|
: null,
|
|
1686
1688
|
};
|
|
1687
1689
|
let soloLaunchBootstrap = null;
|
|
1688
|
-
if (requestedProfile && (normalizedAgent === "ufoo" || normalizedAgent === "claude" || normalizedAgent === "codex" || normalizedAgent === "agy")) {
|
|
1689
|
-
const agentTypeMap = { ufoo: "ufoo-code", claude: "claude-code", codex: "codex", agy: "agy" };
|
|
1690
|
-
const defaultNickMap = { ufoo: "ucode", claude: "claude", codex: "codex", agy: "agy" };
|
|
1690
|
+
if (requestedProfile && (normalizedAgent === "ufoo" || normalizedAgent === "claude" || normalizedAgent === "codex" || normalizedAgent === "agy" || normalizedAgent === "kimi")) {
|
|
1691
|
+
const agentTypeMap = { ufoo: "ufoo-code", claude: "claude-code", codex: "codex", agy: "agy", kimi: "kimi" };
|
|
1692
|
+
const defaultNickMap = { ufoo: "ucode", claude: "claude", codex: "codex", agy: "agy", kimi: "kimi" };
|
|
1691
1693
|
const agentTypeForBootstrap = agentTypeMap[normalizedAgent];
|
|
1692
1694
|
const soloNickname = explicitNickname || defaultNickMap[normalizedAgent];
|
|
1693
1695
|
const profileResult = resolveSoloPromptProfile(projectRoot, requestedProfile);
|
|
@@ -1738,6 +1740,13 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
|
|
|
1738
1740
|
...(Array.isArray(op.extra_args) ? op.extra_args : []),
|
|
1739
1741
|
"-i", built.promptText,
|
|
1740
1742
|
];
|
|
1743
|
+
} else if (normalizedAgent === "kimi") {
|
|
1744
|
+
// kimi: no initial-prompt flag — deliver the bootstrap through
|
|
1745
|
+
// the launcher's post-launch PTY injection instead.
|
|
1746
|
+
op.extra_env = {
|
|
1747
|
+
...(op.extra_env && typeof op.extra_env === "object" ? op.extra_env : {}),
|
|
1748
|
+
UFOO_STARTUP_BOOTSTRAP_TEXT: built.promptText,
|
|
1749
|
+
};
|
|
1741
1750
|
}
|
|
1742
1751
|
soloLaunchBootstrap = {
|
|
1743
1752
|
requested_profile: profileResult.requested_profile,
|
|
@@ -22,6 +22,7 @@ function normalizeLaunchAgent(agent = "") {
|
|
|
22
22
|
if (value === "codex") return "codex";
|
|
23
23
|
if (value === "claude" || value === "claude-code") return "claude";
|
|
24
24
|
if (value === "agy" || value === "antigravity") return "agy";
|
|
25
|
+
if (value === "kimi" || value === "kimi-cli" || value === "kimi-code") return "kimi";
|
|
25
26
|
if (value === "ufoo" || value === "ucode" || value === "ufoo-code") return "ufoo";
|
|
26
27
|
return "";
|
|
27
28
|
}
|
|
@@ -30,6 +31,7 @@ function toBusAgentType(agent = "") {
|
|
|
30
31
|
if (agent === "codex") return "codex";
|
|
31
32
|
if (agent === "claude") return "claude-code";
|
|
32
33
|
if (agent === "agy") return "agy";
|
|
34
|
+
if (agent === "kimi") return "kimi";
|
|
33
35
|
if (agent === "ufoo") return "ufoo-code";
|
|
34
36
|
return "";
|
|
35
37
|
}
|
|
@@ -38,6 +40,7 @@ function toTerminalBinary(agent = "") {
|
|
|
38
40
|
if (agent === "codex") return "ucodex";
|
|
39
41
|
if (agent === "claude") return "uclaude";
|
|
40
42
|
if (agent === "agy") return "uagy";
|
|
43
|
+
if (agent === "kimi") return "ukimi";
|
|
41
44
|
if (agent === "ufoo") return "ucode";
|
|
42
45
|
return "";
|
|
43
46
|
}
|
|
@@ -46,6 +49,7 @@ function toTmuxBinary(agent = "") {
|
|
|
46
49
|
if (agent === "codex") return "ucodex";
|
|
47
50
|
if (agent === "claude") return "uclaude";
|
|
48
51
|
if (agent === "agy") return "uagy";
|
|
52
|
+
if (agent === "kimi") return "ukimi";
|
|
49
53
|
if (agent === "ufoo") return "ucode";
|
|
50
54
|
return "";
|
|
51
55
|
}
|
|
@@ -1100,6 +1104,8 @@ function buildResumeArgs(agent, sessionId) {
|
|
|
1100
1104
|
// Agy resumes by conversation UUID; bin/uagy.js de-duplicates if the same
|
|
1101
1105
|
// flag is already injected via provider_session_id readback.
|
|
1102
1106
|
if (agent === "agy") return [`--conversation=${sessionId}`];
|
|
1107
|
+
// kimi resumes a session by id via `-S, --session <id>`.
|
|
1108
|
+
if (agent === "kimi") return ["--session", sessionId];
|
|
1103
1109
|
return [];
|
|
1104
1110
|
}
|
|
1105
1111
|
|
|
@@ -1152,7 +1158,7 @@ function collectRecoverableAgents(projectRoot, target = "") {
|
|
|
1152
1158
|
continue;
|
|
1153
1159
|
}
|
|
1154
1160
|
const agent = normalizeAgentType(meta.agent_type);
|
|
1155
|
-
if (agent !== "codex" && agent !== "claude" && agent !== "agy") {
|
|
1161
|
+
if (agent !== "codex" && agent !== "claude" && agent !== "agy" && agent !== "kimi") {
|
|
1156
1162
|
skipped.push({ id, reason: "unsupported agent type" });
|
|
1157
1163
|
continue;
|
|
1158
1164
|
}
|
|
@@ -113,9 +113,44 @@ function resolveCodexSessionFromFile(cwd) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Resolve Kimi Code session ID from the session index.
|
|
118
|
+
* Kimi writes $KIMI_CODE_HOME/session_index.jsonl (default ~/.kimi-code/)
|
|
119
|
+
* with one JSON line per session: { sessionId, sessionDir, workDir }.
|
|
120
|
+
* The file is append-only, so the last line matching the cwd is the most
|
|
121
|
+
* recent session for that directory.
|
|
122
|
+
*/
|
|
123
|
+
function resolveKimiSessionFromIndex(cwd) {
|
|
124
|
+
if (!cwd) return null;
|
|
125
|
+
try {
|
|
126
|
+
const kimiHome = String(process.env.KIMI_CODE_HOME || "").trim()
|
|
127
|
+
|| path.join(os.homedir(), ".kimi-code");
|
|
128
|
+
const indexPath = path.join(kimiHome, "session_index.jsonl");
|
|
129
|
+
if (!fs.existsSync(indexPath)) return null;
|
|
130
|
+
const lines = fs.readFileSync(indexPath, "utf8").split("\n");
|
|
131
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
132
|
+
const line = lines[i].trim();
|
|
133
|
+
if (!line) continue;
|
|
134
|
+
try {
|
|
135
|
+
const record = JSON.parse(line);
|
|
136
|
+
const sessionId = record.sessionId || record.session_id || "";
|
|
137
|
+
const workDir = record.workDir || record.work_dir || record.cwd || "";
|
|
138
|
+
if (sessionId && workDir === cwd) {
|
|
139
|
+
return { sessionId, source: indexPath };
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
116
151
|
/**
|
|
117
152
|
* Resolve provider session ID directly from session files.
|
|
118
|
-
* @param {string} agentType - "claude-code" or "
|
|
153
|
+
* @param {string} agentType - "claude-code", "codex" or "kimi"
|
|
119
154
|
* @param {object} opts - { pid, cwd }
|
|
120
155
|
*/
|
|
121
156
|
function resolveSessionFromFile(agentType, opts = {}) {
|
|
@@ -125,6 +160,9 @@ function resolveSessionFromFile(agentType, opts = {}) {
|
|
|
125
160
|
if (agentType === "codex") {
|
|
126
161
|
return resolveCodexSessionFromFile(opts.cwd);
|
|
127
162
|
}
|
|
163
|
+
if (agentType === "kimi") {
|
|
164
|
+
return resolveKimiSessionFromIndex(opts.cwd);
|
|
165
|
+
}
|
|
128
166
|
return null;
|
|
129
167
|
}
|
|
130
168
|
|
|
@@ -169,7 +207,7 @@ function scheduleProviderSessionResolve({
|
|
|
169
207
|
onResolved = null,
|
|
170
208
|
}) {
|
|
171
209
|
if (!subscriberId || !agentType) return null;
|
|
172
|
-
if (agentType !== "codex" && agentType !== "claude-code") return null;
|
|
210
|
+
if (agentType !== "codex" && agentType !== "claude-code" && agentType !== "kimi") return null;
|
|
173
211
|
|
|
174
212
|
let executed = false;
|
|
175
213
|
let cancelled = false;
|
|
@@ -226,5 +264,6 @@ module.exports = {
|
|
|
226
264
|
__private: {
|
|
227
265
|
resolveClaudeSessionFromFile,
|
|
228
266
|
resolveCodexSessionFromFile,
|
|
267
|
+
resolveKimiSessionFromIndex,
|
|
229
268
|
},
|
|
230
269
|
};
|
|
@@ -456,7 +456,7 @@ const LAUNCH_AGENT_SCHEMA = Object.freeze({
|
|
|
456
456
|
properties: Object.freeze({
|
|
457
457
|
agent: Object.freeze({
|
|
458
458
|
type: "string",
|
|
459
|
-
enum: Object.freeze(["codex", "claude", "ucode", "agy"]),
|
|
459
|
+
enum: Object.freeze(["codex", "claude", "ucode", "agy", "kimi"]),
|
|
460
460
|
}),
|
|
461
461
|
count: Object.freeze({ type: "integer", minimum: 1 }),
|
|
462
462
|
nickname: Object.freeze({ type: "string" }),
|
package/src/ui/format/index.js
CHANGED
|
@@ -16,9 +16,21 @@ const UCODE_BANNER_LINES = [
|
|
|
16
16
|
|
|
17
17
|
const UCODE_VERSION = String((pkg && pkg.version) || "dev");
|
|
18
18
|
|
|
19
|
+
// Flying-saucer patrol for busy/loading states: the 🛸 drifts left-right
|
|
20
|
+
// inside a fixed 6-cell field (three emoji slots), so the status text after
|
|
21
|
+
// it stays anchored instead of shifting with the saucer.
|
|
22
|
+
const UFO_FIELD_CELLS = 6;
|
|
23
|
+
const UFO_FRAMES = [];
|
|
24
|
+
for (let i = 0; i <= UFO_FIELD_CELLS - 2; i += 1) {
|
|
25
|
+
UFO_FRAMES.push(`${" ".repeat(i)}🛸${" ".repeat(UFO_FIELD_CELLS - 2 - i)}`);
|
|
26
|
+
}
|
|
27
|
+
for (let i = UFO_FIELD_CELLS - 3; i > 0; i -= 1) {
|
|
28
|
+
UFO_FRAMES.push(`${" ".repeat(i)}🛸${" ".repeat(UFO_FIELD_CELLS - 2 - i)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
const STATUS_INDICATORS = {
|
|
20
|
-
thinking:
|
|
21
|
-
typing:
|
|
32
|
+
thinking: UFO_FRAMES,
|
|
33
|
+
typing: UFO_FRAMES,
|
|
22
34
|
waiting: ["∙", "∙∙", "∙∙∙", "∙∙", "∙"],
|
|
23
35
|
};
|
|
24
36
|
|