@botcord/daemon 0.2.76 → 0.2.78
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/agent-discovery.d.ts +6 -0
- package/dist/agent-discovery.js +6 -0
- package/dist/daemon-config-map.d.ts +6 -0
- package/dist/daemon-config-map.js +5 -4
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +11 -1
- package/dist/gateway/channels/botcord.js +236 -51
- package/dist/gateway/runtimes/deepseek-tui.js +55 -7
- package/dist/provision.d.ts +2 -0
- package/dist/provision.js +66 -1
- package/dist/runtime-models.d.ts +17 -0
- package/dist/runtime-models.js +953 -0
- package/dist/runtime-route-options.d.ts +7 -0
- package/dist/runtime-route-options.js +45 -0
- package/package.json +2 -2
- package/src/__tests__/daemon-config-map.test.ts +26 -1
- package/src/__tests__/provision.test.ts +59 -0
- package/src/__tests__/runtime-discovery.test.ts +68 -9
- package/src/__tests__/runtime-models.test.ts +333 -0
- package/src/agent-discovery.ts +9 -0
- package/src/daemon-config-map.ts +17 -4
- package/src/daemon.ts +15 -3
- package/src/gateway/__tests__/botcord-channel.test.ts +24 -0
- package/src/gateway/__tests__/deepseek-tui-adapter.test.ts +30 -1
- package/src/gateway/channels/botcord.ts +249 -40
- package/src/gateway/runtimes/deepseek-tui.ts +49 -7
- package/src/provision.ts +69 -4
- package/src/runtime-models.ts +972 -0
- package/src/runtime-route-options.ts +52 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface RuntimeSelectionOptions {
|
|
2
|
+
runtimeModel?: string;
|
|
3
|
+
reasoningEffort?: string;
|
|
4
|
+
thinking?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildRuntimeSelectionExtraArgs(runtime: string | undefined, selection: RuntimeSelectionOptions): string[];
|
|
7
|
+
export declare function mergeRuntimeExtraArgs(inherited: string[] | undefined, selected: string[]): string[] | undefined;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function buildRuntimeSelectionExtraArgs(runtime, selection) {
|
|
2
|
+
if (!runtime)
|
|
3
|
+
return [];
|
|
4
|
+
const out = [];
|
|
5
|
+
const model = cleanString(selection.runtimeModel);
|
|
6
|
+
const reasoningEffort = cleanString(selection.reasoningEffort);
|
|
7
|
+
if (runtime === "claude-code") {
|
|
8
|
+
if (model)
|
|
9
|
+
out.push("--model", model);
|
|
10
|
+
if (reasoningEffort)
|
|
11
|
+
out.push("--effort", reasoningEffort);
|
|
12
|
+
}
|
|
13
|
+
else if (runtime === "codex") {
|
|
14
|
+
if (model)
|
|
15
|
+
out.push("--model", model);
|
|
16
|
+
if (reasoningEffort) {
|
|
17
|
+
out.push("-c", `model_reasoning_effort=${quoteCodexConfigValue(reasoningEffort)}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else if (runtime === "deepseek-tui") {
|
|
21
|
+
if (model)
|
|
22
|
+
out.push("--model", model);
|
|
23
|
+
if (reasoningEffort)
|
|
24
|
+
out.push("--reasoning-effort", reasoningEffort);
|
|
25
|
+
}
|
|
26
|
+
else if (runtime === "kimi-cli") {
|
|
27
|
+
if (model)
|
|
28
|
+
out.push("--model", model);
|
|
29
|
+
if (typeof selection.thinking === "boolean") {
|
|
30
|
+
out.push(selection.thinking ? "--thinking" : "--no-thinking");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
export function mergeRuntimeExtraArgs(inherited, selected) {
|
|
36
|
+
const out = [...(inherited ?? []), ...selected];
|
|
37
|
+
return out.length ? out : undefined;
|
|
38
|
+
}
|
|
39
|
+
function cleanString(value) {
|
|
40
|
+
const trimmed = value?.trim();
|
|
41
|
+
return trimmed ? trimmed : undefined;
|
|
42
|
+
}
|
|
43
|
+
function quoteCodexConfigValue(value) {
|
|
44
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botcord/daemon",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.78",
|
|
4
4
|
"description": "BotCord local daemon — bridges Hub inbox push to local Claude Code / Codex / Gemini CLIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@botcord/cli": "^0.1.7",
|
|
31
|
-
"@botcord/protocol-core": "
|
|
31
|
+
"@botcord/protocol-core": "^0.2.9",
|
|
32
32
|
"@larksuiteoapi/node-sdk": "^1.63.1",
|
|
33
33
|
"ws": "^8.20.1"
|
|
34
34
|
},
|
|
@@ -416,6 +416,32 @@ describe("buildManagedRoutes", () => {
|
|
|
416
416
|
expect(map.get("ag_one")?.extraArgs).not.toBe(withExtraArgs.extraArgs);
|
|
417
417
|
});
|
|
418
418
|
|
|
419
|
+
it("appends per-agent model and reasoning selections to inherited extraArgs", () => {
|
|
420
|
+
const withExtraArgs: GatewayRoute = {
|
|
421
|
+
runtime: "codex",
|
|
422
|
+
cwd: "/home/default",
|
|
423
|
+
extraArgs: ["--skip-git-repo-check"],
|
|
424
|
+
};
|
|
425
|
+
const map = buildManagedRoutes(
|
|
426
|
+
["ag_one"],
|
|
427
|
+
{
|
|
428
|
+
ag_one: {
|
|
429
|
+
runtime: "codex",
|
|
430
|
+
runtimeModel: "gpt-5.2",
|
|
431
|
+
reasoningEffort: "high",
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
withExtraArgs,
|
|
435
|
+
);
|
|
436
|
+
expect(map.get("ag_one")?.extraArgs).toEqual([
|
|
437
|
+
"--skip-git-repo-check",
|
|
438
|
+
"--model",
|
|
439
|
+
"gpt-5.2",
|
|
440
|
+
"-c",
|
|
441
|
+
'model_reasoning_effort="high"',
|
|
442
|
+
]);
|
|
443
|
+
});
|
|
444
|
+
|
|
419
445
|
it("omits extraArgs when defaultRoute has none", () => {
|
|
420
446
|
const map = buildManagedRoutes(["ag_one"], {}, defaultRoute);
|
|
421
447
|
expect(map.get("ag_one")).not.toHaveProperty("extraArgs");
|
|
@@ -492,4 +518,3 @@ describe("openclawGateways resolution", () => {
|
|
|
492
518
|
expect(gw.managedRoutes).toEqual([]);
|
|
493
519
|
});
|
|
494
520
|
});
|
|
495
|
-
|
|
@@ -660,6 +660,65 @@ describe("provision_agent handler writes runtime + cwd", () => {
|
|
|
660
660
|
}
|
|
661
661
|
});
|
|
662
662
|
|
|
663
|
+
it("persists model options and hot-adds them to the managed route", async () => {
|
|
664
|
+
const os = await import("node:os");
|
|
665
|
+
const fs = await import("node:fs");
|
|
666
|
+
const nodePath = await import("node:path");
|
|
667
|
+
|
|
668
|
+
const tmp = fs.mkdtempSync(nodePath.join(os.tmpdir(), "daemon-provision-"));
|
|
669
|
+
const prevHome = process.env.HOME;
|
|
670
|
+
process.env.HOME = tmp;
|
|
671
|
+
try {
|
|
672
|
+
mockState.cfg = {
|
|
673
|
+
defaultRoute: {
|
|
674
|
+
adapter: "codex",
|
|
675
|
+
cwd: tmp,
|
|
676
|
+
extraArgs: ["--skip-git-repo-check"],
|
|
677
|
+
},
|
|
678
|
+
routes: [],
|
|
679
|
+
streamBlocks: true,
|
|
680
|
+
};
|
|
681
|
+
const gw = makeFakeGateway();
|
|
682
|
+
const provisioner = createProvisioner({
|
|
683
|
+
gateway: gw as unknown as Parameters<typeof createProvisioner>[0]["gateway"],
|
|
684
|
+
});
|
|
685
|
+
const privateKey = Buffer.alloc(32, 8).toString("base64");
|
|
686
|
+
|
|
687
|
+
const ack = await provisioner({
|
|
688
|
+
id: "req_model",
|
|
689
|
+
type: CONTROL_FRAME_TYPES.PROVISION_AGENT,
|
|
690
|
+
params: {
|
|
691
|
+
runtime: "codex",
|
|
692
|
+
runtimeModel: "gpt-5.2",
|
|
693
|
+
reasoningEffort: "high",
|
|
694
|
+
credentials: {
|
|
695
|
+
agentId: "ag_model",
|
|
696
|
+
keyId: "k_model",
|
|
697
|
+
privateKey,
|
|
698
|
+
hubUrl: "https://hub.example",
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
expect(ack.ok).toBe(true);
|
|
704
|
+
const credFile = nodePath.join(tmp, ".botcord", "credentials", "ag_model.json");
|
|
705
|
+
const saved = JSON.parse(fs.readFileSync(credFile, "utf8")) as Record<string, unknown>;
|
|
706
|
+
expect(saved.runtimeModel).toBe("gpt-5.2");
|
|
707
|
+
expect(saved.reasoningEffort).toBe("high");
|
|
708
|
+
expect(gw.listManagedRoutes()[0].extraArgs).toEqual([
|
|
709
|
+
"--skip-git-repo-check",
|
|
710
|
+
"--model",
|
|
711
|
+
"gpt-5.2",
|
|
712
|
+
"-c",
|
|
713
|
+
'model_reasoning_effort="high"',
|
|
714
|
+
]);
|
|
715
|
+
} finally {
|
|
716
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
717
|
+
else process.env.HOME = prevHome;
|
|
718
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
|
|
663
722
|
it("rejects unknown runtime ids before touching disk", async () => {
|
|
664
723
|
const gw = makeFakeGateway();
|
|
665
724
|
const provisioner = createProvisioner({
|
|
@@ -76,15 +76,74 @@ describe("collectRuntimeSnapshot", () => {
|
|
|
76
76
|
},
|
|
77
77
|
]);
|
|
78
78
|
const snap = collectRuntimeSnapshot();
|
|
79
|
-
expect(snap.runtimes).
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
expect(snap.runtimes[0]).toMatchObject({
|
|
80
|
+
id: "claude-code",
|
|
81
|
+
available: true,
|
|
82
|
+
version: "1.2.3",
|
|
83
|
+
path: "/usr/local/bin/claude",
|
|
84
|
+
});
|
|
85
|
+
const claudeModels = (snap.runtimes[0] as { models?: Array<{ id: string }> }).models;
|
|
86
|
+
expect(claudeModels?.map((m) => m.id)).toContain("sonnet");
|
|
87
|
+
expect(claudeModels?.map((m) => m.id)).toContain("opus");
|
|
88
|
+
expect(snap.runtimes[1]).toEqual({ id: "codex", available: false });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("adds Kimi models from the local config file", () => {
|
|
92
|
+
const tmp = mkdtempSync(path.join(tmpdir(), "daemon-runtime-kimi-"));
|
|
93
|
+
const prevHome = process.env.HOME;
|
|
94
|
+
process.env.HOME = tmp;
|
|
95
|
+
try {
|
|
96
|
+
mkdirSync(path.join(tmp, ".kimi"), { recursive: true });
|
|
97
|
+
writeFileSync(
|
|
98
|
+
path.join(tmp, ".kimi", "config.toml"),
|
|
99
|
+
[
|
|
100
|
+
'default_model = "kimi-code/kimi-for-coding"',
|
|
101
|
+
"",
|
|
102
|
+
'[models."kimi-code/kimi-for-coding"]',
|
|
103
|
+
'provider = "managed:kimi-code"',
|
|
104
|
+
'model = "kimi-for-coding"',
|
|
105
|
+
"max_context_size = 262144",
|
|
106
|
+
'capabilities = ["thinking", "image_in"]',
|
|
107
|
+
'display_name = "Kimi-k2.6"',
|
|
108
|
+
"",
|
|
109
|
+
].join("\n"),
|
|
110
|
+
);
|
|
111
|
+
setRuntimes([
|
|
112
|
+
{
|
|
113
|
+
id: "kimi-cli",
|
|
114
|
+
displayName: "Kimi",
|
|
115
|
+
binary: "kimi",
|
|
116
|
+
supportsRun: true,
|
|
117
|
+
result: { available: true },
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
const [runtime] = collectRuntimeSnapshot().runtimes;
|
|
121
|
+
expect((runtime as { models?: unknown[] }).models).toEqual([
|
|
122
|
+
{
|
|
123
|
+
id: "kimi-code/kimi-for-coding",
|
|
124
|
+
source: "config",
|
|
125
|
+
isDefault: true,
|
|
126
|
+
provider: "managed:kimi-code",
|
|
127
|
+
displayName: "Kimi-k2.6",
|
|
128
|
+
contextLength: 262144,
|
|
129
|
+
capabilities: ["thinking", "image_in"],
|
|
130
|
+
metadata: { model: "kimi-for-coding" },
|
|
131
|
+
parameters: [
|
|
132
|
+
{
|
|
133
|
+
id: "thinking",
|
|
134
|
+
displayName: "Thinking",
|
|
135
|
+
type: "boolean",
|
|
136
|
+
flag: "--thinking/--no-thinking",
|
|
137
|
+
source: "cli",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
} finally {
|
|
143
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
144
|
+
else process.env.HOME = prevHome;
|
|
145
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
146
|
+
}
|
|
88
147
|
});
|
|
89
148
|
|
|
90
149
|
it("omits optional fields rather than emitting explicit undefineds", () => {
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
discoverRuntimeModelCatalog,
|
|
7
|
+
parseCodexModelCatalog,
|
|
8
|
+
parseDeepseekModelList,
|
|
9
|
+
parseKimiConfigModels,
|
|
10
|
+
parseKimiRuntimeParameters,
|
|
11
|
+
} from "../runtime-models.js";
|
|
12
|
+
|
|
13
|
+
describe("runtime model discovery parsers", () => {
|
|
14
|
+
it("parses visible Codex models and trims heavyweight catalog fields", () => {
|
|
15
|
+
const models = parseCodexModelCatalog(
|
|
16
|
+
JSON.stringify({
|
|
17
|
+
models: [
|
|
18
|
+
{
|
|
19
|
+
slug: "gpt-5.5",
|
|
20
|
+
display_name: "GPT-5.5",
|
|
21
|
+
visibility: "list",
|
|
22
|
+
supported_in_api: true,
|
|
23
|
+
default_reasoning_level: "medium",
|
|
24
|
+
supported_reasoning_levels: [{ effort: "low" }, { effort: "medium" }],
|
|
25
|
+
base_instructions: "large prompt text should not be copied into metadata",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
slug: "internal-hidden",
|
|
29
|
+
display_name: "Internal",
|
|
30
|
+
visibility: "hide",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(models).toEqual([
|
|
37
|
+
{
|
|
38
|
+
id: "gpt-5.5",
|
|
39
|
+
displayName: "GPT-5.5",
|
|
40
|
+
provider: "openai",
|
|
41
|
+
source: "cli",
|
|
42
|
+
metadata: {
|
|
43
|
+
supportedInApi: true,
|
|
44
|
+
defaultReasoningLevel: "medium",
|
|
45
|
+
supportedReasoningLevels: ["low", "medium"],
|
|
46
|
+
},
|
|
47
|
+
parameters: [
|
|
48
|
+
{
|
|
49
|
+
id: "reasoning_effort",
|
|
50
|
+
displayName: "Reasoning effort",
|
|
51
|
+
type: "enum",
|
|
52
|
+
flag: "-c model_reasoning_effort=<value>",
|
|
53
|
+
values: ["low", "medium"],
|
|
54
|
+
defaultValue: "medium",
|
|
55
|
+
source: "cli",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("parses Codex CLI cache descriptions", () => {
|
|
63
|
+
expect(
|
|
64
|
+
parseCodexModelCatalog(
|
|
65
|
+
JSON.stringify({
|
|
66
|
+
models: [
|
|
67
|
+
{
|
|
68
|
+
slug: "gpt-cache",
|
|
69
|
+
description: "Cached GPT",
|
|
70
|
+
visibility: "list",
|
|
71
|
+
supported_in_api: true,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
).toEqual([
|
|
77
|
+
{
|
|
78
|
+
id: "gpt-cache",
|
|
79
|
+
displayName: "Cached GPT",
|
|
80
|
+
provider: "openai",
|
|
81
|
+
source: "cli",
|
|
82
|
+
metadata: { supportedInApi: true },
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("uses the Codex CLI model cache when live discovery is unavailable", () => {
|
|
88
|
+
const tmp = mkdtempSync(path.join(tmpdir(), "daemon-codex-cache-"));
|
|
89
|
+
const prevHome = process.env.HOME;
|
|
90
|
+
const prevCodexHome = process.env.CODEX_HOME;
|
|
91
|
+
try {
|
|
92
|
+
const codexHome = path.join(tmp, "codex-home");
|
|
93
|
+
mkdirSync(codexHome, { recursive: true });
|
|
94
|
+
process.env.HOME = path.join(tmp, "home");
|
|
95
|
+
process.env.CODEX_HOME = codexHome;
|
|
96
|
+
writeFileSync(
|
|
97
|
+
path.join(codexHome, "models_cache.json"),
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
models: [
|
|
100
|
+
{
|
|
101
|
+
slug: "gpt-cache",
|
|
102
|
+
description: "Cached GPT",
|
|
103
|
+
visibility: "list",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const catalog = discoverRuntimeModelCatalog({
|
|
110
|
+
id: "codex",
|
|
111
|
+
displayName: "Codex",
|
|
112
|
+
binary: "codex",
|
|
113
|
+
supportsRun: true,
|
|
114
|
+
result: { available: true, path: path.join(tmp, "missing-codex") },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(catalog.models).toEqual([
|
|
118
|
+
{
|
|
119
|
+
id: "gpt-cache",
|
|
120
|
+
displayName: "Cached GPT",
|
|
121
|
+
provider: "openai",
|
|
122
|
+
source: "cli",
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
} finally {
|
|
126
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
127
|
+
else process.env.HOME = prevHome;
|
|
128
|
+
if (prevCodexHome === undefined) delete process.env.CODEX_HOME;
|
|
129
|
+
else process.env.CODEX_HOME = prevCodexHome;
|
|
130
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("falls back to built-in Codex models and persists the runtime catalog cache", () => {
|
|
135
|
+
const tmp = mkdtempSync(path.join(tmpdir(), "daemon-runtime-catalog-"));
|
|
136
|
+
const prevHome = process.env.HOME;
|
|
137
|
+
const prevCodexHome = process.env.CODEX_HOME;
|
|
138
|
+
const prevCacheDir = process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR;
|
|
139
|
+
try {
|
|
140
|
+
const codexHome = path.join(tmp, "codex-home");
|
|
141
|
+
const cacheDir = path.join(tmp, "catalog-cache");
|
|
142
|
+
mkdirSync(codexHome, { recursive: true });
|
|
143
|
+
process.env.HOME = path.join(tmp, "home");
|
|
144
|
+
process.env.CODEX_HOME = codexHome;
|
|
145
|
+
process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR = cacheDir;
|
|
146
|
+
|
|
147
|
+
const catalog = discoverRuntimeModelCatalog({
|
|
148
|
+
id: "codex",
|
|
149
|
+
displayName: "Codex",
|
|
150
|
+
binary: "codex",
|
|
151
|
+
supportsRun: true,
|
|
152
|
+
result: { available: true, path: path.join(tmp, "missing-codex") },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(catalog.models?.map((m) => m.id)).toContain("gpt-5.2");
|
|
156
|
+
expect(readdirSync(cacheDir)).toEqual(["codex.json"]);
|
|
157
|
+
const payload = JSON.parse(readFileSync(path.join(cacheDir, "codex.json"), "utf8"));
|
|
158
|
+
expect(payload.runtimeId).toBe("codex");
|
|
159
|
+
expect(payload.catalog.models.map((m: { id: string }) => m.id)).toContain("gpt-5.2");
|
|
160
|
+
} finally {
|
|
161
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
162
|
+
else process.env.HOME = prevHome;
|
|
163
|
+
if (prevCodexHome === undefined) delete process.env.CODEX_HOME;
|
|
164
|
+
else process.env.CODEX_HOME = prevCodexHome;
|
|
165
|
+
if (prevCacheDir === undefined) delete process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR;
|
|
166
|
+
else process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR = prevCacheDir;
|
|
167
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("parses DeepSeek model list output", () => {
|
|
172
|
+
expect(
|
|
173
|
+
parseDeepseekModelList(
|
|
174
|
+
[
|
|
175
|
+
"deepseek-v4-pro (deepseek)",
|
|
176
|
+
"gpt-4.1-mini (openai)",
|
|
177
|
+
"deepseek-coder:1.3b (ollama)",
|
|
178
|
+
].join("\n"),
|
|
179
|
+
),
|
|
180
|
+
).toEqual([
|
|
181
|
+
{
|
|
182
|
+
id: "deepseek-v4-pro",
|
|
183
|
+
displayName: "deepseek-v4-pro",
|
|
184
|
+
provider: "deepseek",
|
|
185
|
+
source: "cli",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "gpt-4.1-mini",
|
|
189
|
+
displayName: "gpt-4.1-mini",
|
|
190
|
+
provider: "openai",
|
|
191
|
+
source: "cli",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: "deepseek-coder:1.3b",
|
|
195
|
+
displayName: "deepseek-coder:1.3b",
|
|
196
|
+
provider: "ollama",
|
|
197
|
+
source: "cli",
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("parses Kimi models from config.toml", () => {
|
|
203
|
+
expect(
|
|
204
|
+
parseKimiConfigModels(
|
|
205
|
+
[
|
|
206
|
+
'default_model = "kimi-code/kimi-for-coding"',
|
|
207
|
+
"default_thinking = true",
|
|
208
|
+
"",
|
|
209
|
+
'[models."kimi-code/kimi-for-coding"]',
|
|
210
|
+
'provider = "managed:kimi-code"',
|
|
211
|
+
'model = "kimi-for-coding"',
|
|
212
|
+
"max_context_size = 262144",
|
|
213
|
+
'capabilities = ["thinking", "video_in", "image_in"]',
|
|
214
|
+
'display_name = "Kimi-k2.6"',
|
|
215
|
+
].join("\n"),
|
|
216
|
+
),
|
|
217
|
+
).toEqual([
|
|
218
|
+
{
|
|
219
|
+
id: "kimi-code/kimi-for-coding",
|
|
220
|
+
source: "config",
|
|
221
|
+
isDefault: true,
|
|
222
|
+
provider: "managed:kimi-code",
|
|
223
|
+
displayName: "Kimi-k2.6",
|
|
224
|
+
contextLength: 262144,
|
|
225
|
+
capabilities: ["thinking", "video_in", "image_in"],
|
|
226
|
+
metadata: { model: "kimi-for-coding" },
|
|
227
|
+
parameters: [
|
|
228
|
+
{
|
|
229
|
+
id: "thinking",
|
|
230
|
+
displayName: "Thinking",
|
|
231
|
+
type: "boolean",
|
|
232
|
+
flag: "--thinking/--no-thinking",
|
|
233
|
+
defaultValue: true,
|
|
234
|
+
source: "config",
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("parses Kimi runtime parameters from config.toml", () => {
|
|
242
|
+
expect(
|
|
243
|
+
parseKimiRuntimeParameters(
|
|
244
|
+
[
|
|
245
|
+
'default_model = "kimi-code/kimi-for-coding"',
|
|
246
|
+
"default_thinking = true",
|
|
247
|
+
"default_yolo = false",
|
|
248
|
+
"default_plan_mode = false",
|
|
249
|
+
"show_thinking_stream = true",
|
|
250
|
+
"max_steps_per_turn = 1000",
|
|
251
|
+
"max_retries_per_step = 3",
|
|
252
|
+
"max_ralph_iterations = 0",
|
|
253
|
+
"reserved_context_size = 50000",
|
|
254
|
+
].join("\n"),
|
|
255
|
+
),
|
|
256
|
+
).toEqual([
|
|
257
|
+
{
|
|
258
|
+
id: "model",
|
|
259
|
+
displayName: "Default model",
|
|
260
|
+
type: "string",
|
|
261
|
+
flag: "-m, --model",
|
|
262
|
+
defaultValue: "kimi-code/kimi-for-coding",
|
|
263
|
+
source: "config",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "thinking",
|
|
267
|
+
displayName: "Thinking",
|
|
268
|
+
type: "boolean",
|
|
269
|
+
flag: "--thinking/--no-thinking",
|
|
270
|
+
defaultValue: true,
|
|
271
|
+
source: "config",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "show_thinking_stream",
|
|
275
|
+
displayName: "Show thinking stream",
|
|
276
|
+
type: "boolean",
|
|
277
|
+
defaultValue: true,
|
|
278
|
+
source: "config",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "yolo",
|
|
282
|
+
displayName: "Auto approve",
|
|
283
|
+
type: "boolean",
|
|
284
|
+
flag: "--yolo, --yes, -y",
|
|
285
|
+
defaultValue: false,
|
|
286
|
+
source: "config",
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "plan_mode",
|
|
290
|
+
displayName: "Plan mode",
|
|
291
|
+
type: "boolean",
|
|
292
|
+
flag: "--plan",
|
|
293
|
+
defaultValue: false,
|
|
294
|
+
source: "config",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: "max_steps_per_turn",
|
|
298
|
+
displayName: "Max steps per turn",
|
|
299
|
+
type: "integer",
|
|
300
|
+
flag: "--max-steps-per-turn",
|
|
301
|
+
defaultValue: 1000,
|
|
302
|
+
minimum: 1,
|
|
303
|
+
source: "config",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: "max_retries_per_step",
|
|
307
|
+
displayName: "Max retries per step",
|
|
308
|
+
type: "integer",
|
|
309
|
+
flag: "--max-retries-per-step",
|
|
310
|
+
defaultValue: 3,
|
|
311
|
+
minimum: 1,
|
|
312
|
+
source: "config",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: "max_ralph_iterations",
|
|
316
|
+
displayName: "Max Ralph iterations",
|
|
317
|
+
type: "integer",
|
|
318
|
+
flag: "--max-ralph-iterations",
|
|
319
|
+
defaultValue: 0,
|
|
320
|
+
minimum: -1,
|
|
321
|
+
source: "config",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "reserved_context_size",
|
|
325
|
+
displayName: "Reserved context size",
|
|
326
|
+
type: "integer",
|
|
327
|
+
defaultValue: 50000,
|
|
328
|
+
minimum: 0,
|
|
329
|
+
source: "config",
|
|
330
|
+
},
|
|
331
|
+
]);
|
|
332
|
+
});
|
|
333
|
+
});
|
package/src/agent-discovery.ts
CHANGED
|
@@ -36,6 +36,12 @@ export interface DiscoveredAgentCredential {
|
|
|
36
36
|
* in that case.
|
|
37
37
|
*/
|
|
38
38
|
runtime?: string;
|
|
39
|
+
/** Runtime model id/alias selected for this agent. */
|
|
40
|
+
runtimeModel?: string;
|
|
41
|
+
/** Runtime reasoning effort selected for this agent. */
|
|
42
|
+
reasoningEffort?: string;
|
|
43
|
+
/** Kimi-style thinking toggle selected for this agent. */
|
|
44
|
+
thinking?: boolean;
|
|
39
45
|
/** Working directory cached alongside `runtime`. */
|
|
40
46
|
cwd?: string;
|
|
41
47
|
/** OpenClaw gateway profile name from credentials (only meaningful for openclaw-acp). */
|
|
@@ -181,6 +187,9 @@ export function discoverAgentCredentials(
|
|
|
181
187
|
};
|
|
182
188
|
if (creds.displayName) entry.displayName = creds.displayName;
|
|
183
189
|
if (creds.runtime) entry.runtime = creds.runtime;
|
|
190
|
+
if (creds.runtimeModel) entry.runtimeModel = creds.runtimeModel;
|
|
191
|
+
if (creds.reasoningEffort) entry.reasoningEffort = creds.reasoningEffort;
|
|
192
|
+
if (typeof creds.thinking === "boolean") entry.thinking = creds.thinking;
|
|
184
193
|
if (creds.cwd) entry.cwd = creds.cwd;
|
|
185
194
|
if (creds.openclawGateway) entry.openclawGateway = creds.openclawGateway;
|
|
186
195
|
if (creds.openclawAgent) entry.openclawAgent = creds.openclawAgent;
|
package/src/daemon-config-map.ts
CHANGED
|
@@ -18,10 +18,20 @@ import type {
|
|
|
18
18
|
import { resolveAgentIds } from "./config.js";
|
|
19
19
|
import { agentWorkspaceDir } from "./agent-workspace.js";
|
|
20
20
|
import { log as daemonLog } from "./log.js";
|
|
21
|
+
import {
|
|
22
|
+
buildRuntimeSelectionExtraArgs,
|
|
23
|
+
mergeRuntimeExtraArgs,
|
|
24
|
+
} from "./runtime-route-options.js";
|
|
21
25
|
|
|
22
26
|
/** Per-agent metadata cached from credentials, used by `buildManagedRoutes`. */
|
|
23
27
|
export interface AgentRuntimeMeta {
|
|
24
28
|
runtime?: string;
|
|
29
|
+
/** Runtime model id/alias selected for this agent. */
|
|
30
|
+
runtimeModel?: string;
|
|
31
|
+
/** Runtime reasoning effort selected for this agent. */
|
|
32
|
+
reasoningEffort?: string;
|
|
33
|
+
/** Kimi-style thinking toggle selected for this agent. */
|
|
34
|
+
thinking?: boolean;
|
|
25
35
|
cwd?: string;
|
|
26
36
|
/** OpenClaw gateway profile name to lookup in the registry. */
|
|
27
37
|
openclawGateway?: string;
|
|
@@ -346,10 +356,13 @@ export function buildManagedRoutes(
|
|
|
346
356
|
match: { accountId: agentId },
|
|
347
357
|
runtime,
|
|
348
358
|
cwd: meta.cwd || agentWorkspaceDir(agentId),
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
359
|
+
...(() => {
|
|
360
|
+
const extraArgs = mergeRuntimeExtraArgs(
|
|
361
|
+
defaultRoute.extraArgs,
|
|
362
|
+
buildRuntimeSelectionExtraArgs(runtime, meta),
|
|
363
|
+
);
|
|
364
|
+
return extraArgs ? { extraArgs } : {};
|
|
365
|
+
})(),
|
|
353
366
|
};
|
|
354
367
|
if (runtime === "openclaw-acp") {
|
|
355
368
|
// Per RFC §3.4: prefer credentials, fall back to defaultRoute.gateway.
|