@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.
- package/dist/agent-discovery.d.ts +6 -0
- package/dist/agent-discovery.js +6 -0
- package/dist/attention-policy-fetcher.d.ts +14 -0
- package/dist/attention-policy-fetcher.js +59 -0
- package/dist/cloud-daemon.js +8 -0
- package/dist/cloud-gateway-runtime.d.ts +29 -0
- package/dist/cloud-gateway-runtime.js +122 -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 +32 -7
- package/dist/gateway/channels/botcord.js +29 -9
- package/dist/gateway/channels/login-session.d.ts +12 -0
- package/dist/gateway/channels/login-session.js +20 -2
- package/dist/gateway/channels/sanitize.d.ts +5 -18
- package/dist/gateway/channels/sanitize.js +5 -54
- package/dist/gateway/channels/text-split.d.ts +5 -11
- package/dist/gateway/channels/text-split.js +5 -31
- package/dist/gateway/dispatcher.d.ts +7 -1
- package/dist/gateway/dispatcher.js +88 -8
- package/dist/gateway/gateway.d.ts +16 -1
- package/dist/gateway/gateway.js +21 -0
- package/dist/gateway/policy-resolver.js +17 -9
- package/dist/gateway/runtimes/deepseek-tui.js +86 -19
- package/dist/gateway/types.d.ts +12 -57
- package/dist/gateway-control.js +18 -9
- package/dist/provision.d.ts +9 -3
- package/dist/provision.js +181 -9
- package/dist/room-recovery-context.d.ts +11 -0
- package/dist/room-recovery-context.js +97 -0
- 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__/attention-policy-fetcher.test.ts +67 -0
- package/src/__tests__/cloud-gateway-runtime.test.ts +127 -0
- package/src/__tests__/daemon-config-map.test.ts +26 -1
- package/src/__tests__/gateway-control.test.ts +136 -0
- package/src/__tests__/policy-resolver.test.ts +20 -0
- package/src/__tests__/provision.test.ts +124 -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/attention-policy-fetcher.ts +87 -0
- package/src/cloud-daemon.ts +8 -0
- package/src/cloud-gateway-runtime.ts +171 -0
- package/src/daemon-config-map.ts +17 -4
- package/src/daemon.ts +38 -9
- package/src/gateway/__tests__/botcord-channel.test.ts +97 -0
- package/src/gateway/__tests__/deepseek-tui-adapter.test.ts +207 -1
- package/src/gateway/__tests__/dispatcher.test.ts +56 -0
- package/src/gateway/channels/botcord.ts +32 -8
- package/src/gateway/channels/login-session.ts +20 -2
- package/src/gateway/channels/sanitize.ts +8 -66
- package/src/gateway/channels/text-split.ts +5 -27
- package/src/gateway/dispatcher.ts +123 -27
- package/src/gateway/gateway.ts +29 -0
- package/src/gateway/policy-resolver.ts +20 -9
- package/src/gateway/runtimes/deepseek-tui.ts +86 -19
- package/src/gateway/types.ts +31 -59
- package/src/gateway-control.ts +21 -9
- package/src/provision.ts +202 -11
- package/src/room-recovery-context.ts +131 -0
- package/src/runtime-models.ts +972 -0
- package/src/runtime-route-options.ts +52 -0
package/dist/gateway-control.js
CHANGED
|
@@ -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
|
|
112
|
-
if (
|
|
111
|
+
const resolved = sessions.resolve(loginId);
|
|
112
|
+
if (resolved.state !== "live") {
|
|
113
113
|
return {
|
|
114
114
|
ok: false,
|
|
115
|
-
error:
|
|
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
|
|
147
|
-
if (
|
|
149
|
+
const resolved = sessions.resolve(loginId);
|
|
150
|
+
if (resolved.state !== "live") {
|
|
148
151
|
return {
|
|
149
152
|
ok: false,
|
|
150
|
-
error:
|
|
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
|
|
663
|
-
if (
|
|
668
|
+
const resolved = sessions.resolve(params.loginId);
|
|
669
|
+
if (resolved.state !== "live") {
|
|
664
670
|
return {
|
|
665
671
|
ok: false,
|
|
666
|
-
error:
|
|
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
|
}
|
package/dist/provision.d.ts
CHANGED
|
@@ -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
|
|
186
|
-
* file-system error for one agent never
|
|
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:
|
|
66
|
-
|
|
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
|
|
1977
|
-
* file-system error for one agent never
|
|
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
|
-
|
|
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 ||
|
|
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[];
|