@syengup/friday-channel-next 0.1.30 → 0.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/index.js +1 -1
- package/dist/src/agent/abort-run.d.ts +12 -1
- package/dist/src/agent/abort-run.js +24 -9
- package/dist/src/agent/dispatch-bridge.d.ts +1 -1
- package/dist/src/agent/media-bridge.d.ts +8 -1
- package/dist/src/agent/media-bridge.js +23 -2
- package/dist/src/agent/node-pairing-bridge.d.ts +11 -8
- package/dist/src/agent/node-pairing-bridge.js +6 -2
- package/dist/src/agent/subagent-registry.js +0 -3
- package/dist/src/agent-forward-runtime.d.ts +15 -0
- package/dist/src/agent-forward-runtime.js +2 -0
- package/dist/src/agent-id.d.ts +8 -0
- package/dist/src/agent-id.js +21 -0
- package/dist/src/channel-actions.js +48 -15
- package/dist/src/channel.js +22 -3
- package/dist/src/collect-message-media-paths.js +10 -1
- package/dist/src/friday-session.js +34 -10
- package/dist/src/history/normalize-message.js +22 -8
- package/dist/src/http/handlers/agent-config.d.ts +27 -0
- package/dist/src/http/handlers/agent-config.js +188 -0
- package/dist/src/http/handlers/agent-files.d.ts +21 -0
- package/dist/src/http/handlers/agent-files.js +137 -0
- package/dist/src/http/handlers/agent-tools-catalog.d.ts +10 -0
- package/dist/src/http/handlers/agent-tools-catalog.js +33 -0
- package/dist/src/http/handlers/agents-list.js +1 -19
- package/dist/src/http/handlers/cancel.js +14 -6
- package/dist/src/http/handlers/device-approve.js +3 -1
- package/dist/src/http/handlers/files-download.js +6 -8
- package/dist/src/http/handlers/files.d.ts +16 -0
- package/dist/src/http/handlers/files.js +81 -13
- package/dist/src/http/handlers/health.js +18 -4
- package/dist/src/http/handlers/history-messages.js +1 -1
- package/dist/src/http/handlers/history-sessions.js +5 -3
- package/dist/src/http/handlers/messages.js +33 -14
- package/dist/src/http/handlers/models-list.d.ts +5 -0
- package/dist/src/http/handlers/models-list.js +9 -1
- package/dist/src/http/handlers/nodes-approve.js +1 -6
- package/dist/src/http/handlers/plugin-info.js +1 -1
- package/dist/src/http/handlers/sessions-settings.js +15 -10
- package/dist/src/http/server.js +27 -2
- package/dist/src/link-preview/og-parse.js +3 -1
- package/dist/src/link-preview/ssrf-guard.js +6 -2
- package/dist/src/media-fetch.js +4 -1
- package/dist/src/plugin-install-info.js +4 -1
- package/dist/src/session/session-manager.js +9 -3
- package/dist/src/session-usage-store.js +3 -1
- package/dist/src/skills-discovery.d.ts +59 -0
- package/dist/src/skills-discovery.js +252 -0
- package/dist/src/sse/offline-queue.js +4 -1
- package/dist/src/thinking-levels.d.ts +21 -0
- package/dist/src/thinking-levels.js +48 -0
- package/dist/src/tool-catalog.d.ts +53 -0
- package/dist/src/tool-catalog.js +191 -0
- package/dist/src/upgrade-runtime.d.ts +1 -1
- package/dist/src/version.js +4 -2
- package/index.ts +43 -35
- package/install.js +131 -43
- package/package.json +10 -1
- package/src/agent/abort-run.ts +23 -8
- package/src/agent/dispatch-bridge.ts +2 -1
- package/src/agent/media-bridge.test.ts +71 -0
- package/src/agent/media-bridge.ts +30 -1
- package/src/agent/node-pairing-bridge.ts +29 -15
- package/src/agent/run-usage-accumulator.ts +4 -2
- package/src/agent/subagent-registry.ts +0 -4
- package/src/agent-forward-runtime.ts +11 -0
- package/src/agent-id.ts +24 -0
- package/src/agent-run-context-bridge.ts +3 -1
- package/src/channel-actions.test.ts +57 -4
- package/src/channel-actions.ts +41 -15
- package/src/channel.lifecycle.test.ts +41 -0
- package/src/channel.outbound.test.ts +18 -4
- package/src/channel.ts +140 -120
- package/src/collect-message-media-paths.ts +15 -6
- package/src/config.ts +1 -4
- package/src/e2e/agents-list.e2e.test.ts +9 -2
- package/src/e2e/attachments-inbound.e2e.test.ts +5 -1
- package/src/e2e/attachments-outbound.e2e.test.ts +7 -2
- package/src/e2e/auto-approve.integration.test.ts +13 -7
- package/src/e2e/cancel-reconnect-errors.e2e.test.ts +18 -3
- package/src/e2e/connect-and-connected.e2e.test.ts +5 -1
- package/src/e2e/offline-replay.e2e.test.ts +17 -3
- package/src/e2e/send-text.e2e.test.ts +11 -2
- package/src/e2e/slash-commands.e2e.test.ts +5 -1
- package/src/e2e/status-cors-auth.e2e.test.ts +11 -2
- package/src/e2e/subagent-smoke.e2e.test.ts +68 -28
- package/src/e2e/subagent.e2e.test.ts +136 -53
- package/src/e2e/tool-lifecycle.e2e.test.ts +5 -1
- package/src/friday-session.forward-agent.test.ts +44 -12
- package/src/friday-session.ts +44 -20
- package/src/history/normalize-message.test.ts +35 -8
- package/src/history/normalize-message.ts +24 -12
- package/src/history/read-transcript.ts +1 -4
- package/src/http/handlers/agent-config.test.ts +212 -0
- package/src/http/handlers/agent-config.ts +232 -0
- package/src/http/handlers/agent-files.test.ts +136 -0
- package/src/http/handlers/agent-files.ts +149 -0
- package/src/http/handlers/agent-tools-catalog.ts +42 -0
- package/src/http/handlers/agents-list.test.ts +1 -5
- package/src/http/handlers/agents-list.ts +1 -22
- package/src/http/handlers/cancel.test.ts +23 -4
- package/src/http/handlers/cancel.ts +14 -6
- package/src/http/handlers/device-approve.test.ts +12 -3
- package/src/http/handlers/device-approve.ts +33 -21
- package/src/http/handlers/files-download.ts +17 -13
- package/src/http/handlers/files.test.ts +120 -0
- package/src/http/handlers/files.ts +115 -17
- package/src/http/handlers/health.test.ts +43 -11
- package/src/http/handlers/health.ts +22 -6
- package/src/http/handlers/history-messages.test.ts +51 -9
- package/src/http/handlers/history-messages.ts +4 -1
- package/src/http/handlers/history-sessions.test.ts +46 -9
- package/src/http/handlers/history-sessions.ts +5 -3
- package/src/http/handlers/history-set-title.test.ts +14 -5
- package/src/http/handlers/link-preview.test.ts +57 -16
- package/src/http/handlers/link-preview.ts +4 -1
- package/src/http/handlers/messages.test.ts +12 -8
- package/src/http/handlers/messages.ts +64 -21
- package/src/http/handlers/models-list.test.ts +114 -0
- package/src/http/handlers/models-list.ts +26 -8
- package/src/http/handlers/nodes-approve.test.ts +15 -4
- package/src/http/handlers/nodes-approve.ts +38 -40
- package/src/http/handlers/plugin-info.ts +5 -6
- package/src/http/handlers/plugin-upgrade.ts +4 -1
- package/src/http/handlers/sessions-settings.ts +16 -11
- package/src/http/handlers/sse.ts +3 -1
- package/src/http/server.ts +33 -6
- package/src/link-preview/og-parse.test.ts +6 -2
- package/src/link-preview/og-parse.ts +10 -3
- package/src/link-preview/preview-service.ts +4 -1
- package/src/link-preview/ssrf-guard.test.ts +78 -16
- package/src/link-preview/ssrf-guard.ts +7 -2
- package/src/media-fetch.test.ts +8 -3
- package/src/media-fetch.ts +5 -3
- package/src/openclaw.d.ts +41 -10
- package/src/plugin-install-info.ts +20 -9
- package/src/run-metadata.ts +2 -1
- package/src/session/session-manager.ts +19 -11
- package/src/session-usage-snapshot.ts +3 -1
- package/src/session-usage-store.ts +3 -1
- package/src/skills-discovery.test.ts +152 -0
- package/src/skills-discovery.ts +264 -0
- package/src/sse/emitter.test.ts +1 -1
- package/src/sse/emitter.ts +9 -3
- package/src/sse/offline-queue.ts +17 -8
- package/src/test-support/app-simulator.ts +17 -3
- package/src/test-support/mock-dispatch.ts +17 -4
- package/src/thinking-levels.test.ts +143 -0
- package/src/thinking-levels.ts +70 -0
- package/src/tool-catalog.ts +261 -0
- package/src/upgrade-runtime.ts +4 -2
- package/src/version.ts +6 -2
- package/tsconfig.json +1 -1
|
@@ -53,6 +53,57 @@ function registerStoredFile(file) {
|
|
|
53
53
|
function resolveStoredFile(key) {
|
|
54
54
|
return fileIndex.get(key) ?? fileTokenIndex.get(key);
|
|
55
55
|
}
|
|
56
|
+
/** Clear the in-memory file index. Test-only: simulates a gateway restart. */
|
|
57
|
+
export function clearFileIndexForTest() {
|
|
58
|
+
fileIndex.clear();
|
|
59
|
+
fileTokenIndex.clear();
|
|
60
|
+
externalFileSourceIndex.clear();
|
|
61
|
+
}
|
|
62
|
+
const META_SIDECAR_SUFFIX = ".fnmeta";
|
|
63
|
+
function metaSidecarPath(urlToken) {
|
|
64
|
+
return path.join(getAttachmentsDir(), `${urlToken}${META_SIDECAR_SUFFIX}`);
|
|
65
|
+
}
|
|
66
|
+
function writeAttachmentMetaSidecar(urlToken, filename, mimeType) {
|
|
67
|
+
try {
|
|
68
|
+
fs.writeFileSync(metaSidecarPath(urlToken), JSON.stringify({ filename, mimeType }));
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
logger.warn(`writeAttachmentMetaSidecar failed for "${urlToken}": ${String(err)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Remember the original upload filename for an inbound media file.
|
|
76
|
+
*
|
|
77
|
+
* When a user sends an attachment, core's media-store copies it to
|
|
78
|
+
* `~/.openclaw/media/inbound/<uuid>` — a bare uuid with no extension and no original
|
|
79
|
+
* name — and the transcript records THAT path. So on history rebuild the original name
|
|
80
|
+
* is unrecoverable. We stash it here (keyed by the inbound basename, reusing the sidecar
|
|
81
|
+
* scheme but inside our own attachments dir) at send time, while we still know it.
|
|
82
|
+
*/
|
|
83
|
+
export function rememberInboundMediaName(inboundPath, filename, mimeType) {
|
|
84
|
+
const key = path.basename(inboundPath);
|
|
85
|
+
const name = filename.trim();
|
|
86
|
+
if (!key || !name)
|
|
87
|
+
return;
|
|
88
|
+
writeAttachmentMetaSidecar(key, name, mimeType);
|
|
89
|
+
}
|
|
90
|
+
function readAttachmentMetaSidecar(urlToken) {
|
|
91
|
+
try {
|
|
92
|
+
const raw = fs.readFileSync(metaSidecarPath(urlToken), "utf8");
|
|
93
|
+
const parsed = JSON.parse(raw);
|
|
94
|
+
if (parsed && typeof parsed.filename === "string" && parsed.filename) {
|
|
95
|
+
return {
|
|
96
|
+
filename: parsed.filename,
|
|
97
|
+
mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : "",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Missing or malformed sidecar (e.g. attachment stored before this fix) — caller
|
|
103
|
+
// falls back to the on-disk basename.
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
56
107
|
/**
|
|
57
108
|
* Read a file from `attachments/` by URL path token (disk basename).
|
|
58
109
|
* Used when the in-memory index was cleared after a gateway restart.
|
|
@@ -61,13 +112,21 @@ export function readAttachmentFileFromDisk(fileToken) {
|
|
|
61
112
|
const safe = path.basename(fileToken);
|
|
62
113
|
if (!safe || safe === "." || safe === "..")
|
|
63
114
|
return null;
|
|
115
|
+
if (safe.endsWith(META_SIDECAR_SUFFIX))
|
|
116
|
+
return null;
|
|
64
117
|
const dir = getAttachmentsDir();
|
|
65
118
|
const full = path.join(dir, safe);
|
|
66
119
|
if (!fs.existsSync(full) || !fs.statSync(full).isFile())
|
|
67
120
|
return null;
|
|
68
121
|
try {
|
|
69
122
|
const buffer = fs.readFileSync(full);
|
|
70
|
-
|
|
123
|
+
const meta = readAttachmentMetaSidecar(safe);
|
|
124
|
+
return {
|
|
125
|
+
buffer,
|
|
126
|
+
mimeType: meta?.mimeType || guessMimeType(safe),
|
|
127
|
+
filename: meta?.filename || safe,
|
|
128
|
+
diskName: safe,
|
|
129
|
+
};
|
|
71
130
|
}
|
|
72
131
|
catch {
|
|
73
132
|
return null;
|
|
@@ -97,16 +156,19 @@ export function normalizeAgentMediaPath(raw) {
|
|
|
97
156
|
}
|
|
98
157
|
return s;
|
|
99
158
|
}
|
|
100
|
-
function copyLocalFileToAttachments(sourcePath) {
|
|
159
|
+
function copyLocalFileToAttachments(sourcePath, originalFilename) {
|
|
101
160
|
const resolvedPath = normalizeAgentMediaPath(sourcePath);
|
|
102
|
-
const
|
|
161
|
+
const diskBasename = path.basename(resolvedPath);
|
|
162
|
+
// Prefer the caller-supplied original name (recovered from an inbound sidecar); fall
|
|
163
|
+
// back to the on-disk basename (which for core inbound media is a bare uuid).
|
|
164
|
+
const filename = originalFilename?.trim() || diskBasename;
|
|
103
165
|
if (!filename)
|
|
104
166
|
return null;
|
|
105
167
|
try {
|
|
106
168
|
if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isFile())
|
|
107
169
|
return null;
|
|
108
170
|
const id = crypto.randomUUID();
|
|
109
|
-
const ext = path.extname(filename);
|
|
171
|
+
const ext = path.extname(filename) || path.extname(diskBasename);
|
|
110
172
|
const urlToken = ext ? `${id}${ext}` : id;
|
|
111
173
|
const storedPath = path.join(getAttachmentsDir(), urlToken);
|
|
112
174
|
try {
|
|
@@ -131,6 +193,7 @@ function copyLocalFileToAttachments(sourcePath) {
|
|
|
131
193
|
createdAt: Date.now(),
|
|
132
194
|
};
|
|
133
195
|
registerStoredFile(file);
|
|
196
|
+
writeAttachmentMetaSidecar(urlToken, filename, mimeType);
|
|
134
197
|
return file;
|
|
135
198
|
}
|
|
136
199
|
catch (err) {
|
|
@@ -151,7 +214,7 @@ export function storeFile(buffer, filename, mimeType) {
|
|
|
151
214
|
fs.writeFileSync(storedPath, buffer);
|
|
152
215
|
}
|
|
153
216
|
catch (err) {
|
|
154
|
-
throw new Error(`Failed to store file: ${String(err)}
|
|
217
|
+
throw new Error(`Failed to store file: ${String(err)}`, { cause: err });
|
|
155
218
|
}
|
|
156
219
|
const file = {
|
|
157
220
|
id,
|
|
@@ -163,6 +226,7 @@ export function storeFile(buffer, filename, mimeType) {
|
|
|
163
226
|
createdAt: Date.now(),
|
|
164
227
|
};
|
|
165
228
|
registerStoredFile(file);
|
|
229
|
+
writeAttachmentMetaSidecar(urlToken, safeFilename, mimeType);
|
|
166
230
|
return file;
|
|
167
231
|
}
|
|
168
232
|
/**
|
|
@@ -196,7 +260,7 @@ export function fridayFilesPublicUrl(ref) {
|
|
|
196
260
|
}
|
|
197
261
|
const disk = readAttachmentFileFromDisk(lookupKey);
|
|
198
262
|
if (disk) {
|
|
199
|
-
return `/friday-next/files/${encodeURIComponent(disk.
|
|
263
|
+
return `/friday-next/files/${encodeURIComponent(disk.diskName)}`;
|
|
200
264
|
}
|
|
201
265
|
const trimmed = ref.trim();
|
|
202
266
|
if (trimmed.startsWith("/friday-next/files/")) {
|
|
@@ -215,10 +279,10 @@ export function readFile(id) {
|
|
|
215
279
|
if (!file)
|
|
216
280
|
return { buffer: null, mimeType: "application/octet-stream" };
|
|
217
281
|
try {
|
|
218
|
-
return { buffer: fs.readFileSync(file.path), mimeType: file.mimeType };
|
|
282
|
+
return { buffer: fs.readFileSync(file.path), mimeType: file.mimeType, filename: file.filename };
|
|
219
283
|
}
|
|
220
284
|
catch {
|
|
221
|
-
return { buffer: null, mimeType: file.mimeType };
|
|
285
|
+
return { buffer: null, mimeType: file.mimeType, filename: file.filename };
|
|
222
286
|
}
|
|
223
287
|
}
|
|
224
288
|
/**
|
|
@@ -262,19 +326,23 @@ export function resolveMediaAttachment(localPath) {
|
|
|
262
326
|
const fallback = path.basename(token);
|
|
263
327
|
return { fileName: fallback, url: localPath };
|
|
264
328
|
}
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
329
|
+
const basename = path.basename(localPath);
|
|
330
|
+
if (!basename)
|
|
267
331
|
return null;
|
|
268
|
-
|
|
332
|
+
// Core inbound media is stored as a bare uuid (no name/extension). Recover the original
|
|
333
|
+
// upload name we stashed at send time, keyed by that uuid basename.
|
|
334
|
+
const remembered = readAttachmentMetaSidecar(basename)?.filename;
|
|
335
|
+
const originalName = remembered || basename;
|
|
336
|
+
const stored = copyLocalFileToAttachments(localPath, originalName);
|
|
269
337
|
if (!stored) {
|
|
270
338
|
// Best-effort fallback: still return a Friday URL so app can receive attachment event.
|
|
271
339
|
// Download handler will try reading external source path lazily by token.
|
|
272
340
|
const id = crypto.randomUUID();
|
|
273
|
-
const ext = path.extname(
|
|
341
|
+
const ext = path.extname(originalName);
|
|
274
342
|
const token = ext ? `${id}${ext}` : id;
|
|
275
343
|
externalFileSourceIndex.set(token, normalizeAgentMediaPath(localPath));
|
|
276
344
|
return {
|
|
277
|
-
fileName:
|
|
345
|
+
fileName: originalName,
|
|
278
346
|
url: `/friday-next/files/${encodeURIComponent(token)}`,
|
|
279
347
|
};
|
|
280
348
|
}
|
|
@@ -43,7 +43,10 @@ export async function handleHealth(req, res) {
|
|
|
43
43
|
if (nodeDeviceId) {
|
|
44
44
|
result.nodePairing = await checkNodePairing(nodeDeviceId, selfHeal, result, log);
|
|
45
45
|
}
|
|
46
|
-
result.ok =
|
|
46
|
+
result.ok =
|
|
47
|
+
!result.nodePairing ||
|
|
48
|
+
result.nodePairing.status === "ok" ||
|
|
49
|
+
result.nodePairing.status === "pending";
|
|
47
50
|
res.statusCode = 200;
|
|
48
51
|
res.setHeader("Content-Type", "application/json");
|
|
49
52
|
res.end(JSON.stringify(result));
|
|
@@ -108,9 +111,16 @@ async function checkNodePairing(nodeDeviceId, selfHeal, result, log) {
|
|
|
108
111
|
const pendingMatch = pendingNodes.find((entry) => entry.nodeId?.trim().toUpperCase() === normalizedNodeId);
|
|
109
112
|
if (pendingMatch && selfHeal) {
|
|
110
113
|
try {
|
|
111
|
-
const callerScopes = [
|
|
114
|
+
const callerScopes = [
|
|
115
|
+
"operator.admin",
|
|
116
|
+
"operator.pairing",
|
|
117
|
+
"operator.read",
|
|
118
|
+
"operator.write",
|
|
119
|
+
];
|
|
112
120
|
const approved = await approveNodePairing(pendingMatch.requestId, { callerScopes });
|
|
113
|
-
const succeeded = approved != null &&
|
|
121
|
+
const succeeded = approved != null &&
|
|
122
|
+
!("status" in approved && approved.status === "forbidden") &&
|
|
123
|
+
"requestId" in approved;
|
|
114
124
|
(result.repairActions ??= []).push({
|
|
115
125
|
component: "nodePairing",
|
|
116
126
|
action: "approveNodePairing",
|
|
@@ -142,5 +152,9 @@ async function checkNodePairing(nodeDeviceId, selfHeal, result, log) {
|
|
|
142
152
|
if (pendingMatch) {
|
|
143
153
|
return { status: "pending", detail: "Node is pending approval", nodePaired: false };
|
|
144
154
|
}
|
|
145
|
-
return {
|
|
155
|
+
return {
|
|
156
|
+
status: "not_found",
|
|
157
|
+
detail: `Node ${normalizedNodeId} not registered`,
|
|
158
|
+
nodePaired: false,
|
|
159
|
+
};
|
|
146
160
|
}
|
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from "node:url";
|
|
|
13
13
|
import { getFridayNextRuntime } from "../../runtime.js";
|
|
14
14
|
import { extractBearerToken } from "../middleware/auth.js";
|
|
15
15
|
import { normalizeHistoryMessages } from "../../history/normalize-message.js";
|
|
16
|
-
import { readSessionTranscriptRawMessages, resolveSessionId } from "../../history/read-transcript.js";
|
|
16
|
+
import { readSessionTranscriptRawMessages, resolveSessionId, } from "../../history/read-transcript.js";
|
|
17
17
|
import { resolveMediaAttachment } from "./files.js";
|
|
18
18
|
import { readSessionUsageSnapshotFromStore } from "../../session-usage-store.js";
|
|
19
19
|
const DEFAULT_LIMIT = 200;
|
|
@@ -126,12 +126,14 @@ function readAgentSessions(agentId) {
|
|
|
126
126
|
sessionKey: canonicalKey,
|
|
127
127
|
agentId,
|
|
128
128
|
...(readString(entry.sessionId) ? { sessionId: readString(entry.sessionId) } : {}),
|
|
129
|
-
...(readNumber(entry.updatedAt) !== undefined
|
|
130
|
-
|
|
129
|
+
...(readNumber(entry.updatedAt) !== undefined
|
|
130
|
+
? { updatedAt: readNumber(entry.updatedAt) }
|
|
131
|
+
: {}),
|
|
132
|
+
...((readString(entry.model) ?? readString(entry.modelOverride))
|
|
131
133
|
? { model: readString(entry.model) ?? readString(entry.modelOverride) }
|
|
132
134
|
: {}),
|
|
133
135
|
// Server-side session display name (matches OpenClaw's resolution order).
|
|
134
|
-
...(readString(entry.displayName) ?? readString(entry.label)
|
|
136
|
+
...((readString(entry.displayName) ?? readString(entry.label))
|
|
135
137
|
? { title: readString(entry.displayName) ?? readString(entry.label) }
|
|
136
138
|
: {}),
|
|
137
139
|
});
|
|
@@ -19,7 +19,7 @@ import { extractBearerToken } from "../middleware/auth.js";
|
|
|
19
19
|
import { readJsonBody } from "../middleware/body.js";
|
|
20
20
|
import { registerFridaySessionDeviceMapping } from "../../friday-session.js";
|
|
21
21
|
import { touchFridayInbound } from "../../friday-inbound-stats.js";
|
|
22
|
-
import { fridayAttachmentLookupKey, fridayFilesPublicUrl, readFile, resolveMediaAttachment, resolveMediaUrl, } from "./files.js";
|
|
22
|
+
import { fridayAttachmentLookupKey, fridayFilesPublicUrl, readFile, rememberInboundMediaName, resolveMediaAttachment, resolveMediaUrl, } from "./files.js";
|
|
23
23
|
import { runFridayDispatch } from "../../agent/dispatch-bridge.js";
|
|
24
24
|
import { saveInboundMediaBuffer } from "../../agent/media-bridge.js";
|
|
25
25
|
import { contextTokensFromUsageRecord, getRunMetadata, getRunRoute, hasRunFinalDelivered, markRunFinalDelivered, registerRunRoute, setRunMetadata, } from "../../run-metadata.js";
|
|
@@ -148,7 +148,9 @@ export function translateDeliverPayload(pl, kind, meta) {
|
|
|
148
148
|
if (typeof meta?.modelName === "string" && meta.modelName.trim()) {
|
|
149
149
|
nextFridayNext.modelName = meta.modelName.trim();
|
|
150
150
|
}
|
|
151
|
-
if (typeof meta?.totalTokens === "number" &&
|
|
151
|
+
if (typeof meta?.totalTokens === "number" &&
|
|
152
|
+
Number.isFinite(meta.totalTokens) &&
|
|
153
|
+
meta.totalTokens > 0) {
|
|
152
154
|
nextFridayNext.totalTokens = Math.floor(meta.totalTokens);
|
|
153
155
|
}
|
|
154
156
|
if (typeof meta?.contextTokensUsed === "number" &&
|
|
@@ -221,10 +223,16 @@ function pickMetadataFromMessageLike(message) {
|
|
|
221
223
|
? usage.totalTokens
|
|
222
224
|
: undefined) ??
|
|
223
225
|
(typeof usage?.total === "number" && Number.isFinite(usage.total) ? usage.total : undefined) ??
|
|
224
|
-
(typeof usage?.total_tokens === "number" && Number.isFinite(usage.total_tokens)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
226
|
+
(typeof usage?.total_tokens === "number" && Number.isFinite(usage.total_tokens)
|
|
227
|
+
? usage.total_tokens
|
|
228
|
+
: undefined);
|
|
229
|
+
const totalFromMessage = (typeof m.totalTokens === "number" && Number.isFinite(m.totalTokens)
|
|
230
|
+
? m.totalTokens
|
|
231
|
+
: undefined) ??
|
|
232
|
+
(typeof m.total_tokens === "number" && Number.isFinite(m.total_tokens)
|
|
233
|
+
? m.total_tokens
|
|
234
|
+
: undefined);
|
|
235
|
+
const totalTokens = Math.floor(totalFromUsage ?? totalFromMessage ?? 0);
|
|
228
236
|
let contextTokensUsed;
|
|
229
237
|
if (usage) {
|
|
230
238
|
const ctx = contextTokensFromUsageRecord(usage);
|
|
@@ -232,8 +240,12 @@ function pickMetadataFromMessageLike(message) {
|
|
|
232
240
|
contextTokensUsed = ctx;
|
|
233
241
|
}
|
|
234
242
|
}
|
|
235
|
-
const ctxMaxRaw = (typeof m.contextWindow === "number" && Number.isFinite(m.contextWindow)
|
|
236
|
-
|
|
243
|
+
const ctxMaxRaw = (typeof m.contextWindow === "number" && Number.isFinite(m.contextWindow)
|
|
244
|
+
? m.contextWindow
|
|
245
|
+
: undefined) ??
|
|
246
|
+
(typeof m.maxContextTokens === "number" && Number.isFinite(m.maxContextTokens)
|
|
247
|
+
? m.maxContextTokens
|
|
248
|
+
: undefined);
|
|
237
249
|
const contextWindowMax = typeof ctxMaxRaw === "number" && ctxMaxRaw > 0 ? Math.floor(ctxMaxRaw) : undefined;
|
|
238
250
|
if (!modelName && !(totalTokens > 0) && !contextTokensUsed && !contextWindowMax)
|
|
239
251
|
return null;
|
|
@@ -277,11 +289,16 @@ async function buildBodyForAgentWithAttachments(text, attachmentIds) {
|
|
|
277
289
|
return text.trim();
|
|
278
290
|
const mediaRefs = [];
|
|
279
291
|
for (const id of attachmentIds) {
|
|
280
|
-
const { buffer, mimeType } = readFile(fridayAttachmentLookupKey(id));
|
|
292
|
+
const { buffer, mimeType, filename } = readFile(fridayAttachmentLookupKey(id));
|
|
281
293
|
if (!buffer)
|
|
282
294
|
continue;
|
|
283
|
-
const saved = await saveInboundMediaBuffer(buffer, mimeType);
|
|
295
|
+
const saved = await saveInboundMediaBuffer(buffer, mimeType, filename);
|
|
284
296
|
if (saved.id && saved.path) {
|
|
297
|
+
// Core's media-store renames inbound files to a bare uuid (no name/extension) and
|
|
298
|
+
// the transcript records that path — stash the original name now so history rebuild
|
|
299
|
+
// can restore it instead of surfacing the uuid.
|
|
300
|
+
if (filename)
|
|
301
|
+
rememberInboundMediaName(saved.path, filename, mimeType);
|
|
285
302
|
mediaRefs.push(`[media attached: file://${saved.path}]`);
|
|
286
303
|
}
|
|
287
304
|
}
|
|
@@ -402,7 +419,8 @@ export async function handleMessages(req, res) {
|
|
|
402
419
|
dispatcherOptions: {
|
|
403
420
|
deliver: async (pl, info) => {
|
|
404
421
|
let meta = getRunMetadata(runId);
|
|
405
|
-
if (info.kind.toLowerCase() === "final" &&
|
|
422
|
+
if (info.kind.toLowerCase() === "final" &&
|
|
423
|
+
!(meta?.modelName || typeof meta?.totalTokens === "number")) {
|
|
406
424
|
const resolved = await resolveRunMetadataFromRuntimeSession(runtime, baseSessionKey);
|
|
407
425
|
if (resolved) {
|
|
408
426
|
setRunMetadata(runId, resolved);
|
|
@@ -457,9 +475,10 @@ export async function handleMessages(req, res) {
|
|
|
457
475
|
// OpenClaw `pi-embedded-subscribe` gates `streamReasoning` on `typeof onReasoningStream === "function"`.
|
|
458
476
|
// Without this, `emitReasoningStream` never runs and Friday SSE never sees `stream: "thinking"`.
|
|
459
477
|
onReasoningStream: async (pl) => {
|
|
460
|
-
const
|
|
461
|
-
?
|
|
462
|
-
:
|
|
478
|
+
const rawText = typeof pl === "object" && pl !== null && "text" in pl
|
|
479
|
+
? pl.text
|
|
480
|
+
: undefined;
|
|
481
|
+
const text = typeof rawText === "string" ? rawText : "";
|
|
463
482
|
log("REASONING_STREAM", normalizedDeviceId, runId, `textLen=${text.length}`);
|
|
464
483
|
},
|
|
465
484
|
onReasoningEnd: async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { type ThinkingLevelOption } from "../../thinking-levels.js";
|
|
2
3
|
export interface FridayModelEntry {
|
|
3
4
|
id: string;
|
|
4
5
|
name?: string;
|
|
@@ -6,5 +7,9 @@ export interface FridayModelEntry {
|
|
|
6
7
|
reasoning?: boolean;
|
|
7
8
|
contextWindow?: number;
|
|
8
9
|
maxTokens?: number;
|
|
10
|
+
/** Thinking levels this model supports (varies per model). Omitted when only the base set applies. */
|
|
11
|
+
thinkingLevels?: ThinkingLevelOption[];
|
|
12
|
+
/** Provider/model default thinking level, when the gateway reports one. */
|
|
13
|
+
thinkingDefault?: string;
|
|
9
14
|
}
|
|
10
15
|
export declare function handleModelsList(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getFridayAgentForwardRuntime } from "../../agent-forward-runtime.js";
|
|
2
2
|
import { splitModelRef } from "../../session/session-manager.js";
|
|
3
|
+
import { resolveModelThinking } from "../../thinking-levels.js";
|
|
3
4
|
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
5
|
function resolveConfiguredModels() {
|
|
5
6
|
const rt = getFridayAgentForwardRuntime();
|
|
@@ -21,7 +22,7 @@ function resolveConfiguredModels() {
|
|
|
21
22
|
seen.add(modelKey);
|
|
22
23
|
entries.push({
|
|
23
24
|
id: modelKey,
|
|
24
|
-
name: typeof info?.alias === "string" ? info.alias : meta?.name ?? split.modelId,
|
|
25
|
+
name: typeof info?.alias === "string" ? info.alias : (meta?.name ?? split.modelId),
|
|
25
26
|
provider: split.provider,
|
|
26
27
|
reasoning: meta?.reasoning,
|
|
27
28
|
contextWindow: meta?.contextWindow,
|
|
@@ -65,6 +66,13 @@ function resolveConfiguredModels() {
|
|
|
65
66
|
maxTokens: meta?.maxTokens,
|
|
66
67
|
});
|
|
67
68
|
}
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const split = splitModelRef(entry.id);
|
|
71
|
+
const thinking = resolveModelThinking(entry.provider || split.provider, split.modelId);
|
|
72
|
+
entry.thinkingLevels = thinking.levels;
|
|
73
|
+
if (thinking.default)
|
|
74
|
+
entry.thinkingDefault = thinking.default;
|
|
75
|
+
}
|
|
68
76
|
return { models: entries, defaultModel };
|
|
69
77
|
}
|
|
70
78
|
function buildProviderModelMeta(cfg) {
|
|
@@ -58,12 +58,7 @@ export async function handleNodesApprove(req, res) {
|
|
|
58
58
|
if (pendingMatch) {
|
|
59
59
|
const requestId = pendingMatch.requestId;
|
|
60
60
|
log.info(`approving nodeId=${normalizedNodeId} requestId=${requestId}`);
|
|
61
|
-
const callerScopes = [
|
|
62
|
-
"operator.admin",
|
|
63
|
-
"operator.pairing",
|
|
64
|
-
"operator.read",
|
|
65
|
-
"operator.write",
|
|
66
|
-
];
|
|
61
|
+
const callerScopes = ["operator.admin", "operator.pairing", "operator.read", "operator.write"];
|
|
67
62
|
let approved;
|
|
68
63
|
try {
|
|
69
64
|
approved = await approveNodePairing(requestId, { callerScopes });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extractBearerToken } from "../middleware/auth.js";
|
|
2
2
|
import { PLUGIN_VERSION } from "../../version.js";
|
|
3
|
-
import { fetchLatestVersion, getInstallSource, semverGreater
|
|
3
|
+
import { fetchLatestVersion, getInstallSource, semverGreater } from "../../plugin-install-info.js";
|
|
4
4
|
export async function handlePluginInfo(req, res) {
|
|
5
5
|
if (req.method !== "GET") {
|
|
6
6
|
res.statusCode = 405;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { setSessionSettings, getSessionSettings, splitModelRef, resolveAgentDefaults, } from "../../session/session-manager.js";
|
|
2
2
|
import { readJsonBody } from "../middleware/body.js";
|
|
3
3
|
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
+
import { resolveModelThinkingForRef } from "../../thinking-levels.js";
|
|
4
5
|
const VALID_REASONING = new Set(["on", "off", "stream"]);
|
|
5
|
-
const VALID_THINKING = new Set(["off", "minimal", "low", "medium", "high"]);
|
|
6
6
|
export async function handleSessionsSettings(req, res) {
|
|
7
7
|
if (req.method !== "PUT" && req.method !== "GET") {
|
|
8
8
|
res.statusCode = 405;
|
|
@@ -44,12 +44,24 @@ export async function handleSessionsSettings(req, res) {
|
|
|
44
44
|
const reasoningLevel = typeof body?.reasoningLevel === "string" ? body.reasoningLevel : undefined;
|
|
45
45
|
const thinkingLevel = typeof body?.thinkingLevel === "string" ? body.thinkingLevel : undefined;
|
|
46
46
|
const modelRef = typeof body?.modelRef === "string" ? body.modelRef.trim() : undefined;
|
|
47
|
+
// The app omits (or empties) modelRef to mean "use the agent's default model". Resolve that
|
|
48
|
+
// default and write it as an *explicit* override, identical in shape to any other selection — so
|
|
49
|
+
// the agent runs the default exactly the way it runs an explicitly-picked model. Do NOT just
|
|
50
|
+
// clear the override here: the session entry is shared with the OpenClaw core, which stamps it
|
|
51
|
+
// with provenance fields (`modelOverrideSource`, `model`, `modelProvider`); deleting only our
|
|
52
|
+
// three fields leaves those dangling and the core mis-resolves to a fallback model.
|
|
53
|
+
const effectiveModelRef = modelRef || resolveAgentDefaults(sessionKey).model;
|
|
47
54
|
const errors = [];
|
|
48
55
|
if (reasoningLevel !== undefined && !VALID_REASONING.has(reasoningLevel)) {
|
|
49
56
|
errors.push(`reasoningLevel must be one of: ${[...VALID_REASONING].join(", ")}`);
|
|
50
57
|
}
|
|
51
|
-
if (thinkingLevel !== undefined
|
|
52
|
-
|
|
58
|
+
if (thinkingLevel !== undefined) {
|
|
59
|
+
// Thinking levels vary per model, so validate against the levels the *effective* model supports
|
|
60
|
+
// (resolved from the running gateway). Falls back to the base five levels when unresolvable.
|
|
61
|
+
const supported = resolveModelThinkingForRef(effectiveModelRef).levels.map((l) => l.id);
|
|
62
|
+
if (!supported.includes(thinkingLevel)) {
|
|
63
|
+
errors.push(`thinkingLevel must be one of: ${supported.join(", ")}`);
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
66
|
if (errors.length > 0) {
|
|
55
67
|
res.statusCode = 400;
|
|
@@ -57,13 +69,6 @@ export async function handleSessionsSettings(req, res) {
|
|
|
57
69
|
res.end(JSON.stringify({ error: errors.join("; ") }));
|
|
58
70
|
return true;
|
|
59
71
|
}
|
|
60
|
-
// The app omits (or empties) modelRef to mean "use the agent's default model". Resolve that
|
|
61
|
-
// default and write it as an *explicit* override, identical in shape to any other selection — so
|
|
62
|
-
// the agent runs the default exactly the way it runs an explicitly-picked model. Do NOT just
|
|
63
|
-
// clear the override here: the session entry is shared with the OpenClaw core, which stamps it
|
|
64
|
-
// with provenance fields (`modelOverrideSource`, `model`, `modelProvider`); deleting only our
|
|
65
|
-
// three fields leaves those dangling and the core mis-resolves to a fallback model.
|
|
66
|
-
const effectiveModelRef = modelRef || resolveAgentDefaults(sessionKey).model;
|
|
67
72
|
const settings = { reasoningLevel, thinkingLevel };
|
|
68
73
|
if (effectiveModelRef) {
|
|
69
74
|
const split = splitModelRef(effectiveModelRef);
|
package/dist/src/http/server.js
CHANGED
|
@@ -14,6 +14,9 @@ import { handleNodesApprove } from "./handlers/nodes-approve.js";
|
|
|
14
14
|
import { handleSessionsSettings } from "./handlers/sessions-settings.js";
|
|
15
15
|
import { handleModelsList } from "./handlers/models-list.js";
|
|
16
16
|
import { handleAgentsList } from "./handlers/agents-list.js";
|
|
17
|
+
import { handleAgentConfig } from "./handlers/agent-config.js";
|
|
18
|
+
import { handleAgentFiles } from "./handlers/agent-files.js";
|
|
19
|
+
import { handleAgentToolsCatalog } from "./handlers/agent-tools-catalog.js";
|
|
17
20
|
import { handleHistorySessions } from "./handlers/history-sessions.js";
|
|
18
21
|
import { handleHistoryMessages } from "./handlers/history-messages.js";
|
|
19
22
|
import { handleHistorySetTitle } from "./handlers/history-set-title.js";
|
|
@@ -62,7 +65,8 @@ async function handleFridayNextRoute(req, res) {
|
|
|
62
65
|
if (req.method === "POST" && pathname === "/friday-next/nodes-approve") {
|
|
63
66
|
return await handleNodesApprove(req, res);
|
|
64
67
|
}
|
|
65
|
-
if ((req.method === "PUT" || req.method === "GET") &&
|
|
68
|
+
if ((req.method === "PUT" || req.method === "GET") &&
|
|
69
|
+
pathname === "/friday-next/sessions/settings") {
|
|
66
70
|
return await handleSessionsSettings(req, res);
|
|
67
71
|
}
|
|
68
72
|
if (req.method === "GET" && pathname === "/friday-next/models") {
|
|
@@ -71,6 +75,26 @@ async function handleFridayNextRoute(req, res) {
|
|
|
71
75
|
if (req.method === "GET" && pathname === "/friday-next/agents") {
|
|
72
76
|
return await handleAgentsList(req, res);
|
|
73
77
|
}
|
|
78
|
+
// Routes: GET/PUT /friday-next/agents/{id}/config
|
|
79
|
+
// GET /friday-next/agents/{id}/files
|
|
80
|
+
// GET/PUT /friday-next/agents/{id}/files/{name}
|
|
81
|
+
if (pathname.startsWith("/friday-next/agents/")) {
|
|
82
|
+
const segs = pathname
|
|
83
|
+
.slice("/friday-next/agents/".length)
|
|
84
|
+
.split("/")
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.map((s) => decodeURIComponent(s));
|
|
87
|
+
const [id, sub, name] = segs;
|
|
88
|
+
if (id && sub === "config" && segs.length === 2) {
|
|
89
|
+
return await handleAgentConfig(req, res, id);
|
|
90
|
+
}
|
|
91
|
+
if (id && sub === "files" && (segs.length === 2 || segs.length === 3)) {
|
|
92
|
+
return await handleAgentFiles(req, res, id, name);
|
|
93
|
+
}
|
|
94
|
+
if (id && sub === "tools" && name === "catalog" && segs.length === 3) {
|
|
95
|
+
return await handleAgentToolsCatalog(req, res, id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
74
98
|
if (req.method === "GET" && pathname === "/friday-next/status") {
|
|
75
99
|
return await handleStatus(req, res);
|
|
76
100
|
}
|
|
@@ -83,7 +107,8 @@ async function handleFridayNextRoute(req, res) {
|
|
|
83
107
|
return await handleHistoryMessages(req, res);
|
|
84
108
|
}
|
|
85
109
|
// Route: PUT /friday-next/sessions/title (sync app session name → server displayName)
|
|
86
|
-
if ((req.method === "PUT" || req.method === "POST") &&
|
|
110
|
+
if ((req.method === "PUT" || req.method === "POST") &&
|
|
111
|
+
pathname === "/friday-next/sessions/title") {
|
|
87
112
|
return await handleHistorySetTitle(req, res);
|
|
88
113
|
}
|
|
89
114
|
// Route: GET /friday-next/link-preview?url=... (Open Graph metadata for preview cards)
|
|
@@ -78,7 +78,9 @@ export function parseOpenGraph(html, baseUrl) {
|
|
|
78
78
|
let metaDescription = null;
|
|
79
79
|
for (const match of slice.matchAll(META_TAG_RE)) {
|
|
80
80
|
const tag = match[0];
|
|
81
|
-
const key = (attributeValue(tag, "property") ?? attributeValue(tag, "name"))
|
|
81
|
+
const key = (attributeValue(tag, "property") ?? attributeValue(tag, "name"))
|
|
82
|
+
?.trim()
|
|
83
|
+
.toLowerCase();
|
|
82
84
|
if (!key)
|
|
83
85
|
continue;
|
|
84
86
|
const content = attributeValue(tag, "content");
|
|
@@ -98,8 +98,12 @@ function isPrivateIPv6(ip) {
|
|
|
98
98
|
if (lower === "::" || lower === "::1")
|
|
99
99
|
return true; // unspecified / loopback
|
|
100
100
|
const head = lower.split(":")[0];
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// fc00::/8 (the reserved, never-assigned half of ULA fc00::/7) intentionally NOT blocked:
|
|
102
|
+
// RFC 4193 requires locally-assigned ULA to set the L bit, so real LAN services live in
|
|
103
|
+
// fd00::/8, while fake-IP DNS setups (mihomo/clash tun mode with IPv6, common on gateway
|
|
104
|
+
// hosts) resolve EVERY domain into fc00::/18. Same rationale as 198.18/15 on the IPv4 side.
|
|
105
|
+
if (head.startsWith("fd"))
|
|
106
|
+
return true; // fd00::/8 locally-assigned ULA
|
|
103
107
|
if (/^fe[89ab]/.test(head))
|
|
104
108
|
return true; // fe80::/10 link-local
|
|
105
109
|
return false;
|
package/dist/src/media-fetch.js
CHANGED
|
@@ -8,7 +8,10 @@ import { guessMimeType } from "./http/handlers/files.js";
|
|
|
8
8
|
* gateway's `/friday-next/files/` route — identical to the local-file path. This keeps the app's
|
|
9
9
|
* download story uniform (always talks to the trusted gateway host with a bearer token).
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
// Aligns with openclaw's own remote-media ceiling (DEFAULT_FETCH_MEDIA_MAX_BYTES =
|
|
12
|
+
// MAX_DOCUMENT_BYTES = 100MB). The real per-kind limit is enforced downstream by
|
|
13
|
+
// saveMediaBuffer (via resolveMediaMaxBytes); this is just the download ceiling.
|
|
14
|
+
const MAX_REMOTE_MEDIA_BYTES = 100 * 1024 * 1024; // 100MB
|
|
12
15
|
const REMOTE_MEDIA_TIMEOUT_MS = 20_000;
|
|
13
16
|
export function isHttpUrl(value) {
|
|
14
17
|
return /^https?:\/\//i.test(value.trim());
|
|
@@ -87,7 +87,10 @@ export async function fetchLatestVersion(nowMs) {
|
|
|
87
87
|
const controller = new AbortController();
|
|
88
88
|
const timer = setTimeout(() => controller.abort(), 5000);
|
|
89
89
|
try {
|
|
90
|
-
const res = await fetch(`https://registry.npmjs.org/${PLUGIN_PACKAGE_NAME}/latest`, {
|
|
90
|
+
const res = await fetch(`https://registry.npmjs.org/${PLUGIN_PACKAGE_NAME}/latest`, {
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
headers: { Accept: "application/json" },
|
|
93
|
+
});
|
|
91
94
|
if (res.ok) {
|
|
92
95
|
const body = (await res.json());
|
|
93
96
|
if (typeof body.version === "string" && body.version)
|
|
@@ -118,7 +118,11 @@ export function setSessionSettings(sessionKey, settings, historyDir) {
|
|
|
118
118
|
return {};
|
|
119
119
|
upsertSessionEntry(data, fileKey, sessionKey);
|
|
120
120
|
const fieldKeys = [
|
|
121
|
-
"reasoningLevel",
|
|
121
|
+
"reasoningLevel",
|
|
122
|
+
"thinkingLevel",
|
|
123
|
+
"modelRef",
|
|
124
|
+
"providerOverride",
|
|
125
|
+
"modelOverride",
|
|
122
126
|
];
|
|
123
127
|
let updated = false;
|
|
124
128
|
for (const key of fieldKeys) {
|
|
@@ -193,7 +197,7 @@ export function resolveAgentDefaults(sessionKey) {
|
|
|
193
197
|
const ocCfg = (forwardRt.getConfig() ?? {});
|
|
194
198
|
const agents = ocCfg.agents;
|
|
195
199
|
const targetAgentId = agentIdFromSessionKey(sessionKey);
|
|
196
|
-
const agentEntry = agents?.list?.find((a) => agentIdFromSessionKey(`agent:${String(a
|
|
200
|
+
const agentEntry = agents?.list?.find((a) => agentIdFromSessionKey(`agent:${typeof a?.id === "string" ? a.id : typeof a?.id === "number" ? String(a.id) : ""}:x`) === targetAgentId);
|
|
197
201
|
const agentModel = agentEntry?.model;
|
|
198
202
|
const perAgentModel = typeof agentModel === "string"
|
|
199
203
|
? agentModel
|
|
@@ -204,7 +208,9 @@ export function resolveAgentDefaults(sessionKey) {
|
|
|
204
208
|
const agentDefaults = agents?.defaults;
|
|
205
209
|
const model = agentDefaults?.model;
|
|
206
210
|
const globalModel = typeof model?.primary === "string" ? model.primary : undefined;
|
|
207
|
-
const globalThinking = typeof agentDefaults?.thinkingDefault === "string"
|
|
211
|
+
const globalThinking = typeof agentDefaults?.thinkingDefault === "string"
|
|
212
|
+
? agentDefaults.thinkingDefault
|
|
213
|
+
: undefined;
|
|
208
214
|
return { model: perAgentModel ?? globalModel, thinking: perAgentThinking ?? globalThinking };
|
|
209
215
|
}
|
|
210
216
|
catch {
|
|
@@ -24,7 +24,9 @@ export function readSessionUsageSnapshotFromStore(sessionKeyForStore) {
|
|
|
24
24
|
const cfg = access.getConfig();
|
|
25
25
|
const storeConfig = cfg?.session?.store;
|
|
26
26
|
const canonical = toSessionStoreKey(sessionKeyForStore);
|
|
27
|
-
const storePath = access.resolveStorePath(storeConfig, {
|
|
27
|
+
const storePath = access.resolveStorePath(storeConfig, {
|
|
28
|
+
agentId: agentIdFromSessionKey(canonical),
|
|
29
|
+
});
|
|
28
30
|
const store = access.loadSessionStore(storePath, { skipCache: true });
|
|
29
31
|
const entry = store[canonical] ?? store[sessionKeyForStore.trim()];
|
|
30
32
|
if (!entry || typeof entry !== "object")
|