@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.
@@ -22,6 +22,12 @@ export interface DiscoveredAgentCredential {
22
22
  * in that case.
23
23
  */
24
24
  runtime?: string;
25
+ /** Runtime model id/alias selected for this agent. */
26
+ runtimeModel?: string;
27
+ /** Runtime reasoning effort selected for this agent. */
28
+ reasoningEffort?: string;
29
+ /** Kimi-style thinking toggle selected for this agent. */
30
+ thinking?: boolean;
25
31
  /** Working directory cached alongside `runtime`. */
26
32
  cwd?: string;
27
33
  /** OpenClaw gateway profile name from credentials (only meaningful for openclaw-acp). */
@@ -96,6 +96,12 @@ export function discoverAgentCredentials(opts = {}) {
96
96
  entry.displayName = creds.displayName;
97
97
  if (creds.runtime)
98
98
  entry.runtime = creds.runtime;
99
+ if (creds.runtimeModel)
100
+ entry.runtimeModel = creds.runtimeModel;
101
+ if (creds.reasoningEffort)
102
+ entry.reasoningEffort = creds.reasoningEffort;
103
+ if (typeof creds.thinking === "boolean")
104
+ entry.thinking = creds.thinking;
99
105
  if (creds.cwd)
100
106
  entry.cwd = creds.cwd;
101
107
  if (creds.openclawGateway)
@@ -3,6 +3,12 @@ import type { DaemonConfig, OpenclawGatewayProfile } from "./config.js";
3
3
  /** Per-agent metadata cached from credentials, used by `buildManagedRoutes`. */
4
4
  export interface AgentRuntimeMeta {
5
5
  runtime?: string;
6
+ /** Runtime model id/alias selected for this agent. */
7
+ runtimeModel?: string;
8
+ /** Runtime reasoning effort selected for this agent. */
9
+ reasoningEffort?: string;
10
+ /** Kimi-style thinking toggle selected for this agent. */
11
+ thinking?: boolean;
6
12
  cwd?: string;
7
13
  /** OpenClaw gateway profile name to lookup in the registry. */
8
14
  openclawGateway?: string;
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { resolveAgentIds } from "./config.js";
5
5
  import { agentWorkspaceDir } from "./agent-workspace.js";
6
6
  import { log as daemonLog } from "./log.js";
7
+ import { buildRuntimeSelectionExtraArgs, mergeRuntimeExtraArgs, } from "./runtime-route-options.js";
7
8
  function expandHome(p) {
8
9
  if (p === "~")
9
10
  return homedir();
@@ -259,10 +260,10 @@ export function buildManagedRoutes(agentIds, agentRuntimes, defaultRoute, opencl
259
260
  match: { accountId: agentId },
260
261
  runtime,
261
262
  cwd: meta.cwd || agentWorkspaceDir(agentId),
262
- // Inherit defaultRoute's extraArgs so synthesized per-agent routes
263
- // pick up operator-wide flags (e.g. `--permission-mode bypassPermissions`)
264
- // that would otherwise apply only to agents listed in `cfg.routes[]`.
265
- ...(defaultRoute.extraArgs ? { extraArgs: defaultRoute.extraArgs.slice() } : {}),
263
+ ...(() => {
264
+ const extraArgs = mergeRuntimeExtraArgs(defaultRoute.extraArgs, buildRuntimeSelectionExtraArgs(runtime, meta));
265
+ return extraArgs ? { extraArgs } : {};
266
+ })(),
266
267
  };
267
268
  if (runtime === "openclaw-acp") {
268
269
  // Per RFC §3.4: prefer credentials, fall back to defaultRoute.gateway.
package/dist/daemon.d.ts CHANGED
@@ -121,6 +121,9 @@ export interface BootBackfillResult {
121
121
  credentialPathByAgentId: Map<string, string>;
122
122
  agentRuntimes: Record<string, {
123
123
  runtime?: string;
124
+ runtimeModel?: string;
125
+ reasoningEffort?: string;
126
+ thinking?: boolean;
124
127
  cwd?: string;
125
128
  openclawGateway?: string;
126
129
  openclawAgent?: string;
package/dist/daemon.js CHANGED
@@ -527,9 +527,19 @@ export function backfillBootAgents(agents, opts) {
527
527
  for (const a of agents) {
528
528
  if (a.credentialsFile)
529
529
  credentialPathByAgentId.set(a.agentId, a.credentialsFile);
530
- if (a.runtime || a.cwd || a.openclawGateway || a.openclawAgent || a.hermesProfile) {
530
+ if (a.runtime ||
531
+ a.runtimeModel ||
532
+ a.reasoningEffort ||
533
+ typeof a.thinking === "boolean" ||
534
+ a.cwd ||
535
+ a.openclawGateway ||
536
+ a.openclawAgent ||
537
+ a.hermesProfile) {
531
538
  agentRuntimes[a.agentId] = {
532
539
  ...(a.runtime ? { runtime: a.runtime } : {}),
540
+ ...(a.runtimeModel ? { runtimeModel: a.runtimeModel } : {}),
541
+ ...(a.reasoningEffort ? { reasoningEffort: a.reasoningEffort } : {}),
542
+ ...(typeof a.thinking === "boolean" ? { thinking: a.thinking } : {}),
533
543
  ...(a.cwd ? { cwd: a.cwd } : {}),
534
544
  ...(a.openclawGateway ? { openclawGateway: a.openclawGateway } : {}),
535
545
  ...(a.openclawAgent ? { openclawAgent: a.openclawAgent } : {}),
@@ -57,11 +57,12 @@ function defaultClientFactory(input) {
57
57
  function isOwnerTrust(msg) {
58
58
  if (msg.room_id?.startsWith(OWNER_CHAT_PREFIX))
59
59
  return true;
60
- if (msg.source_type === "dashboard_user_chat")
60
+ const sourceType = msg.source_type;
61
+ if (sourceType === "dashboard_user_chat")
61
62
  return true;
62
63
  // Cloud Agent run tasks are Hub-issued on the user's behalf, same
63
64
  // trust posture as owner chat.
64
- if (msg.source_type === "cloud_agent_run")
65
+ if (sourceType === "cloud_agent_run")
65
66
  return true;
66
67
  return false;
67
68
  }
@@ -100,9 +101,10 @@ function normalizeInbox(msg, options) {
100
101
  // run completes). All other envelope types (notification, system,
101
102
  // contact_added/removed, …) are still filtered out — they belong in
102
103
  // a separate push-notification path that daemon does not yet implement.
103
- if (env.type !== "message" &&
104
- env.type !== "contact_request" &&
105
- env.type !== "cloud_run")
104
+ const envType = env.type;
105
+ if (envType !== "message" &&
106
+ envType !== "contact_request" &&
107
+ envType !== "cloud_run")
106
108
  return null;
107
109
  if (!msg.room_id)
108
110
  return null;
@@ -113,7 +115,8 @@ function normalizeInbox(msg, options) {
113
115
  const text = ownerTrust ? rawText : sanitizeUntrustedContent(rawText);
114
116
  const isDm = msg.room_id.startsWith(DM_ROOM_PREFIX);
115
117
  const isOwnerChat = msg.room_id.startsWith(OWNER_CHAT_PREFIX);
116
- const senderKind = ownerTrust || msg.source_type === "dashboard_human_room" ? "user" : "agent";
118
+ const sourceType = msg.source_type;
119
+ const senderKind = ownerTrust || sourceType === "dashboard_human_room" ? "user" : "agent";
117
120
  const senderName = msg.source_user_name ?? undefined;
118
121
  const threadId = msg.topic_id ?? msg.topic ?? null;
119
122
  const streamable = isOwnerChat;
@@ -860,55 +863,29 @@ function normalizeBlockForHub(block, seq) {
860
863
  return { kind: "assistant", seq, payload: { text } };
861
864
  }
862
865
  if (kind === "tool_use") {
863
- // Claude Code: assistant message w/ content[].type === "tool_use" {id,name,input}
864
- // Codex: item.started for command_execution, file_change, mcp_tool_call, web_search
865
- const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
866
- const tu = contents.find((c) => c?.type === "tool_use");
867
- if (tu) {
868
- payload.name = typeof tu.name === "string" ? tu.name : "tool";
869
- if (tu.input && typeof tu.input === "object")
870
- payload.params = tu.input;
871
- if (typeof tu.id === "string")
872
- payload.id = tu.id;
873
- }
874
- else if (raw?.item && typeof raw.item === "object") {
875
- payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
876
- const params = codexToolParams(raw.item);
877
- if (Object.keys(params).length > 0)
878
- payload.params = params;
879
- if (typeof raw.item.id === "string")
880
- payload.id = raw.item.id;
881
- if (typeof raw.item.status === "string")
882
- payload.status = raw.item.status;
866
+ // Claude Code, Codex, DeepSeek TUI, Kimi, and ACP all expose tool calls
867
+ // with slightly different field names. Preserve the real invocation input
868
+ // so the dashboard can show more than a bare "tool" label.
869
+ const call = extractToolCall(raw);
870
+ if (call) {
871
+ payload.name = call.name;
872
+ if (call.params !== undefined && !isEmptyRecord(call.params))
873
+ payload.params = call.params;
874
+ if (call.id)
875
+ payload.id = call.id;
876
+ if (call.status)
877
+ payload.status = call.status;
883
878
  }
884
879
  return { kind: "tool_call", seq, payload };
885
880
  }
886
881
  if (kind === "tool_result") {
887
- // Claude Code: {type:"user", message:{content:[{type:"tool_result",tool_use_id,content}]}}
888
- // Codex: item.completed for command_execution, file_change, mcp_tool_call, web_search
889
- const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
890
- const tr = contents.find((c) => c?.type === "tool_result");
891
- if (tr) {
892
- let resultStr = "";
893
- if (typeof tr.content === "string") {
894
- resultStr = tr.content;
895
- }
896
- else if (Array.isArray(tr.content)) {
897
- resultStr = tr.content
898
- .map((c) => (typeof c?.text === "string" ? c.text : JSON.stringify(c)))
899
- .join("\n");
900
- }
901
- payload.result = resultStr;
902
- if (typeof tr.tool_use_id === "string")
903
- payload.tool_use_id = tr.tool_use_id;
904
- }
905
- else if (raw?.item && typeof raw.item === "object") {
906
- payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
907
- if (typeof raw.item.id === "string")
908
- payload.tool_use_id = raw.item.id;
909
- const result = codexToolResult(raw.item);
910
- if (result)
911
- payload.result = result;
882
+ const result = extractToolResult(raw);
883
+ if (result) {
884
+ if (result.name)
885
+ payload.name = result.name;
886
+ payload.result = result.result;
887
+ if (result.id)
888
+ payload.tool_use_id = result.id;
912
889
  }
913
890
  return { kind: "tool_result", seq, payload };
914
891
  }
@@ -965,6 +942,168 @@ function isTerminalRuntimeBlock(raw) {
965
942
  terminal === "turn.done" ||
966
943
  terminal === "done");
967
944
  }
945
+ function extractToolCall(raw) {
946
+ const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
947
+ const tu = contents.find((c) => c?.type === "tool_use");
948
+ if (tu) {
949
+ return {
950
+ name: stringField(tu, "name") ?? "tool",
951
+ params: parseMaybeJson(tu.input ?? tu.arguments),
952
+ id: stringField(tu, "id"),
953
+ };
954
+ }
955
+ const deepseek = extractDeepseekToolCall(raw);
956
+ if (deepseek)
957
+ return deepseek;
958
+ const item = raw?.item;
959
+ if (item && typeof item === "object") {
960
+ const params = codexToolParams(item);
961
+ return {
962
+ name: stringField(item, "type") ?? stringField(item, "name") ?? "tool",
963
+ params,
964
+ id: stringField(item, "id"),
965
+ status: stringField(item, "status"),
966
+ };
967
+ }
968
+ const toolCalls = Array.isArray(raw?.tool_calls) ? raw.tool_calls : [];
969
+ const toolCall = toolCalls.find((t) => t && typeof t === "object");
970
+ if (toolCall) {
971
+ const fn = toolCall.function && typeof toolCall.function === "object" ? toolCall.function : undefined;
972
+ return {
973
+ name: stringField(fn, "name") ?? stringField(toolCall, "name") ?? "tool",
974
+ params: parseMaybeJson(fn?.arguments ?? toolCall.arguments ?? toolCall.input ?? toolCall.rawInput),
975
+ id: stringField(toolCall, "id"),
976
+ };
977
+ }
978
+ const update = raw?.params?.update ?? raw?.update;
979
+ const acpTool = update?.toolCall ?? update?.tool_call ?? update?.tool;
980
+ if (acpTool && typeof acpTool === "object") {
981
+ return {
982
+ name: stringField(acpTool, "name") ?? stringField(update, "name") ?? "tool",
983
+ params: parseMaybeJson(acpTool.rawInput ??
984
+ acpTool.raw_input ??
985
+ acpTool.input ??
986
+ acpTool.arguments ??
987
+ acpTool.args ??
988
+ acpTool.params) ?? acpTool,
989
+ id: stringField(acpTool, "id") ?? stringField(update, "toolCallId"),
990
+ };
991
+ }
992
+ return null;
993
+ }
994
+ function extractToolResult(raw) {
995
+ const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
996
+ const tr = contents.find((c) => c?.type === "tool_result");
997
+ if (tr) {
998
+ return {
999
+ result: stringifyToolResult(tr.content),
1000
+ id: stringField(tr, "tool_use_id"),
1001
+ };
1002
+ }
1003
+ const deepseek = extractDeepseekToolResult(raw);
1004
+ if (deepseek)
1005
+ return deepseek;
1006
+ const item = raw?.item;
1007
+ if (item && typeof item === "object") {
1008
+ const result = codexToolResult(item);
1009
+ return {
1010
+ name: stringField(item, "type") ?? stringField(item, "name"),
1011
+ result: result || stringifyToolResult(item),
1012
+ id: stringField(item, "id"),
1013
+ };
1014
+ }
1015
+ if (raw?.role === "tool") {
1016
+ return {
1017
+ result: stringifyToolResult(raw.content),
1018
+ id: stringField(raw, "tool_call_id"),
1019
+ };
1020
+ }
1021
+ const update = raw?.params?.update ?? raw?.update;
1022
+ const acpTool = update?.toolCall ?? update?.tool_call ?? update?.tool;
1023
+ if (acpTool && typeof acpTool === "object") {
1024
+ const result = acpTool.output ??
1025
+ acpTool.result ??
1026
+ acpTool.content ??
1027
+ acpTool.error ??
1028
+ update.content ??
1029
+ update;
1030
+ return {
1031
+ name: stringField(acpTool, "name") ?? stringField(update, "name"),
1032
+ result: stringifyToolResult(result),
1033
+ id: stringField(acpTool, "id") ?? stringField(update, "toolCallId"),
1034
+ };
1035
+ }
1036
+ return null;
1037
+ }
1038
+ function extractDeepseekToolCall(raw) {
1039
+ const payload = raw?.payload;
1040
+ if (!payload || typeof payload !== "object")
1041
+ return null;
1042
+ if (raw?.event === "tool.started") {
1043
+ const tool = payload.tool && typeof payload.tool === "object" ? payload.tool : undefined;
1044
+ return {
1045
+ name: stringField(payload, "name") ?? stringField(tool, "name") ?? "tool",
1046
+ params: parseMaybeJson(payload.input ?? payload.arguments ?? payload.params ?? tool?.input ?? tool?.rawInput),
1047
+ id: stringField(payload, "id") ?? stringField(tool, "id"),
1048
+ status: stringField(payload, "status") ?? stringField(tool, "status"),
1049
+ };
1050
+ }
1051
+ if (payload.event === "item.started") {
1052
+ const inner = payload.payload && typeof payload.payload === "object" ? payload.payload : {};
1053
+ const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
1054
+ const tool = inner.tool && typeof inner.tool === "object" ? inner.tool : item?.tool;
1055
+ return {
1056
+ name: stringField(tool, "name") ??
1057
+ stringField(inner, "name") ??
1058
+ stringField(item, "name") ??
1059
+ stringField(item, "type") ??
1060
+ "tool",
1061
+ params: parseMaybeJson(tool?.input ??
1062
+ tool?.rawInput ??
1063
+ tool?.arguments ??
1064
+ inner.input ??
1065
+ item?.input ??
1066
+ item?.arguments) ?? tool ?? item,
1067
+ id: stringField(tool, "id") ?? stringField(inner, "id") ?? stringField(item, "id"),
1068
+ status: stringField(tool, "status") ?? stringField(inner, "status") ?? stringField(item, "status"),
1069
+ };
1070
+ }
1071
+ return null;
1072
+ }
1073
+ function extractDeepseekToolResult(raw) {
1074
+ const payload = raw?.payload;
1075
+ if (!payload || typeof payload !== "object")
1076
+ return null;
1077
+ if (raw?.event === "tool.completed") {
1078
+ const result = payload.output ?? payload.result ?? payload.content ?? payload.error ?? payload;
1079
+ return {
1080
+ name: stringField(payload, "name"),
1081
+ result: stringifyToolResult(result),
1082
+ id: stringField(payload, "id"),
1083
+ };
1084
+ }
1085
+ if (payload.event === "item.completed" || payload.event === "item.failed") {
1086
+ const inner = payload.payload && typeof payload.payload === "object" ? payload.payload : {};
1087
+ const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
1088
+ const result = item?.output ??
1089
+ item?.result ??
1090
+ item?.content ??
1091
+ item?.detail ??
1092
+ item?.summary ??
1093
+ item?.error ??
1094
+ inner.output ??
1095
+ inner.result ??
1096
+ inner.error ??
1097
+ item ??
1098
+ inner;
1099
+ return {
1100
+ name: stringField(item, "name") ?? stringField(item, "type") ?? stringField(inner, "name"),
1101
+ result: stringifyToolResult(result),
1102
+ id: stringField(item, "id") ?? stringField(inner, "id"),
1103
+ };
1104
+ }
1105
+ return null;
1106
+ }
968
1107
  function formatBlockDetails(raw) {
969
1108
  if (!raw || typeof raw !== "object")
970
1109
  return "";
@@ -1034,6 +1173,52 @@ function codexToolResult(item) {
1034
1173
  }
1035
1174
  return parts.join("\n");
1036
1175
  }
1176
+ function stringifyToolResult(value) {
1177
+ if (value == null)
1178
+ return "";
1179
+ if (typeof value === "string")
1180
+ return value;
1181
+ if (Array.isArray(value)) {
1182
+ return value
1183
+ .map((c) => {
1184
+ if (typeof c === "string")
1185
+ return c;
1186
+ if (typeof c?.text === "string")
1187
+ return c.text;
1188
+ return stringifyToolResult(c);
1189
+ })
1190
+ .filter(Boolean)
1191
+ .join("\n");
1192
+ }
1193
+ try {
1194
+ return JSON.stringify(value, null, 2);
1195
+ }
1196
+ catch {
1197
+ return String(value);
1198
+ }
1199
+ }
1200
+ function parseMaybeJson(value) {
1201
+ if (typeof value !== "string")
1202
+ return value;
1203
+ const trimmed = value.trim();
1204
+ if (!trimmed)
1205
+ return value;
1206
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
1207
+ return value;
1208
+ try {
1209
+ return JSON.parse(trimmed);
1210
+ }
1211
+ catch {
1212
+ return value;
1213
+ }
1214
+ }
1215
+ function isEmptyRecord(value) {
1216
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
1217
+ }
1218
+ function stringField(obj, key) {
1219
+ const value = obj?.[key];
1220
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1221
+ }
1037
1222
  function extractContentText(content) {
1038
1223
  if (!content)
1039
1224
  return "";
@@ -202,6 +202,11 @@ export class DeepseekTuiAdapter {
202
202
  auto_approve: opts.trustLevel !== "public",
203
203
  archived: false,
204
204
  };
205
+ const selection = parseDeepseekRuntimeSelection(opts.extraArgs);
206
+ if (selection.model)
207
+ body.model = selection.model;
208
+ if (selection.reasoningEffort)
209
+ body.reasoning_effort = selection.reasoningEffort;
205
210
  if (opts.systemContext)
206
211
  body.system_prompt = opts.systemContext;
207
212
  const res = await this.requestJson(`${baseUrl}/v1/threads`, {
@@ -236,16 +241,22 @@ export class DeepseekTuiAdapter {
236
241
  });
237
242
  let turnId = "";
238
243
  try {
244
+ const selection = parseDeepseekRuntimeSelection(opts.extraArgs);
245
+ const body = {
246
+ prompt: opts.text,
247
+ mode: "agent",
248
+ allow_shell: opts.trustLevel !== "public",
249
+ trust_mode: opts.trustLevel !== "public",
250
+ auto_approve: opts.trustLevel !== "public",
251
+ };
252
+ if (selection.model)
253
+ body.model = selection.model;
254
+ if (selection.reasoningEffort)
255
+ body.reasoning_effort = selection.reasoningEffort;
239
256
  const started = await this.requestJson(`${baseUrl}/v1/threads/${encodeURIComponent(threadId)}/turns`, {
240
257
  method: "POST",
241
258
  headers,
242
- body: JSON.stringify({
243
- prompt: opts.text,
244
- mode: "agent",
245
- allow_shell: opts.trustLevel !== "public",
246
- trust_mode: opts.trustLevel !== "public",
247
- auto_approve: opts.trustLevel !== "public",
248
- }),
259
+ body: JSON.stringify(body),
249
260
  signal,
250
261
  });
251
262
  turnId = stringField(started?.turn, "id") ?? stringField(started, "turn_id") ?? "";
@@ -457,6 +468,43 @@ function parseSseFrame(raw) {
457
468
  function authHeaders(token) {
458
469
  return token ? { authorization: `Bearer ${token}` } : {};
459
470
  }
471
+ function parseDeepseekRuntimeSelection(extraArgs) {
472
+ const out = {};
473
+ if (!extraArgs?.length)
474
+ return out;
475
+ for (let i = 0; i < extraArgs.length; i += 1) {
476
+ const arg = extraArgs[i];
477
+ if (arg === "--model") {
478
+ const value = nextArgValue(extraArgs, i);
479
+ if (value !== undefined) {
480
+ out.model = value;
481
+ i += 1;
482
+ }
483
+ }
484
+ else if (arg.startsWith("--model=")) {
485
+ out.model = arg.slice("--model=".length);
486
+ }
487
+ else if (arg === "--reasoning-effort") {
488
+ const value = nextArgValue(extraArgs, i);
489
+ if (value !== undefined) {
490
+ out.reasoningEffort = value;
491
+ i += 1;
492
+ }
493
+ }
494
+ else if (arg.startsWith("--reasoning-effort=")) {
495
+ out.reasoningEffort = arg.slice("--reasoning-effort=".length);
496
+ }
497
+ }
498
+ return out;
499
+ }
500
+ function nextArgValue(args, index) {
501
+ const next = args[index + 1];
502
+ if (typeof next !== "string")
503
+ return undefined;
504
+ if (!next.startsWith("-"))
505
+ return next;
506
+ return /^-\d/.test(next) ? next : undefined;
507
+ }
460
508
  function poolKey(opts) {
461
509
  return opts.accountId || "default";
462
510
  }
@@ -107,6 +107,8 @@ export declare function collectRuntimeSnapshot(opts?: {
107
107
  export declare function attachRuntimeHealth(snapshot: ListRuntimesResult, live: GatewayRuntimeSnapshot): ListRuntimesResult;
108
108
  /** Maximum number of `endpoints[]` entries persisted per runtime (RFC §3.8.2). */
109
109
  export declare const RUNTIME_ENDPOINTS_CAP = 32;
110
+ export declare const RUNTIME_MODELS_CAP = 128;
111
+ export declare const RUNTIME_PARAMETERS_CAP = 64;
110
112
  /** Injection seam for L2 + L3 endpoint probes — kept testable + side-effect-free. */
111
113
  export type WsEndpointProbeFn = (args: {
112
114
  url: string;
package/dist/provision.js CHANGED
@@ -18,6 +18,8 @@ import { hermesProfileHomeDir, isValidHermesProfileName, listHermesProfiles, } f
18
18
  import { log as daemonLog } from "./log.js";
19
19
  import { discoverAgentCredentials } from "./agent-discovery.js";
20
20
  import { resolveMemoryDir } from "./working-memory.js";
21
+ import { discoverRuntimeModelCatalog } from "./runtime-models.js";
22
+ import { buildRuntimeSelectionExtraArgs, mergeRuntimeExtraArgs, } from "./runtime-route-options.js";
21
23
  /**
22
24
  * Build a dispatcher function that routes a `ControlFrame` to the right
23
25
  * handler. Returned function signature matches
@@ -783,6 +785,9 @@ function upsertManagedRouteForCredentials(credentials, cfg, gateway) {
783
785
  runtime: credentials.runtime ?? cfg.defaultRoute.adapter,
784
786
  cwd: credentials.cwd ?? agentWorkspaceDir(credentials.agentId),
785
787
  };
788
+ const extraArgs = mergeRuntimeExtraArgs(cfg.defaultRoute.extraArgs, buildRuntimeSelectionExtraArgs(synthRoute.runtime, credentials));
789
+ if (extraArgs)
790
+ synthRoute.extraArgs = extraArgs;
786
791
  if (synthRoute.runtime === "openclaw-acp") {
787
792
  const profile = (cfg.openclawGateways ?? []).find((g) => g.name === credentials.openclawGateway);
788
793
  if (profile) {
@@ -887,6 +892,13 @@ async function materializeCredentials(params, cfg, ctx, explicitCwd) {
887
892
  record.tokenExpiresAt = c.tokenExpiresAt;
888
893
  if (runtime)
889
894
  record.runtime = runtime;
895
+ const runtimeSelection = pickRuntimeSelection(params);
896
+ if (runtimeSelection.runtimeModel)
897
+ record.runtimeModel = runtimeSelection.runtimeModel;
898
+ if (runtimeSelection.reasoningEffort)
899
+ record.reasoningEffort = runtimeSelection.reasoningEffort;
900
+ if (typeof runtimeSelection.thinking === "boolean")
901
+ record.thinking = runtimeSelection.thinking;
890
902
  record.cwd = cwd;
891
903
  const openclawSel = pickOpenclawSelection(params);
892
904
  if (openclawSel.gateway)
@@ -922,6 +934,13 @@ async function materializeCredentials(params, cfg, ctx, explicitCwd) {
922
934
  };
923
935
  if (runtime)
924
936
  record.runtime = runtime;
937
+ const runtimeSelection = pickRuntimeSelection(params);
938
+ if (runtimeSelection.runtimeModel)
939
+ record.runtimeModel = runtimeSelection.runtimeModel;
940
+ if (runtimeSelection.reasoningEffort)
941
+ record.reasoningEffort = runtimeSelection.reasoningEffort;
942
+ if (typeof runtimeSelection.thinking === "boolean")
943
+ record.thinking = runtimeSelection.thinking;
925
944
  record.cwd = cwd;
926
945
  const openclawSel = pickOpenclawSelection(params);
927
946
  if (openclawSel.gateway)
@@ -1449,6 +1468,12 @@ export function collectRuntimeSnapshot(opts = {}) {
1449
1468
  record.version = entry.result.version;
1450
1469
  if (entry.result.path)
1451
1470
  record.path = entry.result.path;
1471
+ const catalog = discoverRuntimeModelCatalog(entry);
1472
+ const models = catalog.models;
1473
+ if (models?.length)
1474
+ record.models = models.slice(0, RUNTIME_MODELS_CAP);
1475
+ if (catalog.parameters?.length)
1476
+ record.parameters = catalog.parameters.slice(0, RUNTIME_PARAMETERS_CAP);
1452
1477
  // Gateway's probe surface doesn't expose an `error` string today — it
1453
1478
  // already swallows throws into `{available: false}`. We leave the wire
1454
1479
  // field blank in that case and let callers treat `!available` as reason
@@ -1502,6 +1527,8 @@ export function attachRuntimeHealth(snapshot, live) {
1502
1527
  }
1503
1528
  /** Maximum number of `endpoints[]` entries persisted per runtime (RFC §3.8.2). */
1504
1529
  export const RUNTIME_ENDPOINTS_CAP = 32;
1530
+ export const RUNTIME_MODELS_CAP = 128;
1531
+ export const RUNTIME_PARAMETERS_CAP = 64;
1505
1532
  export function classifyOpenclawAuthError(message) {
1506
1533
  const text = (message ?? "").toLowerCase();
1507
1534
  if (!text)
@@ -2096,6 +2123,12 @@ function readAgentRuntimesFromCredentials(agentIds) {
2096
2123
  const entry = {};
2097
2124
  if (creds.runtime)
2098
2125
  entry.runtime = creds.runtime;
2126
+ if (creds.runtimeModel)
2127
+ entry.runtimeModel = creds.runtimeModel;
2128
+ if (creds.reasoningEffort)
2129
+ entry.reasoningEffort = creds.reasoningEffort;
2130
+ if (typeof creds.thinking === "boolean")
2131
+ entry.thinking = creds.thinking;
2099
2132
  if (creds.cwd)
2100
2133
  entry.cwd = creds.cwd;
2101
2134
  if (creds.openclawGateway)
@@ -2104,8 +2137,16 @@ function readAgentRuntimesFromCredentials(agentIds) {
2104
2137
  entry.openclawAgent = creds.openclawAgent;
2105
2138
  if (creds.hermesProfile)
2106
2139
  entry.hermesProfile = creds.hermesProfile;
2107
- if (entry.runtime || entry.cwd || entry.openclawGateway || entry.openclawAgent || entry.hermesProfile)
2140
+ if (entry.runtime ||
2141
+ entry.runtimeModel ||
2142
+ entry.reasoningEffort ||
2143
+ typeof entry.thinking === "boolean" ||
2144
+ entry.cwd ||
2145
+ entry.openclawGateway ||
2146
+ entry.openclawAgent ||
2147
+ entry.hermesProfile) {
2108
2148
  out[id] = entry;
2149
+ }
2109
2150
  }
2110
2151
  catch {
2111
2152
  // best-effort — skip agents with unreadable credentials
@@ -2309,6 +2350,30 @@ function pickRuntime(params) {
2309
2350
  }
2310
2351
  return undefined;
2311
2352
  }
2353
+ function pickRuntimeSelection(params) {
2354
+ const out = {};
2355
+ const runtimeModel = pickString(params.runtimeModel, params.credentials?.runtimeModel);
2356
+ const reasoningEffort = pickString(params.reasoningEffort, params.credentials?.reasoningEffort);
2357
+ if (runtimeModel)
2358
+ out.runtimeModel = runtimeModel;
2359
+ if (reasoningEffort)
2360
+ out.reasoningEffort = reasoningEffort;
2361
+ if (typeof params.thinking === "boolean") {
2362
+ out.thinking = params.thinking;
2363
+ }
2364
+ else if (typeof params.credentials?.thinking === "boolean") {
2365
+ out.thinking = params.credentials.thinking;
2366
+ }
2367
+ return out;
2368
+ }
2369
+ function pickString(...values) {
2370
+ for (const value of values) {
2371
+ const trimmed = value?.trim();
2372
+ if (trimmed)
2373
+ return trimmed;
2374
+ }
2375
+ return undefined;
2376
+ }
2312
2377
  function assertKnownRuntime(runtime) {
2313
2378
  const mod = getAdapterModule(runtime);
2314
2379
  if (!mod) {