@botcord/daemon 0.2.77 → 0.2.79

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.
Files changed (66) hide show
  1. package/dist/agent-discovery.d.ts +6 -0
  2. package/dist/agent-discovery.js +6 -0
  3. package/dist/attention-policy-fetcher.d.ts +14 -0
  4. package/dist/attention-policy-fetcher.js +59 -0
  5. package/dist/cloud-daemon.js +8 -0
  6. package/dist/cloud-gateway-runtime.d.ts +29 -0
  7. package/dist/cloud-gateway-runtime.js +122 -0
  8. package/dist/daemon-config-map.d.ts +6 -0
  9. package/dist/daemon-config-map.js +5 -4
  10. package/dist/daemon.d.ts +3 -0
  11. package/dist/daemon.js +32 -7
  12. package/dist/gateway/channels/botcord.js +29 -9
  13. package/dist/gateway/channels/login-session.d.ts +12 -0
  14. package/dist/gateway/channels/login-session.js +20 -2
  15. package/dist/gateway/channels/sanitize.d.ts +5 -18
  16. package/dist/gateway/channels/sanitize.js +5 -54
  17. package/dist/gateway/channels/text-split.d.ts +5 -11
  18. package/dist/gateway/channels/text-split.js +5 -31
  19. package/dist/gateway/dispatcher.d.ts +7 -1
  20. package/dist/gateway/dispatcher.js +88 -8
  21. package/dist/gateway/gateway.d.ts +16 -1
  22. package/dist/gateway/gateway.js +21 -0
  23. package/dist/gateway/policy-resolver.js +17 -9
  24. package/dist/gateway/runtimes/deepseek-tui.js +86 -19
  25. package/dist/gateway/types.d.ts +12 -57
  26. package/dist/gateway-control.js +18 -9
  27. package/dist/provision.d.ts +9 -3
  28. package/dist/provision.js +181 -9
  29. package/dist/room-recovery-context.d.ts +11 -0
  30. package/dist/room-recovery-context.js +97 -0
  31. package/dist/runtime-models.d.ts +17 -0
  32. package/dist/runtime-models.js +953 -0
  33. package/dist/runtime-route-options.d.ts +7 -0
  34. package/dist/runtime-route-options.js +45 -0
  35. package/package.json +2 -2
  36. package/src/__tests__/attention-policy-fetcher.test.ts +67 -0
  37. package/src/__tests__/cloud-gateway-runtime.test.ts +127 -0
  38. package/src/__tests__/daemon-config-map.test.ts +26 -1
  39. package/src/__tests__/gateway-control.test.ts +136 -0
  40. package/src/__tests__/policy-resolver.test.ts +20 -0
  41. package/src/__tests__/provision.test.ts +124 -0
  42. package/src/__tests__/runtime-discovery.test.ts +68 -9
  43. package/src/__tests__/runtime-models.test.ts +333 -0
  44. package/src/agent-discovery.ts +9 -0
  45. package/src/attention-policy-fetcher.ts +87 -0
  46. package/src/cloud-daemon.ts +8 -0
  47. package/src/cloud-gateway-runtime.ts +171 -0
  48. package/src/daemon-config-map.ts +17 -4
  49. package/src/daemon.ts +38 -9
  50. package/src/gateway/__tests__/botcord-channel.test.ts +97 -0
  51. package/src/gateway/__tests__/deepseek-tui-adapter.test.ts +207 -1
  52. package/src/gateway/__tests__/dispatcher.test.ts +56 -0
  53. package/src/gateway/channels/botcord.ts +32 -8
  54. package/src/gateway/channels/login-session.ts +20 -2
  55. package/src/gateway/channels/sanitize.ts +8 -66
  56. package/src/gateway/channels/text-split.ts +5 -27
  57. package/src/gateway/dispatcher.ts +123 -27
  58. package/src/gateway/gateway.ts +29 -0
  59. package/src/gateway/policy-resolver.ts +20 -9
  60. package/src/gateway/runtimes/deepseek-tui.ts +86 -19
  61. package/src/gateway/types.ts +31 -59
  62. package/src/gateway-control.ts +21 -9
  63. package/src/provision.ts +202 -11
  64. package/src/room-recovery-context.ts +131 -0
  65. package/src/runtime-models.ts +972 -0
  66. package/src/runtime-route-options.ts +52 -0
@@ -108,13 +108,16 @@ export function createGatewayControl(ctx) {
108
108
  if (!loginId) {
109
109
  return badParams("upsert_gateway: wechat requires loginId");
110
110
  }
111
- const session = sessions.get(loginId);
112
- if (!session) {
111
+ const resolved = sessions.resolve(loginId);
112
+ if (resolved.state !== "live") {
113
113
  return {
114
114
  ok: false,
115
- error: { code: "login_expired", message: `wechat login session "${loginId}" not found or expired` },
115
+ error: resolved.state === "missing"
116
+ ? { code: "login_missing", message: `wechat login session "${loginId}" not found` }
117
+ : { code: "login_expired", message: `wechat login session "${loginId}" expired` },
116
118
  };
117
119
  }
120
+ const session = resolved.session;
118
121
  if (session.provider !== "wechat") {
119
122
  return badParams(`upsert_gateway: login session provider "${session.provider}" != "wechat"`);
120
123
  }
@@ -143,13 +146,16 @@ export function createGatewayControl(ctx) {
143
146
  if (!loginId) {
144
147
  return badParams("upsert_gateway: feishu requires loginId");
145
148
  }
146
- const session = sessions.get(loginId);
147
- if (!session) {
149
+ const resolved = sessions.resolve(loginId);
150
+ if (resolved.state !== "live") {
148
151
  return {
149
152
  ok: false,
150
- error: { code: "login_expired", message: `feishu login session "${loginId}" not found or expired` },
153
+ error: resolved.state === "missing"
154
+ ? { code: "login_missing", message: `feishu login session "${loginId}" not found` }
155
+ : { code: "login_expired", message: `feishu login session "${loginId}" expired` },
151
156
  };
152
157
  }
158
+ const session = resolved.session;
153
159
  if (session.provider !== "feishu") {
154
160
  return badParams(`upsert_gateway: login session provider "${session.provider}" != "feishu"`);
155
161
  }
@@ -659,13 +665,16 @@ export function createGatewayControl(ctx) {
659
665
  if (!params.accountId || typeof params.accountId !== "string") {
660
666
  return badParams("gateway_recent_senders: accountId is required");
661
667
  }
662
- const session = sessions.get(params.loginId);
663
- if (!session) {
668
+ const resolved = sessions.resolve(params.loginId);
669
+ if (resolved.state !== "live") {
664
670
  return {
665
671
  ok: false,
666
- error: { code: "login_expired", message: `wechat login session "${params.loginId}" not found or expired` },
672
+ error: resolved.state === "missing"
673
+ ? { code: "login_missing", message: `wechat login session "${params.loginId}" not found` }
674
+ : { code: "login_expired", message: `wechat login session "${params.loginId}" expired` },
667
675
  };
668
676
  }
677
+ const session = resolved.session;
669
678
  if (session.provider !== "wechat") {
670
679
  return badParams("gateway_recent_senders: provider does not match login session");
671
680
  }
@@ -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;
@@ -180,10 +182,14 @@ interface HelloIdentityResult {
180
182
  updated: number;
181
183
  skipped: number;
182
184
  }
185
+ interface RuntimeSnapshotCtx {
186
+ gateway?: Gateway;
187
+ }
183
188
  /**
184
189
  * Reconcile every agent identity carried by the `hello.agents` snapshot
185
- * against the on-disk `identity.md`. Best-effort: a malformed entry or a
186
- * file-system error for one agent never aborts the rest.
190
+ * against the on-disk `identity.md` and credentials runtime selectors.
191
+ * Best-effort: a malformed entry or a file-system error for one agent never
192
+ * aborts the rest.
187
193
  *
188
194
  * Identity-snapshot semantics intentionally only touch the metadata
189
195
  * line + Bio body — Role/Boundaries paragraphs the user authored locally
@@ -191,7 +197,7 @@ interface HelloIdentityResult {
191
197
  * (agent provisioned on a different daemon, or workspace cleared) are
192
198
  * silently skipped.
193
199
  */
194
- export declare function applyHelloIdentitySnapshot(snapshot: AgentIdentitySnapshot[] | undefined): HelloIdentityResult;
200
+ export declare function applyHelloIdentitySnapshot(snapshot: AgentIdentitySnapshot[] | undefined, ctx?: RuntimeSnapshotCtx): HelloIdentityResult;
195
201
  interface ReloadResult {
196
202
  reloaded: true;
197
203
  added: string[];
package/dist/provision.js CHANGED
@@ -18,6 +18,9 @@ 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";
23
+ import { handleCloudGatewayRuntimeInbound } from "./cloud-gateway-runtime.js";
21
24
  /**
22
25
  * Build a dispatcher function that routes a `ControlFrame` to the right
23
26
  * handler. Returned function signature matches
@@ -39,7 +42,7 @@ export function createProvisioner(opts) {
39
42
  return { ok: true, result: { pong: true, ts: Date.now() } };
40
43
  case CONTROL_FRAME_TYPES.HELLO: {
41
44
  const params = (frame.params ?? {});
42
- const result = applyHelloIdentitySnapshot(params.agents);
45
+ const result = applyHelloIdentitySnapshot(params.agents, { gateway });
43
46
  daemonLog.debug("hello: identity snapshot applied", {
44
47
  frameId: frame.id,
45
48
  received: params.agents?.length ?? 0,
@@ -60,12 +63,19 @@ export function createProvisioner(opts) {
60
63
  displayName: params.displayName,
61
64
  bio: params.bio,
62
65
  });
66
+ const runtimeResult = applyAgentRuntimeSnapshot(params, { gateway });
67
+ const combined = {
68
+ changed: result.changed || runtimeResult.changed,
69
+ identity: result,
70
+ runtime: runtimeResult,
71
+ };
63
72
  daemonLog.info("update_agent applied", {
64
73
  agentId: params.agentId,
65
- changed: result.changed,
66
- skipped: result.skipped ?? null,
74
+ changed: combined.changed,
75
+ identitySkipped: result.skipped ?? null,
76
+ runtimeSkipped: runtimeResult.skipped ?? null,
67
77
  });
68
- return { ok: true, result };
78
+ return { ok: true, result: combined };
69
79
  }
70
80
  case CONTROL_FRAME_TYPES.PROVISION_AGENT: {
71
81
  const params = (frame.params ?? {});
@@ -264,6 +274,30 @@ export function createProvisioner(opts) {
264
274
  return v.ack;
265
275
  return gatewayControl.handleSend(v.params);
266
276
  }
277
+ case "cloud_gateway_runtime_inbound": {
278
+ const params = (frame.params ?? {});
279
+ const runtimeFrame = params.frame;
280
+ if (!runtimeFrame || typeof runtimeFrame !== "object") {
281
+ return {
282
+ ok: false,
283
+ error: {
284
+ code: "bad_params",
285
+ message: "cloud_gateway_runtime_inbound requires params.frame",
286
+ },
287
+ };
288
+ }
289
+ const result = await handleCloudGatewayRuntimeInbound(gateway, runtimeFrame);
290
+ return result.accepted
291
+ ? { ok: true, result }
292
+ : {
293
+ ok: false,
294
+ result,
295
+ error: result.error ?? {
296
+ code: "runtime_inbound_rejected",
297
+ message: "cloud gateway runtime inbound was rejected",
298
+ },
299
+ };
300
+ }
267
301
  case "list_agent_files": {
268
302
  const params = (frame.params ?? {});
269
303
  if (!params.agentId) {
@@ -783,6 +817,9 @@ function upsertManagedRouteForCredentials(credentials, cfg, gateway) {
783
817
  runtime: credentials.runtime ?? cfg.defaultRoute.adapter,
784
818
  cwd: credentials.cwd ?? agentWorkspaceDir(credentials.agentId),
785
819
  };
820
+ const extraArgs = mergeRuntimeExtraArgs(cfg.defaultRoute.extraArgs, buildRuntimeSelectionExtraArgs(synthRoute.runtime, credentials));
821
+ if (extraArgs)
822
+ synthRoute.extraArgs = extraArgs;
786
823
  if (synthRoute.runtime === "openclaw-acp") {
787
824
  const profile = (cfg.openclawGateways ?? []).find((g) => g.name === credentials.openclawGateway);
788
825
  if (profile) {
@@ -887,6 +924,13 @@ async function materializeCredentials(params, cfg, ctx, explicitCwd) {
887
924
  record.tokenExpiresAt = c.tokenExpiresAt;
888
925
  if (runtime)
889
926
  record.runtime = runtime;
927
+ const runtimeSelection = pickRuntimeSelection(params);
928
+ if (runtimeSelection.runtimeModel)
929
+ record.runtimeModel = runtimeSelection.runtimeModel;
930
+ if (runtimeSelection.reasoningEffort)
931
+ record.reasoningEffort = runtimeSelection.reasoningEffort;
932
+ if (typeof runtimeSelection.thinking === "boolean")
933
+ record.thinking = runtimeSelection.thinking;
890
934
  record.cwd = cwd;
891
935
  const openclawSel = pickOpenclawSelection(params);
892
936
  if (openclawSel.gateway)
@@ -922,6 +966,13 @@ async function materializeCredentials(params, cfg, ctx, explicitCwd) {
922
966
  };
923
967
  if (runtime)
924
968
  record.runtime = runtime;
969
+ const runtimeSelection = pickRuntimeSelection(params);
970
+ if (runtimeSelection.runtimeModel)
971
+ record.runtimeModel = runtimeSelection.runtimeModel;
972
+ if (runtimeSelection.reasoningEffort)
973
+ record.reasoningEffort = runtimeSelection.reasoningEffort;
974
+ if (typeof runtimeSelection.thinking === "boolean")
975
+ record.thinking = runtimeSelection.thinking;
925
976
  record.cwd = cwd;
926
977
  const openclawSel = pickOpenclawSelection(params);
927
978
  if (openclawSel.gateway)
@@ -1449,6 +1500,12 @@ export function collectRuntimeSnapshot(opts = {}) {
1449
1500
  record.version = entry.result.version;
1450
1501
  if (entry.result.path)
1451
1502
  record.path = entry.result.path;
1503
+ const catalog = discoverRuntimeModelCatalog(entry);
1504
+ const models = catalog.models;
1505
+ if (models?.length)
1506
+ record.models = models.slice(0, RUNTIME_MODELS_CAP);
1507
+ if (catalog.parameters?.length)
1508
+ record.parameters = catalog.parameters.slice(0, RUNTIME_PARAMETERS_CAP);
1452
1509
  // Gateway's probe surface doesn't expose an `error` string today — it
1453
1510
  // already swallows throws into `{available: false}`. We leave the wire
1454
1511
  // field blank in that case and let callers treat `!available` as reason
@@ -1502,6 +1559,8 @@ export function attachRuntimeHealth(snapshot, live) {
1502
1559
  }
1503
1560
  /** Maximum number of `endpoints[]` entries persisted per runtime (RFC §3.8.2). */
1504
1561
  export const RUNTIME_ENDPOINTS_CAP = 32;
1562
+ export const RUNTIME_MODELS_CAP = 128;
1563
+ export const RUNTIME_PARAMETERS_CAP = 64;
1505
1564
  export function classifyOpenclawAuthError(message) {
1506
1565
  const text = (message ?? "").toLowerCase();
1507
1566
  if (!text)
@@ -1971,10 +2030,84 @@ function openclawBindingIndex() {
1971
2030
  }
1972
2031
  return out;
1973
2032
  }
2033
+ function hasOwnField(obj, key) {
2034
+ return Object.prototype.hasOwnProperty.call(obj, key);
2035
+ }
2036
+ function cleanNullableString(value) {
2037
+ if (typeof value !== "string")
2038
+ return undefined;
2039
+ const trimmed = value.trim();
2040
+ return trimmed || undefined;
2041
+ }
2042
+ function applyAgentRuntimeSnapshot(snapshot, ctx = {}) {
2043
+ const hasRuntimeFields = (hasOwnField(snapshot, "runtime") ||
2044
+ hasOwnField(snapshot, "runtimeModel") ||
2045
+ hasOwnField(snapshot, "reasoningEffort") ||
2046
+ hasOwnField(snapshot, "thinking"));
2047
+ if (!hasRuntimeFields)
2048
+ return { changed: false, skipped: "no_runtime_fields" };
2049
+ const credentialsFile = defaultCredentialsFile(snapshot.agentId);
2050
+ if (!existsSync(credentialsFile)) {
2051
+ return { changed: false, skipped: "credentials_missing" };
2052
+ }
2053
+ const credentials = loadStoredCredentials(credentialsFile);
2054
+ let changed = false;
2055
+ if (hasOwnField(snapshot, "runtime")) {
2056
+ const runtime = cleanNullableString(snapshot.runtime);
2057
+ if (runtime !== credentials.runtime) {
2058
+ if (runtime)
2059
+ credentials.runtime = runtime;
2060
+ else
2061
+ delete credentials.runtime;
2062
+ changed = true;
2063
+ }
2064
+ }
2065
+ if (hasOwnField(snapshot, "runtimeModel")) {
2066
+ const runtimeModel = cleanNullableString(snapshot.runtimeModel);
2067
+ if (runtimeModel !== credentials.runtimeModel) {
2068
+ if (runtimeModel)
2069
+ credentials.runtimeModel = runtimeModel;
2070
+ else
2071
+ delete credentials.runtimeModel;
2072
+ changed = true;
2073
+ }
2074
+ }
2075
+ if (hasOwnField(snapshot, "reasoningEffort")) {
2076
+ const reasoningEffort = cleanNullableString(snapshot.reasoningEffort);
2077
+ if (reasoningEffort !== credentials.reasoningEffort) {
2078
+ if (reasoningEffort)
2079
+ credentials.reasoningEffort = reasoningEffort;
2080
+ else
2081
+ delete credentials.reasoningEffort;
2082
+ changed = true;
2083
+ }
2084
+ }
2085
+ if (hasOwnField(snapshot, "thinking")) {
2086
+ if (typeof snapshot.thinking === "boolean") {
2087
+ if (credentials.thinking !== snapshot.thinking) {
2088
+ credentials.thinking = snapshot.thinking;
2089
+ changed = true;
2090
+ }
2091
+ }
2092
+ else if (typeof credentials.thinking === "boolean") {
2093
+ delete credentials.thinking;
2094
+ changed = true;
2095
+ }
2096
+ }
2097
+ if (!changed)
2098
+ return { changed: false };
2099
+ writeCredentialsFile(credentialsFile, credentials);
2100
+ if (ctx.gateway) {
2101
+ upsertManagedRouteForCredentials(credentials, loadConfig(), ctx.gateway);
2102
+ return { changed: true, routeUpdated: true };
2103
+ }
2104
+ return { changed: true };
2105
+ }
1974
2106
  /**
1975
2107
  * Reconcile every agent identity carried by the `hello.agents` snapshot
1976
- * against the on-disk `identity.md`. Best-effort: a malformed entry or a
1977
- * file-system error for one agent never aborts the rest.
2108
+ * against the on-disk `identity.md` and credentials runtime selectors.
2109
+ * Best-effort: a malformed entry or a file-system error for one agent never
2110
+ * aborts the rest.
1978
2111
  *
1979
2112
  * Identity-snapshot semantics intentionally only touch the metadata
1980
2113
  * line + Bio body — Role/Boundaries paragraphs the user authored locally
@@ -1982,7 +2115,7 @@ function openclawBindingIndex() {
1982
2115
  * (agent provisioned on a different daemon, or workspace cleared) are
1983
2116
  * silently skipped.
1984
2117
  */
1985
- export function applyHelloIdentitySnapshot(snapshot) {
2118
+ export function applyHelloIdentitySnapshot(snapshot, ctx = {}) {
1986
2119
  const out = { updated: 0, skipped: 0 };
1987
2120
  if (!Array.isArray(snapshot))
1988
2121
  return out;
@@ -1996,7 +2129,8 @@ export function applyHelloIdentitySnapshot(snapshot) {
1996
2129
  displayName: entry.displayName,
1997
2130
  bio: entry.bio,
1998
2131
  });
1999
- if (result.changed)
2132
+ const runtimeResult = applyAgentRuntimeSnapshot(entry, ctx);
2133
+ if (result.changed || runtimeResult.changed)
2000
2134
  out.updated += 1;
2001
2135
  else
2002
2136
  out.skipped += 1;
@@ -2096,6 +2230,12 @@ function readAgentRuntimesFromCredentials(agentIds) {
2096
2230
  const entry = {};
2097
2231
  if (creds.runtime)
2098
2232
  entry.runtime = creds.runtime;
2233
+ if (creds.runtimeModel)
2234
+ entry.runtimeModel = creds.runtimeModel;
2235
+ if (creds.reasoningEffort)
2236
+ entry.reasoningEffort = creds.reasoningEffort;
2237
+ if (typeof creds.thinking === "boolean")
2238
+ entry.thinking = creds.thinking;
2099
2239
  if (creds.cwd)
2100
2240
  entry.cwd = creds.cwd;
2101
2241
  if (creds.openclawGateway)
@@ -2104,8 +2244,16 @@ function readAgentRuntimesFromCredentials(agentIds) {
2104
2244
  entry.openclawAgent = creds.openclawAgent;
2105
2245
  if (creds.hermesProfile)
2106
2246
  entry.hermesProfile = creds.hermesProfile;
2107
- if (entry.runtime || entry.cwd || entry.openclawGateway || entry.openclawAgent || entry.hermesProfile)
2247
+ if (entry.runtime ||
2248
+ entry.runtimeModel ||
2249
+ entry.reasoningEffort ||
2250
+ typeof entry.thinking === "boolean" ||
2251
+ entry.cwd ||
2252
+ entry.openclawGateway ||
2253
+ entry.openclawAgent ||
2254
+ entry.hermesProfile) {
2108
2255
  out[id] = entry;
2256
+ }
2109
2257
  }
2110
2258
  catch {
2111
2259
  // best-effort — skip agents with unreadable credentials
@@ -2309,6 +2457,30 @@ function pickRuntime(params) {
2309
2457
  }
2310
2458
  return undefined;
2311
2459
  }
2460
+ function pickRuntimeSelection(params) {
2461
+ const out = {};
2462
+ const runtimeModel = pickString(params.runtimeModel, params.credentials?.runtimeModel);
2463
+ const reasoningEffort = pickString(params.reasoningEffort, params.credentials?.reasoningEffort);
2464
+ if (runtimeModel)
2465
+ out.runtimeModel = runtimeModel;
2466
+ if (reasoningEffort)
2467
+ out.reasoningEffort = reasoningEffort;
2468
+ if (typeof params.thinking === "boolean") {
2469
+ out.thinking = params.thinking;
2470
+ }
2471
+ else if (typeof params.credentials?.thinking === "boolean") {
2472
+ out.thinking = params.credentials.thinking;
2473
+ }
2474
+ return out;
2475
+ }
2476
+ function pickString(...values) {
2477
+ for (const value of values) {
2478
+ const trimmed = value?.trim();
2479
+ if (trimmed)
2480
+ return trimmed;
2481
+ }
2482
+ return undefined;
2483
+ }
2312
2484
  function assertKnownRuntime(runtime) {
2313
2485
  const mod = getAdapterModule(runtime);
2314
2486
  if (!mod) {
@@ -0,0 +1,11 @@
1
+ import type { GatewayInboundMessage } from "./gateway/index.js";
2
+ export interface RecentRoomMessagesRecoveryOptions {
3
+ credentialPathByAgentId: Map<string, string>;
4
+ defaultCredentialsPath?: string;
5
+ hubBaseUrl?: string;
6
+ limit?: number;
7
+ log?: {
8
+ warn: (msg: string, meta?: Record<string, unknown>) => void;
9
+ };
10
+ }
11
+ export declare function createRecentRoomMessagesRecoveryBuilder(opts: RecentRoomMessagesRecoveryOptions): (message: GatewayInboundMessage) => Promise<string | null>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Build a compact, deterministic recovery block from recent Hub room messages.
3
+ * Used when a runtime-native session is discarded and the same turn is retried
4
+ * in a fresh session.
5
+ */
6
+ import { BotCordClient, loadStoredCredentials } from "@botcord/protocol-core";
7
+ import { sanitizeUntrustedContent } from "./gateway/index.js";
8
+ const DEFAULT_RECENT_LIMIT = 20;
9
+ const MAX_MESSAGE_TEXT_CHARS = 1200;
10
+ function stripNewlines(s) {
11
+ return s.replace(/[\r\n]+/g, " ");
12
+ }
13
+ function messageLabel(m) {
14
+ const name = typeof m.from_name === "string" && m.from_name.trim()
15
+ ? m.from_name
16
+ : typeof m.from === "string" && m.from.trim()
17
+ ? m.from
18
+ : "unknown";
19
+ return sanitizeUntrustedContent(stripNewlines(name));
20
+ }
21
+ function formatRecentMessages(messages) {
22
+ if (messages.length === 0)
23
+ return "[Recent Room Messages]\n(none)";
24
+ const chronological = [...messages].reverse();
25
+ const lines = ["[Recent Room Messages]"];
26
+ for (const m of chronological) {
27
+ const text = typeof m.text === "string" ? m.text.trim() : "";
28
+ if (!text)
29
+ continue;
30
+ const ts = typeof m.ts === "string" ? m.ts : "";
31
+ const topic = typeof m.topic_title === "string" && m.topic_title.trim()
32
+ ? ` topic=${sanitizeUntrustedContent(stripNewlines(m.topic_title))}`
33
+ : typeof m.topic_id === "string" && m.topic_id
34
+ ? ` topic=${sanitizeUntrustedContent(stripNewlines(m.topic_id))}`
35
+ : "";
36
+ const safeText = sanitizeUntrustedContent(text.length > MAX_MESSAGE_TEXT_CHARS
37
+ ? `${text.slice(0, MAX_MESSAGE_TEXT_CHARS)}...`
38
+ : text);
39
+ lines.push(`- ${ts ? `${ts} ` : ""}${messageLabel(m)}${topic}: ${safeText}`);
40
+ }
41
+ return lines.join("\n");
42
+ }
43
+ export function createRecentRoomMessagesRecoveryBuilder(opts) {
44
+ const clients = new Map();
45
+ const limit = opts.limit ?? DEFAULT_RECENT_LIMIT;
46
+ function getClient(accountId) {
47
+ const existing = clients.get(accountId);
48
+ if (existing)
49
+ return existing.client;
50
+ const credsPath = opts.credentialPathByAgentId.get(accountId) ?? opts.defaultCredentialsPath;
51
+ if (!credsPath) {
52
+ opts.log?.warn("daemon.recovery-context.no-credentials", { accountId });
53
+ return null;
54
+ }
55
+ try {
56
+ const creds = loadStoredCredentials(credsPath);
57
+ const client = new BotCordClient({
58
+ hubUrl: opts.hubBaseUrl ?? creds.hubUrl,
59
+ agentId: creds.agentId,
60
+ keyId: creds.keyId,
61
+ privateKey: creds.privateKey,
62
+ ...(creds.token ? { token: creds.token } : {}),
63
+ ...(creds.tokenExpiresAt !== undefined
64
+ ? { tokenExpiresAt: creds.tokenExpiresAt }
65
+ : {}),
66
+ });
67
+ clients.set(accountId, { client, credentialsPath: credsPath });
68
+ return client;
69
+ }
70
+ catch (err) {
71
+ opts.log?.warn("daemon.recovery-context.client-init-failed", {
72
+ accountId,
73
+ credsPath,
74
+ error: err instanceof Error ? err.message : String(err),
75
+ });
76
+ return null;
77
+ }
78
+ }
79
+ return async (message) => {
80
+ const client = getClient(message.accountId);
81
+ if (!client)
82
+ return null;
83
+ try {
84
+ const body = await client.roomMessages(message.conversation.id, { limit });
85
+ const messages = Array.isArray(body?.messages) ? body.messages : [];
86
+ return formatRecentMessages(messages);
87
+ }
88
+ catch (err) {
89
+ opts.log?.warn("daemon.recovery-context.fetch-failed", {
90
+ accountId: message.accountId,
91
+ roomId: message.conversation.id,
92
+ error: err instanceof Error ? err.message : String(err),
93
+ });
94
+ return null;
95
+ }
96
+ };
97
+ }
@@ -0,0 +1,17 @@
1
+ import type { RuntimeModelProbe, RuntimeParameterProbe } from "@botcord/protocol-core";
2
+ import type { RuntimeProbeEntry } from "./adapters/runtimes.js";
3
+ export interface RuntimeModelDiscovery {
4
+ models?: RuntimeModelProbe[];
5
+ parameters?: RuntimeParameterProbe[];
6
+ }
7
+ export declare function discoverRuntimeModelCatalog(entry: RuntimeProbeEntry): RuntimeModelDiscovery;
8
+ export declare function discoverRuntimeModels(entry: RuntimeProbeEntry): RuntimeModelProbe[] | undefined;
9
+ export declare function discoverRuntimeParameters(entry: RuntimeProbeEntry): RuntimeParameterProbe[] | undefined;
10
+ export declare function discoverClaudeModels(): RuntimeModelProbe[];
11
+ export declare function discoverCodexModels(command: string | undefined): RuntimeModelProbe[] | undefined;
12
+ export declare function parseCodexModelCatalog(raw: string): RuntimeModelProbe[] | undefined;
13
+ export declare function discoverDeepseekModels(command: string | undefined): RuntimeModelProbe[] | undefined;
14
+ export declare function parseDeepseekModelList(raw: string): RuntimeModelProbe[] | undefined;
15
+ export declare function discoverKimiModels(): RuntimeModelProbe[] | undefined;
16
+ export declare function parseKimiConfigModels(raw: string): RuntimeModelProbe[] | undefined;
17
+ export declare function parseKimiRuntimeParameters(raw: string): RuntimeParameterProbe[];